Compare commits

...

83 Commits

Author SHA1 Message Date
jcreedcmu
8efb060031 Merge pull request #337 from jcreedcmu/jcreed/jump-to-def
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
Add experimental support for Jump-to-def and Find-references
2020-04-28 11:24:55 -04:00
Jason Reed
e242a8fbeb Review comments 2020-04-22 14:09:20 -04:00
Jason Reed
ee591e802f Fix lint violations 2020-04-21 13:19:07 -04:00
Jason Reed
7df8905aa0 Scope tags for ide queries 2020-04-21 13:14:19 -04:00
Shati Patel
23b1c00179 Update changelog 2020-04-21 08:27:29 -07:00
Jason Reed
701804b6a4 Guard find-references with experimental setting 2020-04-21 11:15:12 -04:00
Jason Reed
66665bf25e Refactor definitions/references finding 2020-04-21 11:15:12 -04:00
Jason Reed
1c6b4a6d1e Put CachedOperation in helpers. 2020-04-21 11:15:12 -04:00
Jason Reed
28be98411d Add constants for bqrs column kinds 2020-04-21 11:15:12 -04:00
Jason Reed
5592a77963 Can autodetect language 2020-04-21 11:15:12 -04:00
Jason Reed
a6cd08fb0b Add wrapper for 'resolve queries' cli command 2020-04-21 11:15:12 -04:00
Jason Reed
881c909540 Make rudimentary jump-to-definition work 2020-04-21 11:15:12 -04:00
Henry Mercer
f5e3af02e4 Merge pull request #336 from github/pr-template
Update link in PR template
2020-04-21 12:11:30 +01:00
Shati Patel
3eca4f6734 Update link in PR template 2020-04-20 23:22:25 +01:00
Andrew Eisenberg
596ccdb722 Update change log 2020-04-20 08:50:54 -07:00
Andrew Eisenberg
2aeda002fa Add sort by date added for databases
This uses the dateAdded field on databases. It will only work for 
databases added after that field was added. Otherwise, the dateAdded
property will be undefined.
2020-04-20 08:50:54 -07:00
Andrew Eisenberg
27623f3325 Add dateAdded field for databases
Existing databases will have this value as `undefined`. Newly added
databases will have this value as the Unix timestamp when added.

Stuffing this value into the `options` field of the database. The
name is not quite right, but it seems like this is the right place.
Should I rename?
2020-04-20 08:50:54 -07:00
Andrew Eisenberg
f3df3b9f3e Rename svg for sorting dates add svg for sorting names 2020-04-20 08:50:54 -07:00
Andrew Eisenberg
5850ed3288 refactor: Move icons into light/dark folders
This follows the vscode styles where all icons for dark mode are in
the `dark` folder and all for light mode in the `light` folder. They
always have the same name.
2020-04-20 08:50:54 -07:00
Andrew Eisenberg
a2f8c85359 Add command to sort databases by name
This assumes that we will be adding other sorting mechanisms in the
future, like sort by data added.

Not happy with the icon I chose, but I didn't want to spend too much
time creating a new one.
2020-04-20 08:50:54 -07:00
Andrew Eisenberg
62d9efc4ee Update changelog 2020-04-20 07:55:59 -07:00
Andrew Eisenberg
00026a7727 feat: User can see query text at time of execution
Add new command to view the query text in a synthetic, read-only
document.

Quick eval queries will show the text selected when initially running
the query. Quick eval queries where the user has a single caret
selection will show the entire line of text.
2020-04-20 07:55:59 -07:00
Jason Reed
c292f58e20 Factor out database qlpack inference. 2020-04-20 09:59:41 -04:00
jcreedcmu
6f935ae6e4 Merge pull request #331 from aeisenberg/aeisenberg/changelog
Update changelog
2020-04-16 12:58:57 -04:00
Andrew Eisenberg
1fb65cd7e9 Update changelog 2020-04-16 09:51:01 -07:00
jcreedcmu
21500f0a5b Merge pull request #330 from dbartol/dbartol/unique-highlighting
Highlight `unique` as a keyword
2020-04-15 09:49:24 -04:00
Dave Bartolomeo
efcf9815f0 Update CHANGELOG.md for unique highlighting 2020-04-14 12:53:33 -04:00
Dave Bartolomeo
f8635f41a5 Highlight unique as a keyword
`unique` is really a context-sensitive keyword, but that's even more of a hassle in a TextMate grammars than it is in the compiler itself. We'll just highlight it as a real keyword. The worst that will happen is that existing variables and predicates named "unique" will be highlighted like keywords, which will hopefully just encourage QL developers to rename those anyway.
2020-04-14 12:48:18 -04:00
jcreedcmu
e4df717d2b Merge pull request #324 from jcreedcmu/jcreed/always-save
Add 'Always Save' to running unsaved query dialog
2020-04-07 12:49:07 -04:00
jcreedcmu
9ea4b3936a Merge pull request #326 from p-/qhelp-syntax-highlighting
feat: XML syntax highlighting for qhelp files
2020-04-07 12:10:09 -04:00
Jason Reed
e5305ab4b5 Fix lint compatibly with tsfmt 2020-04-07 12:02:50 -04:00
Jason Reed
c2c86aed0a Add 'Always Save' to running unsaved query dialog 2020-04-07 12:02:50 -04:00
Dave Bartolomeo
2df512f018 Merge pull request #327 from jcreedcmu/jcreed/fix-windows-ci
chore: Fix codeql.cmd overwriting codeql.exe in CI
2020-04-07 11:48:04 -04:00
Jason Reed
ba3381fbf9 Fix codeql.cmd overwriting codeql.exe in CI 2020-04-07 10:06:29 -04:00
Peter Stöckli
869029b856 feat: XML syntax highlighting for .qhelp files 2020-04-04 16:59:02 +02:00
Andrew Eisenberg
b3ad1d6814 fix: Avoid warning user if no new launcher exists
Allow extension to remain on the deprecated launcher.
2020-03-27 08:46:45 -07:00
Andrew Eisenberg
130d3c09e3 build: Enable linting in the build 2020-03-25 12:02:12 -07:00
Andrew Eisenberg
bb28dafc43 lint: Add proper linting for react 2020-03-25 12:02:12 -07:00
Andrew Eisenberg
db6aadbf93 fix: Use a proper 'file' schema for uris 2020-03-25 11:16:32 -07:00
Henry Mercer
d97c8e864d Merge pull request #313 from shati-patel/error-typo
Fix typo in error message
2020-03-25 10:25:54 +00:00
Shati Patel
d8a6368e60 Fix typo in error message
Spotted a small typo ("exterally") and updated the error message.
2020-03-25 08:59:04 +00:00
jcreedcmu
76d6ab4e81 Merge pull request #305 from github/version/bump-to-v1.1.2
Bump version to v1.1.2
2020-03-23 13:01:36 -04:00
github-actions[bot]
bdcabae60e Bump version to v1.1.2 2020-03-23 16:56:34 +00:00
jcreedcmu
aa0fb498a0 Merge pull request #304 from jcreedcmu/jcreed/v1.1.1
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-03-23 12:53:20 -04:00
Jason Reed
176dc1fc71 Better message. 2020-03-23 12:43:03 -04:00
Jason Reed
a0eebb1e5f Fix typo 2020-03-23 12:40:22 -04:00
Jason Reed
2af917284b Capitalize the Brand 2020-03-23 12:30:08 -04:00
Jason Reed
4adb8b6301 Update CHANGELOG for release. 2020-03-23 12:25:24 -04:00
jcreedcmu
8f5ddbd87c Merge pull request #303 from aeisenberg/aeisenberg/deprecate
feat: Display warning when codeql.cmd is used
2020-03-23 12:24:03 -04:00
Andrew Eisenberg
b689e55f61 Merge pull request #302 from aeisenberg/aeisenberg/large-log
feat: Allow large log files to be opened externally
2020-03-23 09:13:42 -07:00
Andrew Eisenberg
7ce3dc2c43 feat: Display warning when codeql.cmd is used
The old launcher has been deprecated and codeql.exe is
recommended.

Fixes #287.
2020-03-23 09:06:53 -07:00
Andrew Eisenberg
eed85e9e28 feat: Allow large log files to be opened externally
If the user tries to open a log file that is too large for vscode's
extension mechanism to handle, reveal the file in the finder/explorer
and let the user open in an external program.
2020-03-23 09:03:15 -07:00
Andrew Eisenberg
0b56092466 Merge pull request #297 from aeisenberg/aeisenberg/updates
fix: Avoid auto-updating the codeql binaries
2020-03-21 21:52:36 -07:00
Andrew Eisenberg
4fce213ca8 fix: Allow autoupdating if no distribution installed 2020-03-20 11:09:29 -07:00
Andrew Eisenberg
8ed7b991be fix: Avoid auto-updating the codeql binaries
On startup, if a new binary is available, request user acceptance
before starting the update.

Fixes #283
2020-03-19 14:16:57 -07:00
Andrew Eisenberg
deb544ab93 Merge pull request #295 from aeisenberg/aeisenberg/lint
lint: Ran the auto-fix command for the linter
2020-03-19 10:40:09 -07:00
jcreedcmu
9ec017a30d Merge pull request #299 from aeisenberg/aeisenberg/package-lock
chore: Remove unnecessary package lock
2020-03-19 13:33:35 -04:00
Andrew Eisenberg
ebdf576196 lint: Run the auto linter on all files 2020-03-19 09:46:20 -07:00
Andrew Eisenberg
13f725acfe chore: Update package lock 2020-03-19 09:44:14 -07:00
jcreedcmu
1401115c08 Merge pull request #298 from aeisenberg/aeisenberg/log-files
chore: Update changelog
2020-03-19 12:01:26 -04:00
Andrew Eisenberg
85c04fc63a chore: Update changelog 2020-03-19 08:38:22 -07:00
Andrew Eisenberg
54ad3649b1 Merge pull request #284 from aeisenberg/aeisenberg/log-files
feat: Save log files per query
2020-03-19 08:29:10 -07:00
Andrew Eisenberg
66e9272525 feat: Remove side log location when query removed
When removing query history item from view, also remove the side log.

Log files can be large, so ensure they don't stick around.

Last piece of #236 and #234.
2020-03-19 08:11:20 -07:00
Andrew Eisenberg
6793f8e92d feat: Adds command to show query log in editor
Right clicking on the history page will now have a new option to show
the associated log.

Closes #236
Closes #234
2020-03-19 08:11:20 -07:00
Andrew Eisenberg
da28beb82e fix: Should join paths using comma 2020-03-19 08:11:20 -07:00
Andrew Eisenberg
b04ff3c8b9 lint: Formatting 2020-03-19 08:11:20 -07:00
Andrew Eisenberg
fd4d6b7f30 fix: Avoid accidentally treating '' as valid 2020-03-19 08:11:20 -07:00
Andrew Eisenberg
5facab1f9e lint: Fix linting and update linting rules 2020-03-19 08:11:20 -07:00
Nick Rolfe
f25c9fd6fd Use codeql.exe instead of codeql.cmd on Windows 2020-03-19 08:11:20 -07:00
Andrew Eisenberg
a6043f2518 feat: Save log files per query
This feature adds logging per-query. Each query will be logged in its
own location in either workspace or globally shared location in
vscode.

There are limitations here. We are only guessing when one query ends
and another begins. We assume that queries don't occur in parallel.
If they do, the previous query will have its results intermingled
with the current query's results.

To fix that, we will need to update how the query-server emits log
messages so that each query message is attached to a tag that
specifies the query that emitted it.
2020-03-19 08:11:20 -07:00
Andrew Eisenberg
6a746ae5bd deps: Add new dependencies for testing
sinon-chai, and proxyquire.
2020-03-19 08:11:20 -07:00
Dave Bartolomeo
a9eb0a40fd Merge pull request #296 from aeisenberg/aeisenberg/fix-minimist
chore: Fix security warning in minimist
2020-03-19 09:32:18 -04:00
Andrew Eisenberg
d6be401d46 chore: Fix security warning in minimist
There is a security warning for minimist. The extension only depends
on it transitively. Not all of its direct dependencies have updated it
yet. I don't like having to add a dependency like this, but if it
avoids github screaming at us, then I think we should.
2020-03-18 11:53:48 -07:00
jcreedcmu
158a07cd89 Merge pull request #294 from jcreedcmu/jcreed/fix-quick-eval
Fix quick-eval in .qll error
2020-03-18 13:44:17 -04:00
Jason Reed
7ac5a8f777 Fix windows broken test 2020-03-18 13:29:41 -04:00
Jason Reed
dc09925149 Add test for quick-query in .qll fix. 2020-03-18 10:42:49 -04:00
Jason Reed
5fd2596537 Fix quick-query error in .qll files
Fixes https://github.com/github/vscode-codeql/issues/293
2020-03-18 10:41:57 -04:00
Jason Reed
22003e1375 Add dependencies for testing 2020-03-18 10:40:34 -04:00
jcreedcmu
2fee4cc368 Merge pull request #292 from github/jcreedcmu-patch-1
Expand release documentation
2020-03-17 16:55:29 -04:00
jcreedcmu
9d2504959b Update CONTRIBUTING.md 2020-03-17 16:52:18 -04:00
jcreedcmu
77b3f0a025 Merge pull request #291 from github/version/bump-to-v1.1.1
Bump version to v1.1.1
2020-03-17 16:50:16 -04:00
jcreedcmu
a096e79bd4 Expand release documentation 2020-03-17 16:45:24 -04:00
github-actions[bot]
dedc9c46ab Bump version to v1.1.1 2020-03-17 20:25:10 +00:00
63 changed files with 2532 additions and 903 deletions

View File

@@ -7,6 +7,6 @@ Replace this with a description of the changes your pull request makes.
## Checklist
- [ ] [CHANGELOG.md](../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/master/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.

View File

@@ -62,11 +62,17 @@ jobs:
npm run build-ci
shell: bash
- name: Lint
run: |
cd extensions/ql-vscode
npm run lint
- name: Install CodeQL
run: |
mkdir codeql-home
curl -L --silent https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql.zip -o codeql-home/codeql.zip
unzip -q -o codeql-home/codeql.zip -d codeql-home
unzip -q -o codeql-home/codeql.zip codeql/codeql.exe -d codeql-home
rm codeql-home/codeql.zip
shell: bash
@@ -80,7 +86,7 @@ jobs:
if: matrix.os == 'windows-latest'
run: |
cd extensions/ql-vscode
$env:CODEQL_PATH=$(Join-Path $env:GITHUB_WORKSPACE -ChildPath 'codeql-home/codeql/codeql.cmd')
$env:CODEQL_PATH=$(Join-Path $env:GITHUB_WORKSPACE -ChildPath 'codeql-home/codeql/codeql.exe')
npm run test
- name: Run integration tests (Linux)

3
.vscode/launch.json vendored
View File

@@ -8,7 +8,8 @@
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql"
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
"${workspaceRoot}/../vscode-codeql-starter/vscode-codeql-starter.code-workspace"
],
"stopOnEntry": false,
"sourceMaps": true,

View File

@@ -144,13 +144,23 @@ 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. Monitor the status of the release build in the `Release` workflow in the Actions tab.
1. Download the VSIX from the draft GitHub release that is created when the release build finishes.
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,
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. Publish the draft GitHub release and confirm the new release is marked as the latest release at <https://github.com/github/vscode-codeql/releases>.
1. Go to the draft GitHub release, 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.
## Resources

929
build/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,8 @@ dependencies:
'@rush-temp/semmle-vscode-utils': 'file:projects/semmle-vscode-utils.tgz'
'@rush-temp/typescript-config': 'file:projects/typescript-config.tgz'
'@rush-temp/vscode-codeql': 'file:projects/vscode-codeql.tgz'
'@types/chai': 4.2.10
'@types/chai': 4.2.11
'@types/chai-as-promised': 7.1.2
'@types/child-process-promise': 2.2.1
'@types/classnames': 2.2.10
'@types/fs-extra': 8.1.0
@@ -18,27 +19,32 @@ dependencies:
'@types/js-yaml': 3.12.2
'@types/jszip': 3.1.7
'@types/mocha': 5.2.7
'@types/node': 12.12.29
'@types/node': 12.12.30
'@types/node-fetch': 2.5.5
'@types/npm-packlist': 1.1.1
'@types/proxyquire': 1.3.28
'@types/react': 16.9.23
'@types/react-dom': 16.9.5
'@types/sarif': 2.1.2
'@types/sinon': 7.5.2
'@types/sinon-chai': 3.2.3
'@types/through2': 2.0.34
'@types/tmp': 0.1.0
'@types/unzipper': 0.10.2
'@types/vinyl': 2.0.4
'@types/vscode': 1.42.0
'@types/vscode': 1.43.0
'@types/webpack': 4.41.7
'@types/xml2js': 0.4.5
'@typescript-eslint/eslint-plugin': 2.23.0_2510d86781fe783b47b58303c18a0d9b
'@typescript-eslint/parser': 2.23.0_eslint@6.8.0+typescript@3.8.3
ansi-colors: 4.1.1
chai: 4.2.0
chai-as-promised: 7.1.1_chai@4.2.0
child-process-promise: 2.2.1
classnames: 2.2.6
css-loader: 3.1.0_webpack@4.42.0
eslint: 6.8.0
eslint-plugin-react: 7.19.0_eslint@6.8.0
fs-extra: 8.1.0
glob: 7.1.6
glob-promise: 3.4.0_glob@7.1.6
@@ -48,16 +54,19 @@ dependencies:
js-yaml: 3.13.1
jsonc-parser: 2.1.1
leb: 0.3.0
minimist: 1.2.5
mocha: 6.2.2
mocha-sinon: 2.1.0
node-fetch: 2.6.0
npm-packlist: 1.4.8
npm-run-all: 4.1.5
plugin-error: 1.0.1
proxyquire: 2.1.3
react: 16.13.0
react-dom: 16.13.0_react@16.13.0
reflect-metadata: 0.1.13
sinon: 9.0.0
sinon: 9.0.1
sinon-chai: 3.5.0_chai@4.2.0+sinon@9.0.1
style-loader: 0.23.1
through2: 3.0.1
tmp: 0.1.0
@@ -93,6 +102,13 @@ packages:
dev: false
resolution:
integrity: sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==
/@babel/runtime-corejs3/7.9.2:
dependencies:
core-js-pure: 3.6.4
regenerator-runtime: 0.13.5
dev: false
resolution:
integrity: sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA==
/@gulp-sourcemaps/identity-map/1.0.2:
dependencies:
acorn: 5.7.4
@@ -322,13 +338,19 @@ packages:
dev: false
resolution:
integrity: sha512-VQgHxyPMTj3hIlq9SY1mctqx+Jj8kpQfoLvDlVSDNOyuYs8JYfkuY3OW/4+dO657yPmNhHpePRx0/Tje5ImNVQ==
/@types/chai/4.2.10:
/@types/chai-as-promised/7.1.2:
dependencies:
'@types/chai': 4.2.11
dev: false
resolution:
integrity: sha512-TlWWgb21+0LdkuFqEqfmy7NEgfB/7Jjux15fWQAh3P93gbmXuwTM/vxEdzW89APIcI2BgKR48yjeAkdeH+4qvQ==
integrity: sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q==
/@types/chai/4.2.11:
dev: false
resolution:
integrity: sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==
/@types/child-process-promise/2.2.1:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-xZ4kkF82YkmqPCERqV9Tj0bVQj3Tk36BqGlNgxv5XhifgDRhwAqp+of+sccksdpZRbbPsNwMOkmUqOnLgxKtGw==
@@ -354,20 +376,20 @@ packages:
integrity: sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==
/@types/fs-extra/5.0.4:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g==
/@types/fs-extra/8.1.0:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-UoOfVEzAUpeSPmjm7h1uk5MH6KZma2z2O7a75onTGjnNvAvMVrPzPL/vBbT65iIGHWj6rokwfmYcmxmlSf2uwg==
/@types/glob-stream/6.1.0:
dependencies:
'@types/glob': 7.1.1
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-RHv6ZQjcTncXo3thYZrsbAVwoy4vSKosSWhuhuQxLOTv74OJuFQxXkmUuZCr3q9uNBEVCvIzmZL/FeRNbHZGUg==
@@ -375,7 +397,7 @@ packages:
dependencies:
'@types/events': 3.0.0
'@types/minimatch': 3.0.3
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
@@ -405,7 +427,7 @@ packages:
integrity: sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
/@types/jszip/3.1.7:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-+XQKNI5zpxutK05hO67huUTw/2imXCuJWjnFdU63tRES/xXSX1yVR9cv/QAdO6Rii2y2tTHbzjQ4i2apLfuK0Q==
@@ -419,7 +441,7 @@ packages:
integrity: sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
/@types/node-fetch/2.5.5:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
form-data: 3.0.0
dev: false
resolution:
@@ -428,10 +450,10 @@ packages:
dev: false
resolution:
integrity: sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==
/@types/node/12.12.29:
/@types/node/12.12.30:
dev: false
resolution:
integrity: sha512-yo8Qz0ygADGFptISDj3pOC9wXfln/5pQaN/ysDIzOaAWXt73cNHmtEC8zSO2Y+kse/txmwIAJzkYZ5fooaS5DQ==
integrity: sha512-sz9MF/zk6qVr3pAnM0BSQvYIBK44tS75QC5N+VbWSE4DjCV/pJ+UzCW/F+vVnl7TkOPcuwQureKNtSSwjBTaMg==
/@types/node/8.5.8:
dev: false
resolution:
@@ -444,6 +466,10 @@ packages:
dev: false
resolution:
integrity: sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
/@types/proxyquire/1.3.28:
dev: false
resolution:
integrity: sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==
/@types/react-dom/16.9.5:
dependencies:
'@types/react': 16.9.23
@@ -461,6 +487,17 @@ packages:
dev: false
resolution:
integrity: sha512-TELZl5h48KaB6SFZqTuaMEw1hrGuusbBcH+yfMaaHdS2pwDr3RTH4CVN0LyY1kqSiDm9PPvAMx8FJ0LUZreOCQ==
/@types/sinon-chai/3.2.3:
dependencies:
'@types/chai': 4.2.11
'@types/sinon': 7.5.2
dev: false
resolution:
integrity: sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ==
/@types/sinon/7.5.2:
dev: false
resolution:
integrity: sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==
/@types/source-list-map/0.1.2:
dev: false
resolution:
@@ -471,7 +508,7 @@ packages:
integrity: sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ==
/@types/through2/2.0.34:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-nhRG8+RuG/L+0fAZBQYaRflXKjTrHOKH8MFTChnf+dNVMxA3wHYYrfj0tztK0W51ABXjGfRCDc0vRkecCOrsow==
@@ -497,14 +534,14 @@ packages:
integrity: sha512-j4iepCSuY2JGW/hShVtUBagic0klYNFIXP7VweavnYnNC2EjiKxJFeaS9uaJmAT0ty9sQSqTS1aagWMZMV0HyA==
/@types/unzipper/0.10.2:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-VgYoNEyj8xkz9I+RTWD00iB9JVViK/RBteNDjOIV3/kdCUPaskka7xAZfFlIxRwKGSPf77F8yje5bJt2PefofQ==
/@types/vinyl-fs/2.4.11:
dependencies:
'@types/glob-stream': 6.1.0
'@types/node': 12.12.29
'@types/node': 12.12.30
'@types/vinyl': 2.0.4
dev: false
resolution:
@@ -512,17 +549,17 @@ packages:
/@types/vinyl/2.0.4:
dependencies:
'@types/expect': 1.20.4
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-2o6a2ixaVI2EbwBPg1QYLGQoHK56p/8X/sGfKbFC8N6sY9lfjsMf/GprtkQkSya0D4uRiutRZ2BWj7k3JvLsAQ==
/@types/vscode/1.42.0:
/@types/vscode/1.43.0:
dev: false
resolution:
integrity: sha512-ds6TceMsh77Fs0Mq0Vap6Y72JbGWB8Bay4DrnJlf5d9ui2RSe1wis13oQm+XhguOeH1HUfLGzaDAoupTUtgabw==
integrity: sha512-kIaR9qzd80rJOxePKpCB/mdy00mz8Apt2QA5Y6rdrKFn13QNFNeP3Hzmsf37Bwh/3cS7QjtAeGSK7wSqAU0sYQ==
/@types/webpack-sources/0.1.6:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
'@types/source-list-map': 0.1.2
source-map: 0.6.1
dev: false
@@ -531,7 +568,7 @@ packages:
/@types/webpack/4.41.7:
dependencies:
'@types/anymatch': 1.3.1
'@types/node': 12.12.29
'@types/node': 12.12.30
'@types/tapable': 1.0.5
'@types/uglify-js': 3.0.4
'@types/webpack-sources': 0.1.6
@@ -541,7 +578,7 @@ packages:
integrity: sha512-OQG9viYwO0V1NaNV7d0n79V+n6mjOV30CwgFPIfTzwmk8DHbt+C4f2aBGdCYbo3yFyYD6sjXfqqOjwkl1j+ulA==
/@types/xml2js/0.4.5:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-yohU3zMn0fkhlape1nxXG2bLEGZRc1FeqF80RoHaYXJN7uibaauXfhzhOJr1Xh36sn+/tx21QAOf07b/xYVk1w==
@@ -1007,6 +1044,16 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha1-p5SvDAWrF1KEbudTofIRoFugxE8=
/array-includes/3.1.1:
dependencies:
define-properties: 1.1.3
es-abstract: 1.17.4
is-string: 1.0.5
dev: false
engines:
node: '>= 0.4'
resolution:
integrity: sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==
/array-initial/1.1.0:
dependencies:
array-slice: 1.1.0
@@ -1363,7 +1410,7 @@ packages:
infer-owner: 1.0.4
lru-cache: 5.1.1
mississippi: 3.0.0
mkdirp: 0.5.1
mkdirp: 0.5.3
move-concurrently: 1.0.1
promise-inflight: 1.0.1
rimraf: 2.7.1
@@ -1407,6 +1454,15 @@ packages:
node: '>=6'
resolution:
integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
/chai-as-promised/7.1.1_chai@4.2.0:
dependencies:
chai: 4.2.0
check-error: 1.0.2
dev: false
peerDependencies:
chai: '>= 2.1.2 < 5'
resolution:
integrity: sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==
/chai/4.2.0:
dependencies:
assertion-error: 1.1.0
@@ -1716,7 +1772,7 @@ packages:
aproba: 1.2.0
fs-write-stream-atomic: 1.0.10
iferr: 0.1.5
mkdirp: 0.5.1
mkdirp: 0.5.3
rimraf: 2.7.1
run-queue: 1.0.3
dev: false
@@ -1735,6 +1791,11 @@ packages:
dev: false
resolution:
integrity: sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==
/core-js-pure/3.6.4:
dev: false
requiresBuild: true
resolution:
integrity: sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==
/core-util-is/1.0.2:
dev: false
resolution:
@@ -1815,7 +1876,7 @@ packages:
postcss-modules-scope: 2.1.1
postcss-modules-values: 3.0.0
postcss-value-parser: 4.0.3
schema-utils: 2.6.4
schema-utils: 2.6.5
webpack: 4.42.0_webpack@4.42.0
dev: false
engines:
@@ -1896,7 +1957,7 @@ packages:
integrity: sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
/debug/4.1.1:
dependencies:
ms: 2.1.1
ms: 2.1.2
dev: false
resolution:
integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -2041,6 +2102,14 @@ packages:
dev: false
resolution:
integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
/doctrine/2.1.0:
dependencies:
esutils: 2.0.3
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
/doctrine/3.0.0:
dependencies:
esutils: 2.0.3
@@ -2285,6 +2354,28 @@ packages:
node: '>=0.8.0'
resolution:
integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
/eslint-plugin-react/7.19.0_eslint@6.8.0:
dependencies:
array-includes: 3.1.1
doctrine: 2.1.0
eslint: 6.8.0
has: 1.0.3
jsx-ast-utils: 2.2.3
object.entries: 1.1.1
object.fromentries: 2.0.2
object.values: 1.1.1
prop-types: 15.7.2
resolve: 1.15.1
semver: 6.3.0
string.prototype.matchall: 4.0.2
xregexp: 4.3.0
dev: false
engines:
node: '>=4'
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
resolution:
integrity: sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==
/eslint-scope/4.0.3:
dependencies:
esrecurse: 4.2.1
@@ -2345,7 +2436,7 @@ packages:
levn: 0.3.0
lodash: 4.17.15
minimatch: 3.0.4
mkdirp: 0.5.1
mkdirp: 0.5.3
natural-compare: 1.4.0
optionator: 0.8.3
progress: 2.0.3
@@ -2355,7 +2446,7 @@ packages:
strip-json-comments: 3.0.1
table: 5.4.6
text-table: 0.2.0
v8-compile-cache: 2.0.3
v8-compile-cache: 2.1.0
dev: false
engines:
node: ^8.10.0 || ^10.13.0 || >=11.10.1
@@ -2577,6 +2668,15 @@ packages:
optional: true
resolution:
integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
/fill-keys/1.0.2:
dependencies:
is-object: 1.0.1
merge-descriptors: 1.0.1
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=
/fill-range/4.0.0:
dependencies:
extend-shallow: 2.0.1
@@ -2789,7 +2889,7 @@ packages:
dependencies:
graceful-fs: 4.2.3
inherits: 2.0.4
mkdirp: 0.5.1
mkdirp: 0.5.3
rimraf: 2.7.1
dev: false
engines:
@@ -3350,6 +3450,16 @@ packages:
node: '>=6.0.0'
resolution:
integrity: sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==
/internal-slot/1.0.2:
dependencies:
es-abstract: 1.17.4
has: 1.0.3
side-channel: 1.0.2
dev: false
engines:
node: '>= 0.4'
resolution:
integrity: sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==
/interpret/1.2.0:
dev: false
engines:
@@ -3539,6 +3649,10 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
/is-object/1.0.1:
dev: false
resolution:
integrity: sha1-iVJojF7C/9awPsyF52ngKQMINHA=
/is-plain-obj/2.1.0:
dev: false
engines:
@@ -3579,6 +3693,12 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
/is-string/1.0.5:
dev: false
engines:
node: '>= 0.4'
resolution:
integrity: sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
/is-subdir/1.1.1:
dependencies:
better-path-resolve: 1.0.0
@@ -3681,20 +3801,29 @@ packages:
integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
/json5/1.0.1:
dependencies:
minimist: 1.2.0
minimist: 1.2.5
dev: false
hasBin: true
resolution:
integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
/json5/2.1.1:
dependencies:
minimist: 1.2.0
minimist: 1.2.5
dev: false
engines:
node: '>=6'
hasBin: true
resolution:
integrity: sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==
/json5/2.1.2:
dependencies:
minimist: 1.2.5
dev: false
engines:
node: '>=6'
hasBin: true
resolution:
integrity: sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==
/jsonc-parser/2.1.1:
dev: false
resolution:
@@ -3705,6 +3834,15 @@ packages:
graceful-fs: 4.2.3
resolution:
integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
/jsx-ast-utils/2.2.3:
dependencies:
array-includes: 3.1.1
object.assign: 4.1.0
dev: false
engines:
node: '>=4.0'
resolution:
integrity: sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==
/just-debounce/1.0.0:
dev: false
resolution:
@@ -4062,6 +4200,10 @@ packages:
node: '>= 0.10.0'
resolution:
integrity: sha1-htcJCzDORV1j+64S3aUaR93K+bI=
/merge-descriptors/1.0.1:
dev: false
resolution:
integrity: sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
/micromatch/3.1.10:
dependencies:
arr-diff: 4.0.0
@@ -4141,10 +4283,10 @@ packages:
dev: false
resolution:
integrity: sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
/minimist/1.2.0:
/minimist/1.2.5:
dev: false
resolution:
integrity: sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
/minipass/2.9.0:
dependencies:
safe-buffer: 5.2.0
@@ -4187,10 +4329,19 @@ packages:
/mkdirp/0.5.1:
dependencies:
minimist: 0.0.8
deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
dev: false
hasBin: true
resolution:
integrity: sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
/mkdirp/0.5.3:
dependencies:
minimist: 1.2.5
deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
dev: false
hasBin: true
resolution:
integrity: sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==
/mocha-sinon/2.1.0:
dev: false
engines:
@@ -4228,12 +4379,16 @@ packages:
hasBin: true
resolution:
integrity: sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==
/module-not-found-error/1.0.1:
dev: false
resolution:
integrity: sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=
/move-concurrently/1.0.1:
dependencies:
aproba: 1.2.0
copy-concurrently: 1.0.5
fs-write-stream-atomic: 1.0.10
mkdirp: 0.5.1
mkdirp: 0.5.3
rimraf: 2.7.1
run-queue: 1.0.3
dev: false
@@ -4247,6 +4402,10 @@ packages:
dev: false
resolution:
integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
/ms/2.1.2:
dev: false
resolution:
integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
/mute-stdout/1.0.1:
dev: false
engines:
@@ -4526,6 +4685,28 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=
/object.entries/1.1.1:
dependencies:
define-properties: 1.1.3
es-abstract: 1.17.4
function-bind: 1.1.1
has: 1.0.3
dev: false
engines:
node: '>= 0.4'
resolution:
integrity: sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==
/object.fromentries/2.0.2:
dependencies:
define-properties: 1.1.3
es-abstract: 1.17.4
function-bind: 1.1.1
has: 1.0.3
dev: false
engines:
node: '>= 0.4'
resolution:
integrity: sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==
/object.getownpropertydescriptors/2.1.0:
dependencies:
define-properties: 1.1.3
@@ -4561,6 +4742,17 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=
/object.values/1.1.1:
dependencies:
define-properties: 1.1.3
es-abstract: 1.17.4
function-bind: 1.1.1
has: 1.0.3
dev: false
engines:
node: '>= 0.4'
resolution:
integrity: sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==
/once/1.4.0:
dependencies:
wrappy: 1.0.2
@@ -4805,7 +4997,7 @@ packages:
integrity: sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg=
/parse5/3.0.3:
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
dev: false
resolution:
integrity: sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==
@@ -5078,6 +5270,14 @@ packages:
dev: false
resolution:
integrity: sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
/proxyquire/2.1.3:
dependencies:
fill-keys: 1.0.2
module-not-found-error: 1.0.1
resolve: 1.15.1
dev: false
resolution:
integrity: sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==
/prr/1.0.1:
dev: false
resolution:
@@ -5310,6 +5510,10 @@ packages:
dev: false
resolution:
integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
/regenerator-runtime/0.13.5:
dev: false
resolution:
integrity: sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
/regex-not/1.0.2:
dependencies:
extend-shallow: 3.0.2
@@ -5319,6 +5523,15 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==
/regexp.prototype.flags/1.3.0:
dependencies:
define-properties: 1.1.3
es-abstract: 1.17.4
dev: false
engines:
node: '>= 0.4'
resolution:
integrity: sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
/regexpp/2.0.1:
dev: false
engines:
@@ -5545,7 +5758,7 @@ packages:
node: '>= 4'
resolution:
integrity: sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
/schema-utils/2.6.4:
/schema-utils/2.6.5:
dependencies:
ajv: 6.12.0
ajv-keywords: 3.4.1_ajv@6.12.0
@@ -5553,7 +5766,7 @@ packages:
engines:
node: '>= 8.9.0'
resolution:
integrity: sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==
integrity: sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==
/semver-greatest-satisfied-range/1.1.0:
dependencies:
sver-compat: 1.5.0
@@ -5626,6 +5839,13 @@ packages:
dev: false
resolution:
integrity: sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
/side-channel/1.0.2:
dependencies:
es-abstract: 1.17.4
object-inspect: 1.7.0
dev: false
resolution:
integrity: sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==
/sigmund/1.0.1:
dev: false
resolution:
@@ -5634,7 +5854,17 @@ packages:
dev: false
resolution:
integrity: sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
/sinon/9.0.0:
/sinon-chai/3.5.0_chai@4.2.0+sinon@9.0.1:
dependencies:
chai: 4.2.0
sinon: 9.0.1
dev: false
peerDependencies:
chai: ^4.0.0
sinon: '>=4.0.0 <10.0.0'
resolution:
integrity: sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==
/sinon/9.0.1:
dependencies:
'@sinonjs/commons': 1.7.1
'@sinonjs/fake-timers': 6.0.0
@@ -5645,7 +5875,7 @@ packages:
supports-color: 7.1.0
dev: false
resolution:
integrity: sha512-c4bREcvuK5VuEGyMW/Oim9I3Rq49Vzb0aMdxouFaA44QCFpilc5LJOugrX+mkrvikbqCimxuK+4cnHVNnLR41g==
integrity: sha512-iTTyiQo5T94jrOx7X7QLBZyucUJ2WvL9J13+96HMfm2CGoJYbIPqRfl6wgNcqmzk0DI28jeGx5bUTXizkrqBmg==
/slice-ansi/2.1.0:
dependencies:
ansi-styles: 3.2.1
@@ -5884,6 +6114,17 @@ packages:
node: '>=8'
resolution:
integrity: sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
/string.prototype.matchall/4.0.2:
dependencies:
define-properties: 1.1.3
es-abstract: 1.17.4
has-symbols: 1.0.1
internal-slot: 1.0.2
regexp.prototype.flags: 1.3.0
side-channel: 1.0.2
dev: false
resolution:
integrity: sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==
/string.prototype.padend/3.1.0:
dependencies:
define-properties: 1.1.3
@@ -6070,7 +6311,7 @@ packages:
fs-minipass: 1.2.7
minipass: 2.9.0
minizlib: 1.3.3
mkdirp: 0.5.1
mkdirp: 0.5.3
safe-buffer: 5.2.0
yallist: 3.1.1
dev: false
@@ -6086,7 +6327,7 @@ packages:
schema-utils: 1.0.0
serialize-javascript: 2.1.2
source-map: 0.6.1
terser: 4.6.6
terser: 4.6.7
webpack: 4.42.0_webpack@4.42.0
webpack-sources: 1.4.3
worker-farm: 1.7.0
@@ -6097,7 +6338,7 @@ packages:
webpack: ^4.0.0
resolution:
integrity: sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==
/terser/4.6.6:
/terser/4.6.7:
dependencies:
commander: 2.20.3
source-map: 0.6.1
@@ -6107,7 +6348,7 @@ packages:
node: '>=6.0.0'
hasBin: true
resolution:
integrity: sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g==
integrity: sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==
/text-table/0.2.0:
dev: false
resolution:
@@ -6540,6 +6781,10 @@ packages:
dev: false
resolution:
integrity: sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
/v8-compile-cache/2.1.0:
dev: false
resolution:
integrity: sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
/v8flags/3.1.3:
dependencies:
homedir-polyfill: 1.0.3
@@ -6761,7 +7006,7 @@ packages:
loader-utils: 1.4.0
memory-fs: 0.4.1
micromatch: 3.1.10
mkdirp: 0.5.1
mkdirp: 0.5.3
neo-async: 2.6.1
node-libs-browser: 2.2.1
schema-utils: 1.0.0
@@ -6860,7 +7105,7 @@ packages:
/write-json5-file/2.1.2:
dependencies:
graceful-fs: 4.2.3
json5: 2.1.1
json5: 2.1.2
make-dir: 3.0.2
sort-keys: 4.0.0
write-file-atomic: 2.4.3
@@ -6883,12 +7128,18 @@ packages:
integrity: sha512-OHzbrlgjw/K/BAH6LdEOcSQFz5nkk0I/25CjKLIVFvcg2Ej7+QE/GTnitgqWnhlsdghor7OV5gfttQPGogQ1XA==
/write/1.0.3:
dependencies:
mkdirp: 0.5.1
mkdirp: 0.5.3
dev: false
engines:
node: '>=4'
resolution:
integrity: sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
/xregexp/4.3.0:
dependencies:
'@babel/runtime-corejs3': 7.9.2
dev: false
resolution:
integrity: sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==
/xtend/4.0.2:
dev: false
engines:
@@ -6918,6 +7169,13 @@ packages:
dev: false
resolution:
integrity: sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
/yargs-parser/13.1.2:
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
dev: false
resolution:
integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
/yargs-parser/5.0.0:
dependencies:
camelcase: 3.0.0
@@ -6946,7 +7204,7 @@ packages:
string-width: 3.1.0
which-module: 2.0.0
y18n: 4.0.0
yargs-parser: 13.1.1
yargs-parser: 13.1.2
dev: false
resolution:
integrity: sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==
@@ -7021,7 +7279,7 @@ packages:
'@types/fs-extra': 8.1.0
'@types/gulp': 4.0.6
'@types/js-yaml': 3.12.2
'@types/node': 12.12.29
'@types/node': 12.12.30
'@types/npm-packlist': 1.1.1
'@types/through2': 2.0.34
'@types/vinyl': 2.0.4
@@ -7051,7 +7309,7 @@ packages:
version: 0.0.0
'file:projects/semmle-bqrs.tgz_typescript@3.8.3':
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
leb: 0.3.0
reflect-metadata: 0.1.13
typescript-formatter: 7.2.2_typescript@3.8.3
@@ -7067,7 +7325,7 @@ packages:
'file:projects/semmle-io-node.tgz_typescript@3.8.3':
dependencies:
'@types/fs-extra': 8.1.0
'@types/node': 12.12.29
'@types/node': 12.12.30
fs-extra: 8.1.0
typescript-formatter: 7.2.2_typescript@3.8.3
dev: false
@@ -7081,7 +7339,7 @@ packages:
version: 0.0.0
'file:projects/semmle-io.tgz_typescript@3.8.3':
dependencies:
'@types/node': 12.12.29
'@types/node': 12.12.30
leb: 0.3.0
typescript-formatter: 7.2.2_typescript@3.8.3
dev: false
@@ -7095,8 +7353,8 @@ packages:
version: 0.0.0
'file:projects/semmle-vscode-utils.tgz':
dependencies:
'@types/node': 12.12.29
'@types/vscode': 1.42.0
'@types/node': 12.12.30
'@types/vscode': 1.43.0
typescript: 3.8.3
typescript-formatter: 7.2.2_typescript@3.8.3
dev: false
@@ -7114,7 +7372,8 @@ packages:
version: 0.0.0
'file:projects/vscode-codeql.tgz':
dependencies:
'@types/chai': 4.2.10
'@types/chai': 4.2.11
'@types/chai-as-promised': 7.1.2
'@types/child-process-promise': 2.2.1
'@types/classnames': 2.2.10
'@types/fs-extra': 8.1.0
@@ -7124,23 +7383,28 @@ packages:
'@types/js-yaml': 3.12.2
'@types/jszip': 3.1.7
'@types/mocha': 5.2.7
'@types/node': 12.12.29
'@types/node': 12.12.30
'@types/node-fetch': 2.5.5
'@types/proxyquire': 1.3.28
'@types/react': 16.9.23
'@types/react-dom': 16.9.5
'@types/sarif': 2.1.2
'@types/sinon': 7.5.2
'@types/sinon-chai': 3.2.3
'@types/tmp': 0.1.0
'@types/unzipper': 0.10.2
'@types/vscode': 1.42.0
'@types/vscode': 1.43.0
'@types/webpack': 4.41.7
'@types/xml2js': 0.4.5
'@typescript-eslint/eslint-plugin': 2.23.0_2510d86781fe783b47b58303c18a0d9b
'@typescript-eslint/parser': 2.23.0_eslint@6.8.0+typescript@3.8.3
chai: 4.2.0
chai-as-promised: 7.1.1_chai@4.2.0
child-process-promise: 2.2.1
classnames: 2.2.6
css-loader: 3.1.0_webpack@4.42.0
eslint: 6.8.0
eslint-plugin-react: 7.19.0_eslint@6.8.0
fs-extra: 8.1.0
glob: 7.1.6
glob-promise: 3.4.0_glob@7.1.6
@@ -7148,13 +7412,16 @@ packages:
gulp-sourcemaps: 2.6.5
gulp-typescript: 5.0.1_typescript@3.8.3
js-yaml: 3.13.1
minimist: 1.2.5
mocha: 6.2.2
mocha-sinon: 2.1.0
node-fetch: 2.6.0
npm-run-all: 4.1.5
proxyquire: 2.1.3
react: 16.13.0
react-dom: 16.13.0_react@16.13.0
sinon: 9.0.0
sinon: 9.0.1
sinon-chai: 3.5.0_chai@4.2.0+sinon@9.0.1
style-loader: 0.23.1
through2: 3.0.1
tmp: 0.1.0
@@ -7176,7 +7443,7 @@ packages:
dev: false
name: '@rush-temp/vscode-codeql'
resolution:
integrity: sha512-S/tOZMV3yl8jqavEC2X5ly4pwsb8hcACsAfEggZ5x/l7HDplczVae+Xtc49JFeMueYF00KZN1dSM0cADeftzew==
integrity: sha512-R6CO9tHF5naHATAvu28jP69lPvY3WvU2zeF41QZMU5OIieppNnoWEAlLjcRLScZxCtA70356Jzk90hK/LHvMpA==
tarball: 'file:projects/vscode-codeql.tgz'
version: 0.0.0
registry: ''
@@ -7191,6 +7458,7 @@ specifiers:
'@rush-temp/typescript-config': 'file:./projects/typescript-config.tgz'
'@rush-temp/vscode-codeql': 'file:./projects/vscode-codeql.tgz'
'@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
@@ -7203,9 +7471,12 @@ specifiers:
'@types/node': ^12.0.8
'@types/node-fetch': ~2.5.2
'@types/npm-packlist': ~1.1.1
'@types/proxyquire': ~1.3.28
'@types/react': ^16.8.17
'@types/react-dom': ^16.8.4
'@types/sarif': ~2.1.2
'@types/sinon': ~7.5.2
'@types/sinon-chai': ~3.2.3
'@types/through2': ~2.0.34
'@types/tmp': ^0.1.0
'@types/unzipper': ~0.10.1
@@ -7217,10 +7488,12 @@ specifiers:
'@typescript-eslint/parser': ~2.23.0
ansi-colors: ^4.0.1
chai: ^4.2.0
chai-as-promised: ~7.1.1
child-process-promise: ^2.2.1
classnames: ~2.2.6
css-loader: ~3.1.0
eslint: ~6.8.0
eslint-plugin-react: ~7.19.0
fs-extra: ^8.1.0
glob: ^7.1.4
glob-promise: ^3.4.0
@@ -7230,16 +7503,19 @@ specifiers:
js-yaml: ^3.12.0
jsonc-parser: ~2.1.0
leb: ^0.3.0
minimist: ~1.2.5
mocha: ~6.2.1
mocha-sinon: ~2.1.0
node-fetch: ~2.6.0
npm-packlist: ~1.4.4
npm-run-all: ^4.1.5
plugin-error: ^1.0.1
proxyquire: ~2.1.3
react: ^16.8.6
react-dom: ^16.8.6
reflect-metadata: ~0.1.13
sinon: ~9.0.0
sinon-chai: ~3.5.0
style-loader: ~0.23.1
through2: ^3.0.1
tmp: ^0.1.0

View File

@@ -27,4 +27,4 @@
"../../test",
"../../**/view"
]
}
}

View File

@@ -3,9 +3,7 @@ module.exports = {
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
modules: true,
},
project: ['tsconfig.json', './src/**/tsconfig.json'],
},
plugins: ['@typescript-eslint'],
env: {
@@ -32,6 +30,7 @@ module.exports = {
"SwitchCase": 1,
"FunctionDeclaration": { "body": 1, "parameters": 1 }
}],
"@typescript-eslint/no-throw-literal": "error"
"@typescript-eslint/no-throw-literal": "error",
"no-useless-escape": 0
},
};

View File

@@ -1,5 +1,23 @@
# CodeQL for Visual Studio Code: Changelog
## 1.1.2
- Implement syntax highlighting for the new `unique` aggregate.
- Implement XML syntax highlighting for `.qhelp` files.
- Add option to auto save queries before running them.
- Add new command in query history to view the query text of the
selected query (note that this may be different from the current
contents of the query file if the file has been edited).
- Add ability to sort CodeQL databases by name or by date added.
## 1.1.1 - 23 March 2020
- Fix quick evaluation in `.qll` files.
- Add new command in query history view to view the log file of a
query.
- Request user acknowledgment before updating the CodeQL binaries.
- Warn when using the deprecated `codeql.cmd` launcher on Windows.
## 1.1.0 - 17 March 2020
- Add functionality for testing custom CodeQL queries by using the VS
@@ -52,7 +70,6 @@ hour for unauthenticated IPs.
- Fix the automatic upgrading of CodeQL databases when using upgrade scripts from the workspace.
- Allow removal of items from the CodeQL Query History view.
## 1.0.0 - 14 November 2019
Initial release of CodeQL for Visual Studio Code.

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,17 @@
<?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"/>
<polygon points="301.44,304.32 427.947,120.853 427.947,93.973 250.88,93.973 250.88,128.107 376.32,128.107 250.027,310.72
250.027,338.24 432,338.24 432,304.32" fill="#C5C5C5"/>
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227" fill="#C5C5C5"/>
<path d="M226.773,338.24L130.987,93.76H96L0,338.24h39.253l19.627-52.267h109.013l19.627,52.267H226.773z M71.893,250.987
L113.28,140.48l41.387,110.507H71.893z" fill="#C5C5C5"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 953 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="M7 2L6 3V6H7V3H14V5.45306L14.2071 5.29286L15 6.08576V3L14 2H7ZM8 4H10V6H8V4ZM5 9H3V11H5V9ZM2 7L1 8V13L2 14H9L10 13V8L9 7H2ZM2 13V8H9V13H2ZM8 10H6V12H8V10ZM13 4H12V7.86388L10.818 6.68192L10.1109 7.38903L12.1465 9.42454L12.8536 9.42454L14.889 7.38908L14.1819 6.68197L13 7.86388V4Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 449 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,17 @@
<?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 "/>
<polygon points="301.44,304.32 427.947,120.853 427.947,93.973 250.88,93.973 250.88,128.107 376.32,128.107 250.027,310.72
250.027,338.24 432,338.24 432,304.32 "/>
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227 "/>
<path d="M226.773,338.24L130.987,93.76H96L0,338.24h39.253l19.627-52.267h109.013l19.627,52.267H226.773z M71.893,250.987
L113.28,140.48l41.387,110.507H71.893z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 894 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="M7 2L6 3V6H7V3H14V5.45306L14.2071 5.29286L15 6.08576V3L14 2H7ZM8 4H10V6H8V4ZM5 9H3V11H5V9ZM2 7L1 8V13L2 14H9L10 13V8L9 7H2ZM2 13V8H9V13H2ZM8 10H6V12H8V10ZM13 4H12V7.86388L10.818 6.68192L10.1109 7.38903L12.1465 9.42454L12.8536 9.42454L14.889 7.38908L14.1819 6.68197L13 7.86388V4Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 449 B

View File

@@ -4,7 +4,7 @@
"description": "CodeQL for Visual Studio Code",
"author": "GitHub",
"private": true,
"version": "1.1.0",
"version": "1.1.2",
"publisher": "GitHub",
"license": "MIT",
"icon": "media/VS-marketplace-CodeQL-icon.png",
@@ -77,6 +77,15 @@
".dbscheme"
],
"configuration": "./language-configuration.json"
},
{
"id": "xml",
"aliases": [
"qhelp"
],
"extensions": [
".qhelp"
]
}
],
"grammars": [
@@ -99,7 +108,7 @@
"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.cmd` 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. This overrides all other CodeQL CLI settings."
},
"codeQL.runningQueries.numberOfThreads": {
"type": "integer",
@@ -132,6 +141,11 @@
"default": false,
"description": "Enable debug logging and tuple counting when running CodeQL queries. This information is useful for debugging query performance."
},
"codeQL.runningQueries.autoSave": {
"type": "boolean",
"default": false,
"description": "Enable automatically saving a modified query file when running a query."
},
"codeQL.queryHistory.format": {
"type": "string",
"default": "[%t] %q on %d - %s",
@@ -164,8 +178,8 @@
"command": "codeQL.chooseDatabase",
"title": "CodeQL: Choose Database",
"icon": {
"light": "media/black-plus.svg",
"dark": "media/white-plus.svg"
"light": "media/light/plus.svg",
"dark": "media/dark/plus.svg"
}
},
{
@@ -192,6 +206,22 @@
"command": "codeQLDatabases.upgradeDatabase",
"title": "Upgrade Database"
},
{
"command": "codeQLDatabases.sortByName",
"title": "Sort by Name",
"icon": {
"light": "media/light/sort-alpha.svg",
"dark": "media/dark/sort-alpha.svg"
}
},
{
"command": "codeQLDatabases.sortByDateAdded",
"title": "Sort by Date Added",
"icon": {
"light": "media/light/sort-date.svg",
"dark": "media/dark/sort-date.svg"
}
},
{
"command": "codeQL.checkForUpdatesToCLI",
"title": "CodeQL: Check for CLI Updates"
@@ -208,6 +238,14 @@
"command": "codeQLQueryHistory.itemClicked",
"title": "Query History Item"
},
{
"command": "codeQLQueryHistory.showQueryLog",
"title": "Show Query Log"
},
{
"command": "codeQLQueryHistory.showQueryText",
"title": "Show Query Text"
},
{
"command": "codeQLQueryResults.nextPathStep",
"title": "CodeQL: Show Next Step on Path"
@@ -235,6 +273,16 @@
],
"menus": {
"view/title": [
{
"command": "codeQLDatabases.sortByName",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQLDatabases.sortByDateAdded",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQL.chooseDatabase",
"when": "view == codeQLDatabases",
@@ -272,6 +320,16 @@
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.showQueryLog",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLQueryHistory.showQueryText",
"group": "9_qlCommands",
"when": "view == codeQLQueryHistory"
},
{
"command": "codeQLTests.showOutputDifferences",
"group": "qltest@1",
@@ -312,6 +370,14 @@
"command": "codeQLDatabases.setCurrentDatabase",
"when": "false"
},
{
"command": "codeQLDatabases.sortByName",
"when": "false"
},
{
"command": "codeQLDatabases.sortByDateAdded",
"when": "false"
},
{
"command": "codeQLDatabases.removeDatabase",
"when": "false"
@@ -328,6 +394,14 @@
"command": "codeQLQueryHistory.itemClicked",
"when": "false"
},
{
"command": "codeQLQueryHistory.showQueryLog",
"when": "false"
},
{
"command": "codeQLQueryHistory.showQueryText",
"when": "false"
},
{
"command": "codeQLQueryHistory.setLabel",
"when": "false"
@@ -376,7 +450,7 @@
"update-vscode": "node ./node_modules/vscode/bin/install",
"postinstall": "node ./node_modules/vscode/bin/install",
"format": "tsfmt -r",
"lint": "eslint . --ext .ts,.tsx"
"lint": "eslint src test --ext .ts,.tsx"
},
"dependencies": {
"child-process-promise": "^2.2.1",
@@ -396,7 +470,8 @@
"vscode-jsonrpc": "^4.0.0",
"vscode-languageclient": "^5.2.1",
"vscode-test-adapter-api": "~1.7.0",
"vscode-test-adapter-util": "~0.7.0"
"vscode-test-adapter-util": "~0.7.0",
"minimist": "~1.2.5"
},
"devDependencies": {
"@types/chai": "^4.1.7",
@@ -444,6 +519,14 @@
"webpack-cli": "^3.3.2",
"eslint": "~6.8.0",
"@typescript-eslint/eslint-plugin": "~2.23.0",
"@typescript-eslint/parser": "~2.23.0"
"@typescript-eslint/parser": "~2.23.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",
"eslint-plugin-react": "~7.19.0"
}
}

View File

@@ -51,7 +51,10 @@ export type Entry = File | Directory;
*/
export type DirectoryHierarchyMap = Map<string, Map<string, vscode.FileType>>;
export type ZipFileReference = { sourceArchiveZipPath: string, pathWithinSourceArchive: string };
export type ZipFileReference = {
sourceArchiveZipPath: string;
pathWithinSourceArchive: string;
};
/** Encodes a reference to a source file within a zipped source archive into a single URI. */
export function encodeSourceArchiveUri(ref: ZipFileReference): vscode.Uri {
@@ -87,7 +90,7 @@ export function encodeSourceArchiveUri(ref: ZipFileReference): vscode.Uri {
});
}
const sourceArchiveUriAuthorityPattern = /^(\d+)\-(\d+)$/;
const sourceArchiveUriAuthorityPattern = /^(\d+)-(\d+)$/;
class InvalidSourceArchiveUriError extends Error {
constructor(uri: vscode.Uri) {
@@ -139,8 +142,8 @@ function ensureDir(map: DirectoryHierarchyMap, dir: string) {
}
type Archive = {
unzipped: unzipper.CentralDirectory,
dirMap: DirectoryHierarchyMap,
unzipped: unzipper.CentralDirectory;
dirMap: DirectoryHierarchyMap;
};
export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
@@ -169,7 +172,7 @@ export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
const ref = decodeSourceArchiveUri(uri);
const archive = await this.getArchive(ref.sourceArchiveZipPath);
let contents = archive.dirMap.get(ref.pathWithinSourceArchive);
const contents = archive.dirMap.get(ref.pathWithinSourceArchive);
const result = contents === undefined ? [] : Array.from(contents.entries());
if (result === undefined) {
throw vscode.FileSystemError.FileNotFound(uri);
@@ -189,7 +192,7 @@ export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
// write operations, all disabled
writeFile(_uri: vscode.Uri, _content: Uint8Array, _options: { create: boolean, overwrite: boolean }): void {
writeFile(_uri: vscode.Uri, _content: Uint8Array, _options: { create: boolean; overwrite: boolean }): void {
throw this.readOnlyError;
}
@@ -239,7 +242,7 @@ export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
}
private async _lookupAsFile(uri: vscode.Uri): Promise<File> {
let entry = await this._lookup(uri);
const entry = await this._lookup(uri);
if (entry instanceof File) {
return entry;
}
@@ -254,7 +257,7 @@ export class ArchiveFileSystemProvider implements vscode.FileSystemProvider {
watch(_resource: vscode.Uri): vscode.Disposable {
// ignore, fires for all changes...
return new vscode.Disposable(() => { });
return new vscode.Disposable(() => { /**/ });
}
}

View File

@@ -1,14 +1,34 @@
export const PAGE_SIZE = 1000;
export type ColumnKind = "f" | "i" | "s" | "b" | "d" | "e";
/**
* The single-character codes used in the bqrs format for the the kind
* of a result column. This namespace is intentionally not an enum, see
* the "for the sake of extensibility" comment in messages.ts.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace ColumnKindCode {
export const FLOAT = "f";
export const INTEGER = "i";
export const STRING = "s";
export const BOOLEAN = "b";
export const DATE = "d";
export const ENTITY = "e";
}
export type ColumnKind =
| typeof ColumnKindCode.FLOAT
| typeof ColumnKindCode.INTEGER
| typeof ColumnKindCode.STRING
| typeof ColumnKindCode.BOOLEAN
| typeof ColumnKindCode.DATE
| typeof ColumnKindCode.ENTITY;
export interface Column {
name?: string;
kind: ColumnKind;
}
export interface ResultSetSchema {
name: string;
rows: number;

View File

@@ -10,10 +10,10 @@ import * as tk from 'tree-kill';
import * as util 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 { DistributionProvider } from "./distribution";
import { assertNever } from "./helpers-pure";
import { QueryMetadata, SortDirection } from "./interface-types";
import { Logger, ProgressReporter } from "./logging";
/**
* The version of the SARIF format that we are using.
@@ -243,11 +243,12 @@ export class CodeQLCliServer implements Disposable {
// Kill the process if it isn't already dead.
this.killProcessIfRunning();
// Report the error (if there is a stderr then use that otherwise just report the error cod or nodejs error)
if (stderrBuffers.length == 0) {
throw new Error(`${description} failed: ${err}`)
} else {
throw new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString("utf8")}`);
}
const newError =
stderrBuffers.length == 0
? new Error(`${description} failed: ${err}`)
: new Error(`${description} failed: ${Buffer.concat(stderrBuffers).toString("utf8")}`);
newError.stack += (err.stack || '');
throw newError;
} finally {
this.logger.log(Buffer.concat(stderrBuffers).toString("utf8"));
// Remove the listeners we set up.
@@ -604,7 +605,7 @@ export class CodeQLCliServer implements Disposable {
resolveQlpacks(additionalPacks: string[], searchPath?: string[]): Promise<QlpacksInfo> {
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
if (searchPath !== undefined) {
args.push('--search-path', searchPath.join(path.delimiter));
args.push('--search-path', path.join(...searchPath));
}
return this.runJsonCodeQlCliCommand<QlpacksInfo>(
@@ -613,6 +614,27 @@ export class CodeQLCliServer implements Disposable {
"Resolving qlpack information",
);
}
/**
* Gets information about queries in a query suite.
* @param suite The suite to resolve.
* @param additionalPacks A list of directories to search for qlpacks before searching in `searchPath`.
* @param searchPath A list of directories to search for packs not found in `additionalPacks`. If undefined,
* the default CLI search path is used.
* @returns A list of query files found.
*/
resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
if (searchPath !== undefined) {
args.push('--search-path', path.join(...searchPath));
}
args.push(suite);
return this.runJsonCodeQlCliCommand<string[]>(
['resolve', 'queries'],
args,
"Resolving queries",
);
}
}
/**

View File

@@ -1,5 +1,5 @@
import { DisposableObject } from 'semmle-vscode-utils';
import { workspace, Event, EventEmitter, ConfigurationChangeEvent } from 'vscode';
import { workspace, Event, EventEmitter, ConfigurationChangeEvent, ConfigurationTarget } from 'vscode';
import { DistributionManager } from './distribution';
import { logger } from './logging';
@@ -27,10 +27,29 @@ class Setting {
}
return workspace.getConfiguration(this.parent.qualifiedName).get<T>(this.name)!;
}
updateValue<T>(value: T, target: ConfigurationTarget): Thenable<void> {
if (this.parent === undefined) {
throw new Error('Cannot update the value of a root setting.');
}
return workspace.getConfiguration(this.parent.qualifiedName).update(this.name, value, target);
}
}
const ROOT_SETTING = new Setting('codeQL');
// Enable experimental features
/**
* 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);
// Distribution configuration
const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING);
@@ -59,16 +78,17 @@ const NUMBER_OF_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_QUERIES
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);
export const AUTOSAVE_SETTING = new Setting('autoSave', RUNNING_QUERIES_SETTING);
/** When these settings change, the running query server should be restarted. */
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, MEMORY_SETTING, DEBUG_SETTING];
export interface QueryServerConfig {
codeQlPath: string,
debug: boolean,
numThreads: number,
queryMemoryMb?: number,
timeoutSecs: number,
codeQlPath: string;
debug: boolean;
numThreads: number;
queryMemoryMb?: number;
timeoutSecs: number;
onDidChangeQueryServerConfiguration?: Event<void>;
}
@@ -76,7 +96,7 @@ export interface QueryServerConfig {
const QUERY_HISTORY_SETTINGS = [QUERY_HISTORY_FORMAT_SETTING];
export interface QueryHistoryConfig {
format: string,
format: string;
onDidChangeQueryHistoryConfiguration: Event<void>;
}
@@ -111,7 +131,7 @@ abstract class ConfigListener extends DisposableObject {
export class DistributionConfigListener extends ConfigListener implements DistributionConfig {
public get customCodeQlPath(): string | undefined {
return CUSTOM_CODEQL_PATH_SETTING.getValue() ? CUSTOM_CODEQL_PATH_SETTING.getValue() : undefined;
return CUSTOM_CODEQL_PATH_SETTING.getValue() || undefined;
}
public get includePrerelease(): boolean {
@@ -119,7 +139,7 @@ export class DistributionConfigListener extends ConfigListener implements Distri
}
public get personalAccessToken(): string | undefined {
return PERSONAL_ACCESS_TOKEN_SETTING.getValue() ? PERSONAL_ACCESS_TOKEN_SETTING.getValue() : undefined;
return PERSONAL_ACCESS_TOKEN_SETTING.getValue() || undefined;
}
public get onDidChangeDistributionConfiguration(): Event<void> {

View File

@@ -9,14 +9,14 @@ import { clearCacheInDatabase, UserCancellationException } from './run-queries';
import * as qsClient from './queryserver-client';
import { upgradeDatabase } from './upgrades';
type ThemableIconPath = { light: string, dark: string } | string;
type ThemableIconPath = { light: string; dark: string } | string;
/**
* Path to icons to display next to currently selected database.
*/
const SELECTED_DATABASE_ICON: ThemableIconPath = {
light: 'media/check-light-mode.svg',
dark: 'media/check-dark-mode.svg',
light: 'media/light/check.svg',
dark: 'media/dark/check.svg',
};
/**
@@ -34,12 +34,21 @@ function joinThemableIconPath(base: string, iconPath: ThemableIconPath): Themabl
return path.join(base, iconPath);
}
enum SortOrder {
NameAsc = 'NameAsc',
NameDesc = 'NameDesc',
DateAddedAsc = 'DateAddedAsc',
DateAddedDesc = 'DateAddedDesc'
}
/**
* Tree data provider for the databases view.
*/
class DatabaseTreeDataProvider extends DisposableObject
implements TreeDataProvider<DatabaseItem> {
private _sortOrder = SortOrder.NameAsc;
private readonly _onDidChangeTreeData = new EventEmitter<DatabaseItem | undefined>();
private currentDatabaseItem: DatabaseItem | undefined;
@@ -84,7 +93,18 @@ class DatabaseTreeDataProvider extends DisposableObject
public getChildren(element?: DatabaseItem): ProviderResult<DatabaseItem[]> {
if (element === undefined) {
return this.databaseManager.databaseItems.slice(0);
return this.databaseManager.databaseItems.slice(0).sort((db1, db2) => {
switch(this.sortOrder) {
case SortOrder.NameAsc:
return db1.name.localeCompare(db2.name);
case SortOrder.NameDesc:
return db2.name.localeCompare(db1.name);
case SortOrder.DateAddedAsc:
return (db1.dateAdded || 0) - (db2.dateAdded || 0);
case SortOrder.DateAddedDesc:
return (db2.dateAdded || 0) - (db1.dateAdded || 0);
}
});
}
else {
return [];
@@ -98,6 +118,15 @@ class DatabaseTreeDataProvider extends DisposableObject
public getCurrent(): DatabaseItem | undefined {
return this.currentDatabaseItem;
}
public get sortOrder() {
return this._sortOrder;
}
public set sortOrder(newSortOrder: SortOrder) {
this._sortOrder = newSortOrder;
this._onDidChangeTreeData.fire();
}
}
/** Gets the first element in the given list, if any, or undefined if the list is empty or undefined. */
@@ -129,19 +158,26 @@ async function chooseDatabaseDir(): Promise<Uri | undefined> {
}
export class DatabaseUI extends DisposableObject {
public constructor(ctx: ExtensionContext, private cliserver: cli.CodeQLCliServer, private databaseManager: DatabaseManager,
private readonly queryServer: qsClient.QueryServerClient | undefined) {
private treeDataProvider: DatabaseTreeDataProvider;
public constructor(
ctx: ExtensionContext,
private cliserver: cli.CodeQLCliServer,
private databaseManager: DatabaseManager,
private readonly queryServer: qsClient.QueryServerClient | undefined
) {
super();
const treeDataProvider = this.push(new DatabaseTreeDataProvider(ctx, databaseManager));
this.push(window.createTreeView('codeQLDatabases', { treeDataProvider }));
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));
}
@@ -154,6 +190,22 @@ export class DatabaseUI extends DisposableObject {
return await this.chooseAndSetDatabase();
}
private handleSortByName = async () => {
if (this.treeDataProvider.sortOrder === SortOrder.NameAsc) {
this.treeDataProvider.sortOrder = SortOrder.NameDesc;
} else {
this.treeDataProvider.sortOrder = SortOrder.NameAsc;
}
}
private handleSortByDateAdded = async () => {
if (this.treeDataProvider.sortOrder === SortOrder.DateAddedAsc) {
this.treeDataProvider.sortOrder = SortOrder.DateAddedDesc;
} else {
this.treeDataProvider.sortOrder = SortOrder.DateAddedAsc;
}
}
private handleUpgradeCurrentDatabase = async (): Promise<void> => {
await this.handleUpgradeDatabase(this.databaseManager.currentDatabaseItem);
}

View File

@@ -24,21 +24,23 @@ import { Logger, logger } from './logging';
* The name of the key in the workspaceState dictionary in which we
* persist the current database across sessions.
*/
const CURRENT_DB: string = 'currentDatabase';
const CURRENT_DB = 'currentDatabase';
/**
* The name of the key in the workspaceState dictionary in which we
* persist the list of databases across sessions.
*/
const DB_LIST: string = 'databaseList';
const DB_LIST = 'databaseList';
export interface DatabaseOptions {
displayName?: string;
ignoreSourceArchive?: boolean;
dateAdded?: number | undefined;
}
interface FullDatabaseOptions extends DatabaseOptions {
ignoreSourceArchive: boolean;
dateAdded: number | undefined;
}
interface PersistedDatabaseItem {
@@ -107,8 +109,8 @@ async function findDataset(parentDirectory: string): Promise<vscode.Uri> {
return vscode.Uri.file(dbAbsolutePath);
}
async function findSourceArchive(databasePath: string, silent: boolean = false):
Promise<vscode.Uri | undefined> {
async function findSourceArchive(databasePath: string, silent = false):
Promise<vscode.Uri | undefined> {
const relativePaths = ['src', 'output/src_archive']
@@ -128,8 +130,9 @@ async function findSourceArchive(databasePath: string, silent: boolean = false):
return undefined;
}
async function resolveDatabase(databasePath: string):
Promise<DatabaseContents | undefined> {
async function resolveDatabase(
databasePath: string
): Promise<DatabaseContents | undefined> {
const name = path.basename(databasePath);
@@ -208,6 +211,12 @@ export interface DatabaseItem {
* Will be `undefined` if the database is invalid. Can be updated by calling `refresh()`.
*/
readonly contents: DatabaseContents | undefined;
/**
* The date this database was added as a unix timestamp. Or undefined if we don't know.
*/
readonly dateAdded: number | undefined;
/** If the database is invalid, describes why. */
readonly error: Error | undefined;
/**
@@ -291,6 +300,10 @@ class DatabaseItemImpl implements DatabaseItem {
return this._contents;
}
public get dateAdded(): number | undefined {
return this.options.dateAdded;
}
public get error(): Error | undefined {
return this._error;
}
@@ -427,31 +440,31 @@ class DatabaseItemImpl implements DatabaseItem {
* `event` fires. If waiting for the event takes too long (by default
* >1000ms) log a warning, and resolve to undefined.
*/
function eventFired<T>(event: vscode.Event<T>, timeoutMs: number = 1000): Promise<T | undefined> {
function eventFired<T>(event: vscode.Event<T>, timeoutMs = 1000): Promise<T | undefined> {
return new Promise((res, _rej) => {
let timeout: NodeJS.Timeout | undefined;
let disposable: vscode.Disposable | undefined;
function dispose() {
if (timeout !== undefined) clearTimeout(timeout);
if (disposable !== undefined) disposable.dispose();
}
disposable = event(e => {
res(e); dispose();
});
timeout = setTimeout(() => {
const timeout = setTimeout(() => {
logger.log(`Waiting for event ${event} timed out after ${timeoutMs}ms`);
res(undefined); dispose();
res(undefined);
dispose();
}, timeoutMs);
const disposable = event(e => {
res(e);
dispose();
});
function dispose() {
clearTimeout(timeout);
disposable.dispose();
}
});
}
export class DatabaseManager extends DisposableObject {
private readonly _onDidChangeDatabaseItem =
this.push(new vscode.EventEmitter<DatabaseItem | undefined>());
this.push(new vscode.EventEmitter<DatabaseItem | undefined>());
readonly onDidChangeDatabaseItem = this._onDidChangeDatabaseItem.event;
private readonly _onDidChangeCurrentDatabaseItem =
this.push(new vscode.EventEmitter<DatabaseItem | undefined>());
this.push(new vscode.EventEmitter<DatabaseItem | undefined>());
readonly onDidChangeCurrentDatabaseItem = this._onDidChangeCurrentDatabaseItem.event;
private readonly _databaseItems: DatabaseItemImpl[] = [];
@@ -465,8 +478,9 @@ export class DatabaseManager extends DisposableObject {
this.loadPersistedState(); // Let this run async.
}
public async openDatabase(uri: vscode.Uri, options?: DatabaseOptions):
Promise<DatabaseItem> {
public async openDatabase(
uri: vscode.Uri, options?: DatabaseOptions
): Promise<DatabaseItem> {
const contents = await resolveDatabaseContents(uri);
const realOptions = options || {};
@@ -475,7 +489,8 @@ export class DatabaseManager extends DisposableObject {
const fullOptions: FullDatabaseOptions = {
ignoreSourceArchive: (realOptions.ignoreSourceArchive !== undefined) ?
realOptions.ignoreSourceArchive : isQLTestDatabase,
displayName: realOptions.displayName
displayName: realOptions.displayName,
dateAdded: realOptions.dateAdded || Date.now()
};
const databaseItem = new DatabaseItemImpl(uri, contents, fullOptions, (item) => {
this._onDidChangeDatabaseItem.fire(item);
@@ -525,11 +540,13 @@ export class DatabaseManager extends DisposableObject {
}
}
private async createDatabaseItemFromPersistedState(state: PersistedDatabaseItem):
Promise<DatabaseItem> {
private async createDatabaseItemFromPersistedState(
state: PersistedDatabaseItem
): Promise<DatabaseItem> {
let displayName: string | undefined = undefined;
let ignoreSourceArchive = false;
let dateAdded = undefined;
if (state.options) {
if (typeof state.options.displayName === 'string') {
displayName = state.options.displayName;
@@ -537,10 +554,14 @@ export class DatabaseManager extends DisposableObject {
if (typeof state.options.ignoreSourceArchive === 'boolean') {
ignoreSourceArchive = state.options.ignoreSourceArchive;
}
if (typeof state.options.dateAdded === 'number') {
dateAdded = state.options.dateAdded;
}
}
const fullOptions: FullDatabaseOptions = {
ignoreSourceArchive: ignoreSourceArchive,
displayName: displayName
ignoreSourceArchive,
displayName,
dateAdded
};
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri), undefined, fullOptions,
(item) => {
@@ -584,7 +605,7 @@ export class DatabaseManager extends DisposableObject {
}
public async setCurrentDatabaseItem(item: DatabaseItem | undefined,
skipRefresh: boolean = false): Promise<void> {
skipRefresh = false): Promise<void> {
if (!skipRefresh && (item !== undefined)) {
await item.refresh(); // Will throw on invalid database.
@@ -610,6 +631,11 @@ export class DatabaseManager extends DisposableObject {
return this._databaseItems.find(item => item.databaseUri.toString(true) === uriString);
}
public findDatabaseItemBySourceArchive(uri: vscode.Uri): DatabaseItem | undefined {
const uriString = uri.toString(true);
return this._databaseItems.find(item => item.sourceArchive && item.sourceArchive.toString(true) === uriString);
}
private async addDatabaseItem(item: DatabaseItemImpl) {
this._databaseItems.push(item);
this.updatePersistedDatabaseList();

View File

@@ -0,0 +1,203 @@
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, (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, (_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,
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.ReferenceQuery)) {
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

@@ -84,4 +84,4 @@ export abstract class Discovery<T> extends DisposableObject {
* @param results The discovery results returned by the `discover` function.
*/
protected abstract update(results: T): void;
}
}

View File

@@ -8,6 +8,7 @@ import { ExtensionContext, Event } from "vscode";
import { DistributionConfig } from "./config";
import { InvocationRateLimiter, InvocationRateLimiterResultKind, ProgressUpdate, showAndLogErrorMessage } from "./helpers";
import { logger } from "./logging";
import * as helpers from "./helpers";
import { getCodeQlCliVersion, tryParseVersionString, Version } from "./cli-version";
/**
@@ -19,7 +20,7 @@ import { getCodeQlCliVersion, tryParseVersionString, Version } from "./cli-versi
/**
* Default value for the owner name of the extension-managed distribution on GitHub.
*
*
* We set the default here rather than as a default config value so that this default is invoked
* upon blanking the setting.
*/
@@ -27,7 +28,7 @@ const DEFAULT_DISTRIBUTION_OWNER_NAME = "github";
/**
* Default value for the repository name of the extension-managed distribution on GitHub.
*
*
* We set the default here rather than as a default config value so that this default is invoked
* upon blanking the setting.
*/
@@ -35,7 +36,7 @@ const DEFAULT_DISTRIBUTION_REPOSITORY_NAME = "codeql-cli-binaries";
/**
* Version constraint for the CLI.
*
*
* This applies to both extension-managed and CLI distributions.
*/
export const DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT: VersionConstraint = {
@@ -46,8 +47,8 @@ export const DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT: VersionConstraint = {
}
export interface DistributionProvider {
getCodeQlPathWithoutVersionCheck(): Promise<string | undefined>,
onDidChangeDistribution?: Event<void>
getCodeQlPathWithoutVersionCheck(): Promise<string | undefined>;
onDidChangeDistribution?: Event<void>;
}
export class DistributionManager implements DistributionProvider {
@@ -94,18 +95,32 @@ export class DistributionManager implements DistributionProvider {
};
}
public async hasDistribution(): Promise<boolean> {
const result = await this.getDistribution();
return result.kind !== FindDistributionResultKind.NoDistribution;
}
/**
* Returns the path to a possibly-compatible CodeQL launcher binary, or undefined if a binary not be found.
*/
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
// Check config setting, then extension specific distribution, then PATH.
if (this._config.customCodeQlPath !== undefined) {
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()!) &&
await this.hasNewLauncherName()
) {
warnDeprecatedLauncher();
}
return this._config.customCodeQlPath;
}
@@ -116,8 +131,8 @@ export class DistributionManager implements DistributionProvider {
if (process.env.PATH) {
for (const searchDirectory of process.env.PATH.split(path.delimiter)) {
const expectedLauncherPath = path.join(searchDirectory, codeQlLauncherName());
if (await fs.pathExists(expectedLauncherPath)) {
const expectedLauncherPath = await getExecutableFromDirectory(searchDirectory);
if (expectedLauncherPath) {
return expectedLauncherPath;
}
}
@@ -130,7 +145,7 @@ export class DistributionManager implements DistributionProvider {
/**
* Check for updates to the extension-managed distribution. If one has not already been installed,
* this will return an update available result with the latest available release.
*
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public async checkForUpdatesToExtensionManagedDistribution(
@@ -152,7 +167,7 @@ export class DistributionManager implements DistributionProvider {
/**
* Installs a release of the extension-managed distribution.
*
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public installExtensionManagedDistributionRelease(release: Release,
@@ -164,6 +179,21 @@ export class DistributionManager implements DistributionProvider {
return this._onDidChangeDistribution;
}
/**
* @return true if the non-deprecated launcher name exists on the file system
* in the same directory as the specified launcher only if using an external
* installation. False otherwise.
*/
private async hasNewLauncherName(): Promise<boolean> {
if (!this._config.customCodeQlPath) {
// not managed externally
return false;
}
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>;
@@ -181,12 +211,11 @@ class ExtensionSpecificDistributionManager {
public async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
if (this.getInstalledRelease() !== undefined) {
// An extension specific distribution has been installed.
const expectedLauncherPath = path.join(this.getDistributionRootPath(), codeQlLauncherName());
if (await fs.pathExists(expectedLauncherPath)) {
const expectedLauncherPath = await getExecutableFromDirectory(this.getDistributionRootPath(), true);
if (expectedLauncherPath) {
return expectedLauncherPath;
}
logger.log(`WARNING: Expected to find a CodeQL CLI executable at ${expectedLauncherPath} but one was not found. ` +
"Will try PATH.");
try {
await this.removeDistribution();
} catch (e) {
@@ -200,7 +229,7 @@ class ExtensionSpecificDistributionManager {
/**
* Check for updates to the extension-managed distribution. If one has not already been installed,
* this will return an update available result with the latest available release.
*
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public async checkForUpdatesToDistribution(): Promise<DistributionUpdateCheckResult> {
@@ -208,7 +237,11 @@ class ExtensionSpecificDistributionManager {
const extensionSpecificRelease = this.getInstalledRelease();
const latestRelease = await this.getLatestRelease();
if (extensionSpecificRelease !== undefined && codeQlPath !== undefined && latestRelease.id === extensionSpecificRelease.id) {
if (
extensionSpecificRelease !== undefined &&
codeQlPath !== undefined &&
latestRelease.id === extensionSpecificRelease.id
) {
return createAlreadyUpToDateResult();
}
return createUpdateAvailableResult(latestRelease);
@@ -216,7 +249,7 @@ class ExtensionSpecificDistributionManager {
/**
* Installs a release of the extension-managed distribution.
*
*
* Returns a failed promise if an unexpected error occurs during installation.
*/
public async installDistributionRelease(release: Release,
@@ -247,8 +280,8 @@ class ExtensionSpecificDistributionManager {
if (progressCallback && contentLength !== null) {
const totalNumBytes = parseInt(contentLength, 10);
const bytesToDisplayMB = (numBytes: number) => `${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = () => {
const bytesToDisplayMB = (numBytes: number): string => `${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = (): void => {
progressCallback({
step: numBytesDownloaded,
maxStep: totalNumBytes,
@@ -282,7 +315,7 @@ class ExtensionSpecificDistributionManager {
/**
* Remove the extension-managed distribution.
*
*
* This should not be called for a distribution that is currently in use, as remove may fail.
*/
private async removeDistribution(): Promise<void> {
@@ -357,7 +390,7 @@ export class ReleasesApiConsumer {
this._repoName = repoName;
}
public async getLatestRelease(versionConstraint: VersionConstraint, includePrerelease: boolean = false): Promise<Release> {
public async getLatestRelease(versionConstraint: VersionConstraint, includePrerelease = false): Promise<Release> {
const apiPath = `/repos/${this._ownerName}/${this._repoName}/releases`;
const allReleases: GithubRelease[] = await (await this.makeApiCall(apiPath)).json();
const compatibleReleases = allReleases.filter(release => {
@@ -428,7 +461,7 @@ export class ReleasesApiConsumer {
private async makeRawRequest(
requestUrl: string,
headers: { [key: string]: string },
redirectCount: number = 0): Promise<fetch.Response> {
redirectCount = 0): Promise<fetch.Response> {
const response = await fetch.default(requestUrl, {
headers,
redirect: "manual"
@@ -480,7 +513,7 @@ 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.
@@ -502,7 +535,11 @@ export function versionCompare(a: Version, b: Version): number {
}
function codeQlLauncherName(): string {
return (os.platform() === "win32") ? "codeql.cmd" : "codeql";
return (os.platform() === "win32") ? "codeql.exe" : "codeql";
}
function deprecatedCodeQlLauncherName(): string | undefined {
return (os.platform() === "win32") ? "codeql.cmd" : undefined;
}
function isRedirectStatusCode(statusCode: number): boolean {
@@ -520,13 +557,16 @@ export enum FindDistributionResultKind {
NoDistribution
}
export type FindDistributionResult = CompatibleDistributionResult | UnknownCompatibilityDistributionResult |
IncompatibleDistributionResult | NoDistributionResult;
export type FindDistributionResult =
| CompatibleDistributionResult
| UnknownCompatibilityDistributionResult
| IncompatibleDistributionResult
| NoDistributionResult;
interface CompatibleDistributionResult {
codeQlPath: string;
kind: FindDistributionResultKind.CompatibleDistribution;
version: Version
version: Version;
}
interface UnknownCompatibilityDistributionResult {
@@ -551,11 +591,14 @@ export enum DistributionUpdateCheckResultKind {
UpdateAvailable
}
type DistributionUpdateCheckResult = AlreadyCheckedRecentlyResult | AlreadyUpToDateResult | InvalidLocationResult |
UpdateAvailableResult;
type DistributionUpdateCheckResult =
| AlreadyCheckedRecentlyResult
| AlreadyUpToDateResult
| InvalidLocationResult
| UpdateAvailableResult;
export interface AlreadyCheckedRecentlyResult {
kind: DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult
kind: DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult;
}
export interface AlreadyUpToDateResult {
@@ -599,6 +642,31 @@ function createUpdateAvailableResult(updatedRelease: Release): UpdateAvailableRe
};
}
// Exported for testing
export async function getExecutableFromDirectory(directory: string, warnWhenNotFound = false): Promise<string | undefined> {
const expectedLauncherPath = path.join(directory, codeQlLauncherName());
const deprecatedLauncherName = deprecatedCodeQlLauncherName();
const alternateExpectedLauncherPath = deprecatedLauncherName ? path.join(directory, deprecatedLauncherName) : undefined;
if (await fs.pathExists(expectedLauncherPath)) {
return expectedLauncherPath;
} else if (alternateExpectedLauncherPath && (await fs.pathExists(alternateExpectedLauncherPath))) {
warnDeprecatedLauncher();
return alternateExpectedLauncherPath;
}
if (warnWhenNotFound) {
logger.log(`WARNING: Expected to find a CodeQL CLI executable at ${expectedLauncherPath} but one was not found. ` +
"Will try PATH.");
}
return undefined;
}
function warnDeprecatedLauncher() {
helpers.showAndLogWarningMessage(
`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.`
);
}
/**
* A release on GitHub.
*/

View File

@@ -1,25 +1,23 @@
import { commands, Disposable, ExtensionContext, extensions, ProgressLocation, ProgressOptions, window as Window, Uri } from 'vscode';
import { commands, Disposable, ExtensionContext, extensions, languages, ProgressLocation, ProgressOptions, Uri, window as Window } from 'vscode';
import { LanguageClient } from 'vscode-languageclient';
import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
import * as archiveFilesystemProvider from './archive-filesystem-provider';
import { DistributionConfigListener, QueryServerConfigListener, QueryHistoryConfigListener } from './config';
import { CodeQLCliServer } from './cli';
import { DistributionConfigListener, QueryHistoryConfigListener, QueryServerConfigListener, EXPERIMENTAL_FEATURES_SETTING } from './config';
import { DatabaseManager } from './databases';
import { DatabaseUI } from './databases-ui';
import {
DistributionUpdateCheckResultKind, DistributionManager, FindDistributionResult, FindDistributionResultKind, GithubApiError,
DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT, GithubRateLimitedError
} from './distribution';
import { TemplateQueryDefinitionProvider, TemplateQueryReferenceProvider } from './definitions';
import { DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT, DistributionManager, DistributionUpdateCheckResultKind, FindDistributionResult, FindDistributionResultKind, GithubApiError, GithubRateLimitedError } from './distribution';
import * as helpers from './helpers';
import { assertNever } from './helpers-pure';
import { spawnIdeServer } from './ide-server';
import { InterfaceManager, WebviewReveal } from './interface';
import { ideServerLogger, logger, queryServerLogger } from './logging';
import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationException } from './run-queries';
import { CompletedQuery } from './query-results';
import { QueryHistoryManager } from './query-history';
import { CompletedQuery } from './query-results';
import * as qsClient from './queryserver-client';
import { CodeQLCliServer } from './cli';
import { assertNever } from './helpers-pure';
import { displayQuickQuery } from './quick-query';
import { TestHub, testExplorerExtensionId } from 'vscode-test-adapter-api';
import { compileAndRunQueryAgainstDatabase, tmpDirDisposal, UserCancellationException } from './run-queries';
import { QLTestAdapterFactory } from './test-adapter';
import { TestUIService } from './test-ui';
@@ -76,10 +74,10 @@ function registerErrorStubs(excludedCommands: string[], stubGenerator: (command:
}
export async function activate(ctx: ExtensionContext): Promise<void> {
// Initialise logging, and ensure all loggers are disposed upon exit.
ctx.subscriptions.push(logger);
logger.log('Starting CodeQL extension');
initializeLogging(ctx);
const distributionConfigListener = new DistributionConfigListener();
ctx.subscriptions.push(distributionConfigListener);
const distributionManager = new DistributionManager(ctx, distributionConfigListener, DEFAULT_DISTRIBUTION_VERSION_CONSTRAINT);
@@ -93,6 +91,7 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
interface DistributionUpdateConfig {
isUserInitiated: boolean;
shouldDisplayMessageWhenNoUpdates: boolean;
allowAutoUpdating: boolean;
}
async function installOrUpdateDistributionWithProgressTitle(progressTitle: string, config: DistributionUpdateConfig): Promise<void> {
@@ -100,24 +99,28 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
const noUpdatesLoggingFunc = config.shouldDisplayMessageWhenNoUpdates ?
helpers.showAndLogInformationMessage : async (message: string) => logger.log(message);
const result = await distributionManager.checkForUpdatesToExtensionManagedDistribution(minSecondsSinceLastUpdateCheck);
// We do want to auto update if there is no distribution at all
const allowAutoUpdating = config.allowAutoUpdating || !await distributionManager.hasDistribution();
switch (result.kind) {
case DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult:
logger.log("Didn't perform CodeQL CLI update check since a check was already performed within the previous " +
`${minSecondsSinceLastUpdateCheck} seconds.`);
break;
case DistributionUpdateCheckResultKind.AlreadyUpToDate:
await noUpdatesLoggingFunc("CodeQL CLI already up to date.");
await noUpdatesLoggingFunc('CodeQL CLI already up to date.');
break;
case DistributionUpdateCheckResultKind.InvalidLocation:
await noUpdatesLoggingFunc("CodeQL CLI is installed externally so could not be updated.");
await noUpdatesLoggingFunc('CodeQL CLI is installed externally so could not be updated.');
break;
case DistributionUpdateCheckResultKind.UpdateAvailable:
if (beganMainExtensionActivation) {
if (beganMainExtensionActivation || !allowAutoUpdating) {
const updateAvailableMessage = `Version "${result.updatedRelease.name}" of the CodeQL CLI is now available. ` +
"The update will be installed after Visual Studio Code restarts. Restart now to upgrade?";
'Do you wish to upgrade?';
await ctx.globalState.update(shouldUpdateOnNextActivationKey, true);
if (await helpers.showInformationMessageWithAction(updateAvailableMessage, "Restart and Upgrade")) {
await commands.executeCommand("workbench.action.reloadWindow");
if (await helpers.showInformationMessageWithAction(updateAvailableMessage, 'Restart and Upgrade')) {
await commands.executeCommand('workbench.action.reloadWindow');
}
} else {
const progressOptions: ProgressOptions = {
@@ -144,8 +147,12 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
isInstallingOrUpdatingDistribution = true;
const codeQlInstalled = await distributionManager.getCodeQlPathWithoutVersionCheck() !== undefined;
const willUpdateCodeQl = ctx.globalState.get(shouldUpdateOnNextActivationKey);
const messageText = willUpdateCodeQl ? "Updating CodeQL CLI" :
codeQlInstalled ? "Checking for updates to CodeQL CLI" : "Installing CodeQL CLI";
const messageText = willUpdateCodeQl
? "Updating CodeQL CLI"
: codeQlInstalled
? "Checking for updates to CodeQL CLI"
: "Installing CodeQL CLI";
try {
await installOrUpdateDistributionWithProgressTitle(messageText, config);
} catch (e) {
@@ -207,7 +214,8 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
if (chosenAction === installActionName) {
installOrUpdateThenTryActivate({
isUserInitiated: true,
shouldDisplayMessageWhenNoUpdates: false
shouldDisplayMessageWhenNoUpdates: false,
allowAutoUpdating: true
});
}
});
@@ -216,16 +224,22 @@ export async function activate(ctx: ExtensionContext): Promise<void> {
ctx.subscriptions.push(distributionConfigListener.onDidChangeDistributionConfiguration(() => installOrUpdateThenTryActivate({
isUserInitiated: true,
shouldDisplayMessageWhenNoUpdates: false
shouldDisplayMessageWhenNoUpdates: false,
allowAutoUpdating: true
})));
ctx.subscriptions.push(commands.registerCommand(checkForUpdatesCommand, () => installOrUpdateThenTryActivate({
isUserInitiated: true,
shouldDisplayMessageWhenNoUpdates: true
shouldDisplayMessageWhenNoUpdates: true,
allowAutoUpdating: true
})));
await installOrUpdateThenTryActivate({
isUserInitiated: !!ctx.globalState.get(shouldUpdateOnNextActivationKey),
shouldDisplayMessageWhenNoUpdates: false
shouldDisplayMessageWhenNoUpdates: false,
// only auto update on startup if the user has previously requested an update
// otherwise, ask user to accept the update
allowAutoUpdating: !!ctx.globalState.get(shouldUpdateOnNextActivationKey)
});
}
@@ -238,10 +252,6 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener(distributionManager);
ctx.subscriptions.push(qlConfigurationListener);
ctx.subscriptions.push(queryServerLogger);
ctx.subscriptions.push(ideServerLogger);
const cliServer = new CodeQLCliServer(distributionManager, logger);
ctx.subscriptions.push(cliServer);
@@ -325,6 +335,26 @@ async function activateWithInstalledDistribution(ctx: ExtensionContext, distribu
}));
ctx.subscriptions.push(client.start());
if (EXPERIMENTAL_FEATURES_SETTING.getValue()) {
languages.registerDefinitionProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryDefinitionProvider(cliServer, qs, dbm)
);
languages.registerReferenceProvider(
{ scheme: archiveFilesystemProvider.zipArchiveScheme },
new TemplateQueryReferenceProvider(cliServer, qs, dbm)
);
}
}
function initializeLogging(ctx: ExtensionContext): void {
logger.init(ctx);
queryServerLogger.init(ctx);
ideServerLogger.init(ctx);
ctx.subscriptions.push(logger);
ctx.subscriptions.push(queryServerLogger);
ctx.subscriptions.push(ideServerLogger);
}
const checkForUpdatesCommand = 'codeQL.checkForUpdatesToCLI';

View File

@@ -1,5 +1,9 @@
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 { CodeQLCliServer } from './cli';
import { logger } from './logging';
import { QueryInfo } from './run-queries';
@@ -131,7 +135,7 @@ export async function showInformationMessageWithAction(message: string, actionMe
/** Gets all active workspace folders that are on the filesystem. */
export function getOnDiskWorkspaceFolders() {
const workspaceFolders = workspace.workspaceFolders || [];
let diskWorkspaceFolders: string[] = [];
const diskWorkspaceFolders: string[] = [];
for (const workspaceFolder of workspaceFolders) {
if (workspaceFolder.uri.scheme === "file")
diskWorkspaceFolders.push(workspaceFolder.uri.fsPath)
@@ -179,8 +183,12 @@ export class InvocationRateLimiter<T> {
public async invokeFunctionIfIntervalElapsed(minSecondsSinceLastInvocation: number): Promise<InvocationRateLimiterResult<T>> {
const updateCheckStartDate = this._createDate();
const lastInvocationDate = this.getLastInvocationDate();
if (minSecondsSinceLastInvocation && lastInvocationDate && lastInvocationDate <= updateCheckStartDate &&
lastInvocationDate.getTime() + minSecondsSinceLastInvocation * 1000 > updateCheckStartDate.getTime()) {
if (
minSecondsSinceLastInvocation &&
lastInvocationDate &&
lastInvocationDate <= updateCheckStartDate &&
lastInvocationDate.getTime() + minSecondsSinceLastInvocation * 1000 > updateCheckStartDate.getTime()
) {
return createRateLimitedResult();
}
const result = await this._func();
@@ -215,15 +223,15 @@ export enum InvocationRateLimiterResultKind {
* The function was invoked and returned the value `result`.
*/
interface InvokedResult<T> {
kind: InvocationRateLimiterResultKind.Invoked,
result: T
kind: InvocationRateLimiterResultKind.Invoked;
result: T;
}
/**
* The function was not invoked as the minimum interval since the last invocation had not elapsed.
*/
interface RateLimitedResult {
kind: InvocationRateLimiterResultKind.RateLimited
kind: InvocationRateLimiterResultKind.RateLimited;
}
type InvocationRateLimiterResult<T> = InvokedResult<T> | RateLimitedResult;
@@ -240,3 +248,110 @@ function createRateLimitedResult(): RateLimitedResult {
kind: InvocationRateLimiterResultKind.RateLimited
};
}
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 }[] =
Object.entries(qlpacks).map(([packName, dirs]) => {
if (dirs.length < 1) {
logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`);
return { packName, packDir: undefined };
}
if (dirs.length > 1) {
logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has more than one directory; arbitrarily choosing the first`);
}
return {
packName,
packDir: dirs[0]
}
});
for (const { packDir, packName } of packs) {
if (packDir !== undefined) {
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8'));
if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) {
return packName;
}
}
}
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'))
if (dbschemes.length < 1) {
throw new Error(`Can't find dbscheme for current database in ${datasetFolder}`);
}
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 };
}
/**
* A cached mapping from strings to value of type U.
*/
export class CachedOperation<U> {
private readonly operation: (t: string) => 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) {
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> {
// 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])
return fromCache;
}
// Otherwise check if in progress
const inProgressCallback = this.inProgressCallbacks.get(t);
if (inProgressCallback !== undefined) {
// If so wait for it to resolve
return await new Promise((resolve, reject) => {
inProgressCallback.push([resolve, reject]);
});
}
// Otherwise compute the new value, but leave a callback to allow sharing work
const callbacks: [(u: U) => void, (reason?: any) => void][] = [];
this.inProgressCallbacks.set(t, callbacks);
try {
const result = await this.operation(t);
callbacks.forEach(f => f[0](result));
this.inProgressCallbacks.delete(t);
if (this.lru.length > this.cacheSize) {
const toRemove = this.lru.shift()!;
this.cached.delete(toRemove);
}
this.lru.push(t);
this.cached.set(t, result);
return result;
} catch (e) {
// Rethrow error on all callbacks
callbacks.forEach(f => f[1](e));
throw e;
} finally {
this.inProgressCallbacks.delete(t);
}
}
}

View File

@@ -17,8 +17,8 @@ export async function spawnIdeServer(config: QueryServerConfig): Promise<StreamI
['execute', 'language-server'],
['--check-errors', 'ON_CHANGE'],
ideServerLogger,
data => ideServerLogger.logWithoutTrailingNewline(data.toString()),
data => ideServerLogger.logWithoutTrailingNewline(data.toString()),
data => ideServerLogger.log(data.toString(), { trailingNewline: false }),
data => ideServerLogger.log(data.toString(), { trailingNewline: false }),
progressReporter
);
return { writer: child.stdin!, reader: child.stdout! };

View File

@@ -18,10 +18,10 @@ export interface DatabaseInfo {
/** Arbitrary query metadata */
export interface QueryMetadata {
name?: string,
description?: string,
id?: string,
kind?: string
name?: string;
description?: string;
id?: string;
kind?: string;
}
export interface PreviousExecution {
@@ -70,18 +70,18 @@ export interface SetStateMsg {
sortedResultsMap: SortedResultsMap;
interpretation: undefined | Interpretation;
database: DatabaseInfo;
metadata?: QueryMetadata
metadata?: QueryMetadata;
/**
* Whether to keep displaying the old results while rendering the new results.
*
* This is useful to prevent properties like scroll state being lost when rendering the sorted results after sorting a column.
*/
shouldKeepOldResultsWhileRendering: boolean;
};
}
/** Advance to the next or previous path no in the path viewer */
export interface NavigatePathMsg {
t: 'navigatePath',
t: 'navigatePath';
/** 1 for next, -1 for previous */
direction: number;
@@ -100,20 +100,20 @@ interface ViewSourceFileMsg {
t: 'viewSourceFile';
loc: ResolvableLocationValue;
databaseUri: string;
};
}
interface ToggleDiagnostics {
t: 'toggleDiagnostics';
databaseUri: string;
metadata?: QueryMetadata
metadata?: QueryMetadata;
origResultsPaths: ResultsPaths;
visible: boolean;
kind?: string;
};
}
interface ResultViewLoaded {
t: 'resultViewLoaded';
};
}
export enum SortDirection {
asc, desc

View File

@@ -437,9 +437,9 @@ export class InterfaceManager extends DisposableObject {
sourceArchiveUri === undefined
? undefined
: {
sourceArchive: sourceArchiveUri.fsPath,
sourceLocationPrefix
};
sourceArchive: sourceArchiveUri.fsPath,
sourceLocationPrefix
};
interpretation = await this.getTruncatedResults(
query.metadata,
query.resultsPaths,
@@ -471,9 +471,9 @@ export class InterfaceManager extends DisposableObject {
sourceArchiveUri === undefined
? undefined
: {
sourceArchive: sourceArchiveUri.fsPath,
sourceLocationPrefix
};
sourceArchive: sourceArchiveUri.fsPath,
sourceLocationPrefix
};
const interpretation = await this.getTruncatedResults(
metadata,
resultsInfo,

View File

@@ -1,17 +1,36 @@
import { window as Window, OutputChannel, Progress } from 'vscode';
import { window as Window, OutputChannel, Progress, ExtensionContext, Disposable } from 'vscode';
import { DisposableObject } from 'semmle-vscode-utils';
import * as fs from 'fs-extra';
import * as path from 'path';
interface LogOptions {
/** If false, don't output a trailing newline for the log entry. Default true. */
trailingNewline?: boolean;
/** If specified, add this log entry to the log file at the specified location. */
additionalLogLocation?: string;
}
export interface Logger {
/** Writes the given log message, followed by a newline. */
log(message: string): void;
/** Writes the given log message, not followed by a newline. */
logWithoutTrailingNewline(message: string): void;
/** Writes the given log message, optionally followed by a newline. */
log(message: string, options?: LogOptions): Promise<void>;
/**
* Reveal this channel in the UI.
*
* @param preserveFocus When `true` the channel will not take focus.
*/
show(preserveFocus?: boolean): void;
/**
* Remove the log at the specified location
* @param location log to remove
*/
removeAdditionalLogLocation(location: string | undefined): void;
/**
* The base location location where all side log files are stored.
*/
getBaseLocation(): string | undefined;
}
export type ProgressReporter = Progress<{ message: string }>;
@@ -19,24 +38,98 @@ export type ProgressReporter = Progress<{ message: string }>;
/** A logger that writes messages to an output channel in the Output tab. */
export class OutputChannelLogger extends DisposableObject implements Logger {
public readonly outputChannel: OutputChannel;
private readonly additionalLocations = new Map<string, AdditionalLogLocation>();
private additionalLogLocationPath: string | undefined;
constructor(title: string) {
constructor(private title: string) {
super();
this.outputChannel = Window.createOutputChannel(title);
this.push(this.outputChannel);
}
log(message: string) {
this.outputChannel.appendLine(message);
init(ctx: ExtensionContext): void {
this.additionalLogLocationPath = path.join(ctx.storagePath || ctx.globalStoragePath, this.title);
// clear out any old state from previous runs
fs.remove(this.additionalLogLocationPath);
}
logWithoutTrailingNewline(message: string) {
this.outputChannel.append(message);
/**
* This function is asynchronous and will only resolve once the message is written
* to the side log (if required). It is not necessary to await the results of this
* function if you don't need to guarantee that the log writing is complete before
* continuing.
*/
async log(message: string, options = {} as LogOptions): Promise<void> {
if (options.trailingNewline === undefined) {
options.trailingNewline = true;
}
if (options.trailingNewline) {
this.outputChannel.appendLine(message);
} else {
this.outputChannel.append(message);
}
if (this.additionalLogLocationPath && options.additionalLogLocation) {
const logPath = path.join(this.additionalLogLocationPath, options.additionalLogLocation);
let additional = this.additionalLocations.get(logPath);
if (!additional) {
const msg = `| Log being saved to ${logPath} |`;
const separator = new Array(msg.length).fill('-').join('');
this.outputChannel.appendLine(separator);
this.outputChannel.appendLine(msg);
this.outputChannel.appendLine(separator);
additional = new AdditionalLogLocation(logPath);
this.additionalLocations.set(logPath, additional);
this.track(additional);
}
await additional.log(message, options);
}
}
show(preserveFocus?: boolean) {
show(preserveFocus?: boolean): void {
this.outputChannel.show(preserveFocus);
}
removeAdditionalLogLocation(location: string | undefined): void {
if (this.additionalLogLocationPath && location) {
const logPath = location.startsWith(this.additionalLogLocationPath)
? location
: path.join(this.additionalLogLocationPath, location);
const additional = this.additionalLocations.get(logPath);
if (additional) {
this.disposeAndStopTracking(additional);
this.additionalLocations.delete(logPath);
}
}
}
getBaseLocation() {
return this.additionalLogLocationPath;
}
}
class AdditionalLogLocation extends Disposable {
constructor(private location: string) {
super(() => { /**/ });
}
async log(message: string, options = {} as LogOptions): Promise<void> {
if (options.trailingNewline === undefined) {
options.trailingNewline = true;
}
await fs.ensureFile(this.location);
await fs.appendFile(this.location, message + (options.trailingNewline ? '\n' : ''), {
encoding: 'utf8'
});
}
async dispose(): Promise<void> {
await fs.remove(this.location);
}
}
/** The global logger for the extension. */
@@ -46,7 +139,9 @@ export const logger = new OutputChannelLogger('CodeQL Extension Log');
export const queryServerLogger = new OutputChannelLogger('CodeQL Query Server');
/** The logger for messages from the language server. */
export const ideServerLogger = new OutputChannelLogger('CodeQL Language Server');
export const ideServerLogger = new OutputChannelLogger(
'CodeQL Language Server'
);
/** The logger for messages from tests. */
export const testLogger = new OutputChannelLogger('CodeQL Tests');

View File

@@ -216,19 +216,19 @@ export interface QlFileSet {
/**
* The files imported by the given file
*/
imports: { [key: string]: string[]; };
imports: { [key: string]: string[] };
/**
* An id of each file
*/
nodeNumbering: { [key: string]: number; };
nodeNumbering: { [key: string]: number };
/**
* The code for each file
*/
qlCode: { [key: string]: string; };
qlCode: { [key: string]: string };
/**
* The resolution of an import in each directory.
*/
resolvedDirImports: { [key: string]: { [key: string]: string; }; };
resolvedDirImports: { [key: string]: { [key: string]: string } };
}
/**
@@ -313,6 +313,7 @@ export type Severity = number;
* Severity of different messages. This namespace is intentionally not
* an enum, see "for the sake of extensibility" comment above.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Severity {
/**
* The message is a compilation error.
@@ -360,6 +361,7 @@ export type ResultColumnKind = number;
* The kind of a result column. This namespace is intentionally not an enum, see "for the sake of
* extensibility" comment above.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace ResultColumnKind {
/**
* A column of type `float`
@@ -635,7 +637,7 @@ export interface EvaluateQueriesParams {
useSequenceHint: boolean;
}
export type TemplateDefinitions = { [key: string]: TemplateSource; }
export type TemplateDefinitions = { [key: string]: TemplateSource }
/**
* A single query that should be run
@@ -748,7 +750,7 @@ export interface ResultSet {
/**
* The type returned when the evaluation is complete
*/
export interface EvaluationComplete { }
export type EvaluationComplete = {};
/**
* The result of a single query
@@ -778,6 +780,11 @@ export interface EvaluationResult {
* An error message if an error happened
*/
message?: string;
/**
* Full path to file with all log messages emitted while this query was active, if one exists
*/
logFileLocation?: string;
}
export type QueryResultType = number;
@@ -785,6 +792,7 @@ export type QueryResultType = number;
* The result of running a query. This namespace is intentionally not
* an enum, see "for the sake of extensibility" comment above.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace QueryResultType {
/**
* The query ran successfully
@@ -857,11 +865,11 @@ export interface WithProgressId<T> {
/**
* The main body
*/
body: T,
body: T;
/**
* The id used to report progress updates
*/
progressId: number
progressId: number;
}
export interface ProgressMessage {
@@ -930,7 +938,7 @@ export const runUpgrade = new rpc.RequestType<WithProgressId<RunUpgradeParams>,
* Request returned to the client to notify completion of a query.
* The full runQueries job is completed when all queries are acknowledged.
*/
export const completeQuery = new rpc.RequestType<EvaluationResult, Object, void, void>('evaluation/queryCompleted');
export const completeQuery = new rpc.RequestType<EvaluationResult, Record<string, any>, void, void>('evaluation/queryCompleted');
/**
* A notification that the progress has been changed.

View File

@@ -6,7 +6,7 @@ import { Discovery } from './discovery';
export interface QLPack {
name: string;
uri: Uri;
};
}
/**
* Service to discover all available QL packs in a workspace folder.

View File

@@ -64,7 +64,7 @@ export class QLTestDirectory extends QLTestNode {
private createChildDirectory(name: string): QLTestDirectory {
const existingChild = this._children.find((child) => child.name === name);
if (existingChild !== undefined) {
return <QLTestDirectory>existingChild;
return existingChild as QLTestDirectory;
}
else {
const newChild = new QLTestDirectory(path.join(this.path, name), name);
@@ -87,6 +87,7 @@ export class QLTestFile extends QLTestNode {
}
public finish(): void {
/**/
}
}

View File

@@ -4,6 +4,9 @@ import { ExtensionContext, window as Window } from 'vscode';
import { CompletedQuery } from './query-results';
import { QueryHistoryConfig } from './config';
import { QueryWithResults } from './run-queries';
import * as helpers from './helpers';
import { logger } from './logging';
import { URLSearchParams } from 'url';
/**
* query-history.ts
@@ -15,14 +18,37 @@ import { QueryWithResults } from './run-queries';
*/
export type QueryHistoryItemOptions = {
label?: string, // user-settable label
queryText?: string, // stored query for quick query
label?: string; // user-settable label
queryText?: string; // text of the selected file
isQuickQuery?: boolean;
}
const SHOW_QUERY_TEXT_MSG = `\
////////////////////////////////////////////////////////////////////////////////////
// This is the text of the entire query file when it was executed for this query //
// run. The text or dependent libraries may have changed since then. //
// //
// This buffer is readonly. To re-execute this query, you must open the original //
// query file. //
////////////////////////////////////////////////////////////////////////////////////
`;
const SHOW_QUERY_TEXT_QUICK_EVAL_MSG = `\
////////////////////////////////////////////////////////////////////////////////////
// This is the Quick Eval selection of the query file when it was executed for //
// this query run. The text or dependent libraries may have changed since then. //
// //
// This buffer is readonly. To re-execute this query, you must open the original //
// query file. //
////////////////////////////////////////////////////////////////////////////////////
`;
/**
* Path to icon to display next to a failed query history item.
*/
const FAILED_QUERY_HISTORY_ITEM_ICON: string = 'media/red-x.svg';
const FAILED_QUERY_HISTORY_ITEM_ICON = 'media/red-x.svg';
/**
* Tree data provider for the query history view.
@@ -122,7 +148,7 @@ export class QueryHistoryManager {
ctx: ExtensionContext;
treeView: vscode.TreeView<CompletedQuery>;
selectedCallback: ((item: CompletedQuery) => void) | undefined;
lastItemClick: { time: Date, item: CompletedQuery } | undefined;
lastItemClick: { time: Date; item: CompletedQuery } | undefined;
async invokeCallbackOn(queryHistoryItem: CompletedQuery) {
if (this.selectedCallback !== undefined) {
@@ -135,7 +161,7 @@ export class QueryHistoryManager {
const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.file(queryHistoryItem.query.program.queryPath));
const editor = await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One);
const queryText = queryHistoryItem.options.queryText;
if (queryText !== undefined) {
if (queryText !== undefined && queryHistoryItem.options.isQuickQuery) {
await editor.edit(edit => edit.replace(textDocument.validateRange(
new vscode.Range(0, 0, textDocument.lineCount, 0)), queryText)
);
@@ -144,6 +170,7 @@ export class QueryHistoryManager {
async handleRemoveHistoryItem(queryHistoryItem: CompletedQuery) {
this.treeDataProvider.remove(queryHistoryItem);
queryHistoryItem.dispose();
const current = this.treeDataProvider.getCurrent();
if (current !== undefined) {
this.treeView.reveal(current);
@@ -187,6 +214,64 @@ export class QueryHistoryManager {
}
}
async handleShowQueryLog(queryHistoryItem: CompletedQuery) {
if (queryHistoryItem.logFileLocation) {
const uri = vscode.Uri.file(queryHistoryItem.logFileLocation);
try {
await vscode.window.showTextDocument(uri, {
});
} catch (e) {
if (e.message.includes('Files above 50MB cannot be synchronized with extensions')) {
const res = await helpers.showBinaryChoiceDialog('File is too large to open in the editor, do you want to open it externally?');
if (res) {
try {
await vscode.commands.executeCommand('revealFileInOS', uri);
} catch (e) {
helpers.showAndLogErrorMessage(e.message);
}
}
} else {
helpers.showAndLogErrorMessage(`Could not open log file ${queryHistoryItem.logFileLocation}`);
logger.log(e.message);
logger.log(e.stack);
}
}
} else {
helpers.showAndLogWarningMessage('No log file available');
}
}
async handleShowQueryText(queryHistoryItem: CompletedQuery) {
try {
const queryName = queryHistoryItem.queryName.endsWith('.ql') ? queryHistoryItem.queryName : queryHistoryItem.queryName + '.ql';
const params = new URLSearchParams({
isQuickEval: String(!!queryHistoryItem.query.quickEvalPosition),
queryText: await this.getQueryText(queryHistoryItem)
});
const uri = vscode.Uri.parse(`codeql:${queryHistoryItem.query.queryID}-${queryName}?${params.toString()}`);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc, { preview: false });
} catch (e) {
helpers.showAndLogErrorMessage(e.message);
}
}
async getQueryText(queryHistoryItem: CompletedQuery): Promise<string> {
if (queryHistoryItem.options.queryText) {
return queryHistoryItem.options.queryText;
} else if (queryHistoryItem.query.quickEvalPosition) {
// capture all selected lines
const startLine = queryHistoryItem.query.quickEvalPosition.line;
const endLine = queryHistoryItem.query.quickEvalPosition.endLine;
const textDocument =
await vscode.workspace.openTextDocument(queryHistoryItem.query.quickEvalPosition.fileName);
return textDocument.getText(new vscode.Range(startLine - 1, 0, endLine, 0));
} else {
return '';
}
}
constructor(
ctx: ExtensionContext,
private queryHistoryConfigListener: QueryHistoryConfig,
@@ -208,12 +293,25 @@ export class QueryHistoryManager {
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.openQuery', this.handleOpenQuery));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.removeHistoryItem', this.handleRemoveHistoryItem.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.setLabel', this.handleSetLabel.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryLog', this.handleShowQueryLog.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.showQueryText', this.handleShowQueryText.bind(this)));
ctx.subscriptions.push(vscode.commands.registerCommand('codeQLQueryHistory.itemClicked', async (item) => {
return this.handleItemClicked(item);
}));
queryHistoryConfigListener.onDidChangeQueryHistoryConfiguration(() => {
this.treeDataProvider.refresh();
});
// displays query text in a read-only document
vscode.workspace.registerTextDocumentContentProvider('codeql', {
provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> {
const params = new URLSearchParams(uri.query)
return (
JSON.parse(params.get('isQuickEval') || '') ? SHOW_QUERY_TEXT_QUICK_EVAL_MSG : SHOW_QUERY_TEXT_MSG
) + params.get('queryText');
}
});
}
addQuery(info: QueryWithResults): CompletedQuery {

View File

@@ -14,7 +14,9 @@ export class CompletedQuery implements QueryWithResults {
readonly query: QueryInfo;
readonly result: messages.EvaluationResult;
readonly database: DatabaseInfo;
readonly logFileLocation?: string;
options: QueryHistoryItemOptions;
dispose: () => void;
/**
* Map from result set name to SortedResultSetInfo.
@@ -31,15 +33,18 @@ export class CompletedQuery implements QueryWithResults {
interpretedResultsSortState: InterpretedResultsSortState | undefined;
constructor(
evalaution: QueryWithResults,
evaluation: QueryWithResults,
public config: QueryHistoryConfig,
) {
this.query = evalaution.query;
this.result = evalaution.result;
this.database = evalaution.database;
this.query = evaluation.query;
this.result = evaluation.result;
this.database = evaluation.database;
this.logFileLocation = evaluation.logFileLocation;
this.options = evaluation.options;
this.dispose = evaluation.dispose;
this.time = new Date().toLocaleString();
this.sortedResultsInfo = new Map();
this.options = evalaution.options;
}
get databaseName(): string {

View File

@@ -1,4 +1,5 @@
import * as cp from 'child_process';
import * as path from 'path';
// Import from the specific module within `semmle-vscode-utils`, rather than via `index.ts`, because
// we avoid taking an accidental runtime dependency on `vscode` this way.
import { DisposableObject } from 'semmle-vscode-utils/out/disposable-object';
@@ -8,9 +9,10 @@ import * as cli from './cli';
import { QueryServerConfig } from './config';
import { Logger, ProgressReporter } from './logging';
import { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from './messages';
import * as messages from './messages';
type ServerOpts = {
logger: Logger
logger: Logger;
}
/** A running query server process and its associated message connection. */
@@ -25,7 +27,7 @@ class ServerProcess implements Disposable {
this.logger = logger;
}
dispose() {
dispose(): void {
this.logger.log('Stopping query server...');
this.connection.dispose();
this.child.stdin!.end();
@@ -53,6 +55,7 @@ export class QueryServerClient extends DisposableObject {
nextCallback: number;
nextProgress: number;
withProgressReporting: WithProgressReporting;
public activeQueryName: string | undefined;
constructor(readonly config: QueryServerConfig, readonly cliServer: cli.CodeQLCliServer, readonly opts: ServerOpts, withProgressReporting: WithProgressReporting) {
super();
@@ -70,10 +73,12 @@ export class QueryServerClient extends DisposableObject {
this.evaluationResultCallbacks = {};
}
get logger() { return this.opts.logger; }
get logger(): Logger {
return this.opts.logger;
}
/** Stops the query server by disposing of the current server process. */
private stopQueryServer() {
private stopQueryServer(): void {
if (this.serverProcess !== undefined) {
this.disposeAndStopTracking(this.serverProcess);
} else {
@@ -82,23 +87,23 @@ export class QueryServerClient extends DisposableObject {
}
/** Restarts the query server by disposing of the current server process and then starting a new one. */
async restartQueryServer() {
async restartQueryServer(): Promise<void> {
this.stopQueryServer();
await this.startQueryServer();
}
async showLog() {
showLog(): void {
this.logger.show();
}
/** Starts a new query server process, sending progress messages to the status bar. */
async startQueryServer() {
async startQueryServer(): Promise<void> {
// Use an arrow function to preserve the value of `this`.
return this.withProgressReporting((progress, _) => this.startQueryServerImpl(progress));
}
/** Starts a new query server process, sending progress messages to the given reporter. */
private async startQueryServerImpl(progressReporter: ProgressReporter) {
private async startQueryServerImpl(progressReporter: ProgressReporter): Promise<void> {
const ramArgs = await this.cliServer.resolveRam(this.config.queryMemoryMb, progressReporter);
const args = ['--threads', this.config.numThreads.toString()].concat(ramArgs);
if (this.config.debug) {
@@ -110,7 +115,10 @@ export class QueryServerClient extends DisposableObject {
['execute', 'query-server'],
args,
this.logger,
data => this.logger.logWithoutTrailingNewline(data.toString()),
data => this.logger.log(data.toString(), {
trailingNewline: false,
additionalLogLocation: this.activeQueryName
}),
undefined, // no listener for stdout
progressReporter
);
@@ -121,12 +129,16 @@ export class QueryServerClient extends DisposableObject {
this.logger.log(`No callback associated with run id ${res.runId}, continuing without executing any callback`);
}
else {
const baseLocation = this.logger.getBaseLocation();
if (baseLocation && this.activeQueryName) {
res.logFileLocation = path.join(baseLocation, this.activeQueryName);
}
this.evaluationResultCallbacks[res.runId](res);
}
return {};
})
connection.onNotification(progress, res => {
let callback = this.progressCallbacks[res.id];
const callback = this.progressCallbacks[res.id];
if (callback) {
callback(res);
}
@@ -148,7 +160,7 @@ export class QueryServerClient extends DisposableObject {
return id;
}
unRegisterCallback(id: number) {
unRegisterCallback(id: number): void {
delete this.evaluationResultCallbacks[id];
}
@@ -157,8 +169,10 @@ export class QueryServerClient extends DisposableObject {
}
async sendRequest<P, R, E, RO>(type: RequestType<WithProgressId<P>, R, E, RO>, parameter: P, token?: CancellationToken, progress?: (res: ProgressMessage) => void): Promise<R> {
let id = this.nextProgress++;
const id = this.nextProgress++;
this.progressCallbacks[id] = progress;
this.updateActiveQuery(type.method, parameter);
try {
if (this.serverProcess === undefined) {
throw new Error('No query server process found.');
@@ -168,4 +182,19 @@ export class QueryServerClient extends DisposableObject {
delete this.progressCallbacks[id];
}
}
/**
* Updates the active query every time there is a new request to compile.
* The active query is used to specify the side log.
*
* This isn't ideal because in situations where there are queries running
* in parallel, each query's log messages are interleaved. Fixing this
* properly will require a change in the query server.
*/
private updateActiveQuery(method: string, parameter: any): void {
if (method === messages.compileQuery.method) {
const queryPath = parameter?.queryToCheck?.queryPath || 'unknown';
this.activeQueryName = `query-${path.basename(queryPath)}-${this.nextProgress}.log`;
}
}
}

View File

@@ -1,5 +1,4 @@
import * as fs from 'fs-extra';
import * as glob from 'glob-promise';
import * as yaml from 'js-yaml';
import * as path from 'path';
import { ExtensionContext, window as Window, workspace, Uri } from 'vscode';
@@ -18,33 +17,6 @@ export function isQuickQueryPath(queryPath: string): boolean {
return path.basename(queryPath) === QUICK_QUERY_QUERY_NAME;
}
async function getQlPackFor(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
const qlpacks = await cliServer.resolveQlpacks(helpers.getOnDiskWorkspaceFolders());
const packs: { packDir: string | undefined, packName: string }[] =
Object.entries(qlpacks).map(([packName, dirs]) => {
if (dirs.length < 1) {
logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`);
return { packName, packDir: undefined };
}
if (dirs.length > 1) {
logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has more than one directory; arbitrarily choosing the first`);
}
return {
packName,
packDir: dirs[0]
}
});
for (const { packDir, packName } of packs) {
if (packDir !== undefined) {
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8'));
if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) {
return packName;
}
}
}
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
}
/**
* `getBaseText` heuristically returns an appropriate import statement
* prelude based on the filename of the dbscheme file given. TODO: add
@@ -60,7 +32,7 @@ function getBaseText(dbschemeBase: string) {
return 'select ""';
}
async function getQuickQueriesDir(ctx: ExtensionContext): Promise<string> {
function getQuickQueriesDir(ctx: ExtensionContext): string {
const storagePath = ctx.storagePath;
if (storagePath === undefined) {
throw new Error('Workspace storage path is undefined');
@@ -70,23 +42,26 @@ async function getQuickQueriesDir(ctx: ExtensionContext): Promise<string> {
return queriesPath;
}
/**
* Show a buffer the user can enter a simple query into.
*/
export async function displayQuickQuery(ctx: ExtensionContext, cliServer: CodeQLCliServer, databaseUI: DatabaseUI) {
try {
function updateQuickQueryDir(queriesDir: string, index: number, len: number) {
workspace.updateWorkspaceFolders(
index,
len,
{ uri: Uri.file(queriesDir), name: QUICK_QUERY_WORKSPACE_FOLDER_NAME }
);
}
try {
const workspaceFolders = workspace.workspaceFolders || [];
const queriesDir = await getQuickQueriesDir(ctx);
function updateQuickQueryDir(index: number, len: number) {
workspace.updateWorkspaceFolders(
index,
len,
{ uri: Uri.file(queriesDir), name: QUICK_QUERY_WORKSPACE_FOLDER_NAME }
);
}
// If there is already a quick query open, don't clobber it, just
// show it.
const existing = workspace.textDocuments.find(doc => path.basename(doc.uri.fsPath) === QUICK_QUERY_QUERY_NAME);
@@ -107,16 +82,16 @@ export async function displayQuickQuery(ctx: ExtensionContext, cliServer: CodeQL
if (workspace.workspaceFile === undefined) {
const makeMultiRoot = await helpers.showBinaryChoiceDialog('Quick query requires multiple folders in the workspace. Reload workspace as multi-folder workspace?');
if (makeMultiRoot) {
updateQuickQueryDir(workspaceFolders.length, 0);
updateQuickQueryDir(queriesDir, workspaceFolders.length, 0);
}
return;
}
const index = workspaceFolders.findIndex(folder => folder.name === QUICK_QUERY_WORKSPACE_FOLDER_NAME)
if (index === -1)
updateQuickQueryDir(workspaceFolders.length, 0);
updateQuickQueryDir(queriesDir, workspaceFolders.length, 0);
else
updateQuickQueryDir(index, 1);
updateQuickQueryDir(queriesDir, index, 1);
// We're going to infer which qlpack to use from the current database
const dbItem = await databaseUI.getDatabaseItem();
@@ -125,19 +100,7 @@ export async function displayQuickQuery(ctx: ExtensionContext, cliServer: CodeQL
}
const datasetFolder = await dbItem.getDatasetFolder(cliServer);
const dbschemes = await glob(path.join(datasetFolder, '*.dbscheme'))
if (dbschemes.length < 1) {
throw new Error(`Can't find dbscheme for current database in ${datasetFolder}`);
}
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 getQlPackFor(cliServer, dbscheme);
const { qlpack, dbscheme } = await helpers.resolveDatasetFolder(cliServer, datasetFolder);
const quickQueryQlpackYaml: any = {
name: "quick-query",
version: "1.0.0",

View File

@@ -38,12 +38,12 @@ export function getResult(sarif: sarif.Log, key: Result): sarif.Result | undefin
* Looks up a specific path in a result set.
*/
export function getPath(sarif: sarif.Log, key: Path): sarif.ThreadFlow | undefined {
let result = getResult(sarif, key);
const result = getResult(sarif, key);
if (result === undefined) return undefined;
let index = -1;
if (result.codeFlows === undefined) return undefined;
for (let codeFlows of result.codeFlows) {
for (let threadFlow of codeFlows.threadFlows) {
for (const codeFlows of result.codeFlows) {
for (const threadFlow of codeFlows.threadFlows) {
++index;
if (index == key.pathIndex)
return threadFlow;
@@ -56,7 +56,7 @@ export function getPath(sarif: sarif.Log, key: Path): sarif.ThreadFlow | undefin
* Looks up a specific path node in a result set.
*/
export function getPathNode(sarif: sarif.Log, key: PathNode): sarif.Location | undefined {
let path = getPath(sarif, key);
const path = getPath(sarif, key);
if (path === undefined) return undefined;
return path.locations[key.pathNodeIndex];
}
@@ -85,7 +85,7 @@ export function equalsNotUndefined(key1: PathNode | undefined, key2: PathNode |
*/
export function getAllPaths(result: sarif.Result): sarif.ThreadFlow[] {
if (result.codeFlows === undefined) return [];
let paths = [];
const paths = [];
for (const codeFlow of result.codeFlows) {
for (const threadFlow of codeFlow.threadFlows) {
paths.push(threadFlow);

View File

@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import { ErrorCodes, ResponseError } from 'vscode-languageclient';
import * as cli from './cli';
import * as config from './config';
import { DatabaseItem, getUpgradesDirectories } from './databases';
import * as helpers from './helpers';
import { DatabaseInfo, QueryMetadata, ResultsPaths } from './interface-types';
@@ -121,6 +122,9 @@ export class QueryInfo {
): Promise<messages.CompilationMessage[]> {
let compiled: messages.CheckQueryResult | undefined;
try {
const target = this.quickEvalPosition ? {
quickEval: { quickEvalPos: this.quickEvalPosition }
} : { query: {} };
const params: messages.CompileQueryParams = {
compilationOptions: {
computeNoLocationUrls: true,
@@ -136,10 +140,9 @@ export class QueryInfo {
},
queryToCheck: this.program,
resultPath: this.compiledQueryPath,
target: !!this.quickEvalPosition ? { quickEval: { quickEvalPos: this.quickEvalPosition } } : { query: {} }
target,
};
compiled = await helpers.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Compiling Query",
@@ -170,10 +173,13 @@ export interface QueryWithResults {
readonly result: messages.EvaluationResult;
readonly database: DatabaseInfo;
readonly options: QueryHistoryItemOptions;
readonly logFileLocation?: string;
readonly dispose: () => void;
}
export async function clearCacheInDatabase(qs: qsClient.QueryServerClient, dbItem: DatabaseItem):
Promise<messages.ClearCacheResult> {
export async function clearCacheInDatabase(
qs: qsClient.QueryServerClient, dbItem: DatabaseItem
): Promise<messages.ClearCacheResult> {
if (dbItem.contents === undefined) {
throw new Error('Can\'t clear the cache in an invalid database.');
}
@@ -233,8 +239,10 @@ async function getSelectedPosition(editor: vscode.TextEditor): Promise<messages.
// Convert from 0-based to 1-based line and column numbers.
return {
fileName: await convertToQlPath(editor.document.fileName),
line: pos.line + 1, column: pos.character + 1,
endLine: posEnd.line + 1, endColumn: posEnd.character + 1
line: pos.line + 1,
column: pos.character + 1,
endLine: posEnd.line + 1,
endColumn: posEnd.character + 1
};
}
@@ -253,7 +261,7 @@ async function checkDbschemeCompatibility(
if (query.dbItem.contents !== undefined && query.dbItem.contents.dbSchemeUri !== undefined) {
const { scripts, finalDbscheme } = await cliServer.resolveUpgrades(query.dbItem.contents.dbSchemeUri.fsPath, searchPath);
async function hash(filename: string): Promise<string> {
const hash = async function(filename: string): Promise<string> {
return crypto.createHash('sha256').update(await fs.readFile(filename)).digest('hex');
}
@@ -288,19 +296,39 @@ async function checkDbschemeCompatibility(
}
}
/** Prompts the user to save `document` if it has unsaved changes. */
async function promptUserToSaveChanges(document: vscode.TextDocument) {
/**
* Prompts the user to save `document` if it has unsaved changes.
* Returns true if we should save changes.
*/
async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<boolean> {
if (document.isDirty) {
// TODO: add 'always save' button which records preference in configuration
if (await helpers.showBinaryChoiceDialog('Query file has unsaved changes. Save now?')) {
await document.save();
if (config.AUTOSAVE_SETTING.getValue()) {
return true;
}
else {
const yesItem = { title: 'Yes', isCloseAffordance: false };
const alwaysItem = { title: 'Always Save', isCloseAffordance: false };
const noItem = { title: 'No', isCloseAffordance: true }
const message = 'Query file has unsaved changes. Save now?';
const chosenItem = await vscode.window.showInformationMessage(message, { modal: true }, yesItem, alwaysItem, noItem);
if (chosenItem === alwaysItem) {
await config.AUTOSAVE_SETTING.updateValue(true, vscode.ConfigurationTarget.Workspace);
return true;
}
if (chosenItem === yesItem) {
return true;
}
}
}
return false;
}
type SelectedQuery = {
queryPath: string,
quickEvalPosition?: messages.Position
queryPath: string;
quickEvalPosition?: messages.Position;
quickEvalText?: string;
};
/**
@@ -313,7 +341,7 @@ type SelectedQuery = {
* @param selectedResourceUri The selected resource when the command was run.
* @param quickEval Whether the command being run is `Quick Evaluation`.
*/
async function determineSelectedQuery(selectedResourceUri: vscode.Uri | undefined, quickEval: boolean): Promise<SelectedQuery> {
export async function determineSelectedQuery(selectedResourceUri: vscode.Uri | undefined, quickEval: boolean): Promise<SelectedQuery> {
const editor = vscode.window.activeTextEditor;
// Choose which QL file to use.
@@ -336,18 +364,28 @@ async function determineSelectedQuery(selectedResourceUri: vscode.Uri | undefine
}
const queryPath = queryUri.fsPath || '';
if (!queryPath.endsWith('.ql')) {
throw new Error('The selected resource is not a CodeQL query file; It should have the extension ".ql".');
if (quickEval) {
if (!(queryPath.endsWith('.ql') || queryPath.endsWith('.qll'))) {
throw new Error('The selected resource is not a CodeQL file; It should have the extension ".ql" or ".qll".');
}
}
else {
if (!(queryPath.endsWith('.ql'))) {
throw new Error('The selected resource is not a CodeQL query file; It should have the extension ".ql".');
}
}
// Whether we chose the file from the active editor or from a context menu,
// if the same file is open with unsaved changes in the active editor,
// then prompt the user to save it first.
if (editor !== undefined && editor.document.uri.fsPath === queryPath) {
await promptUserToSaveChanges(editor.document);
if (await promptUserToSaveChanges(editor.document)) {
editor.document.save();
}
}
let quickEvalPosition: messages.Position | undefined = undefined;
let quickEvalText: string | undefined = undefined;
if (quickEval) {
if (editor == undefined) {
throw new Error('Can\'t run quick evaluation without an active editor.');
@@ -358,9 +396,10 @@ async function determineSelectedQuery(selectedResourceUri: vscode.Uri | undefine
throw new Error('The selected resource for quick evaluation should match the active editor.');
}
quickEvalPosition = await getSelectedPosition(editor);
quickEvalText = editor.document.getText(editor.selection);
}
return { queryPath, quickEvalPosition };
return { queryPath, quickEvalPosition, quickEvalText };
}
export async function compileAndRunQueryAgainstDatabase(
@@ -377,12 +416,14 @@ export async function compileAndRunQueryAgainstDatabase(
}
// Determine which query to run, based on the selection and the active editor.
const { queryPath, quickEvalPosition } = await determineSelectedQuery(selectedQueryUri, quickEval);
const { queryPath, quickEvalPosition, quickEvalText } = await determineSelectedQuery(selectedQueryUri, quickEval);
// If this is quick query, store the query text
const historyItemOptions: QueryHistoryItemOptions = {};
if (isQuickQueryPath(queryPath)) {
historyItemOptions.queryText = await fs.readFile(queryPath, 'utf8');
historyItemOptions.queryText = await fs.readFile(queryPath, 'utf8');
historyItemOptions.isQuickQuery === isQuickQueryPath(queryPath);
if (quickEval) {
historyItemOptions.queryText = quickEvalText;
}
// Get the workspace folder paths.
@@ -450,7 +491,11 @@ export async function compileAndRunQueryAgainstDatabase(
name: db.name,
databaseUri: db.databaseUri.toString(true)
},
options: historyItemOptions
options: historyItemOptions,
logFileLocation: result.logFileLocation,
dispose: () => {
qs.logger.removeAdditionalLogLocation(result.logFileLocation);
}
};
} else {
// Error dialogs are limited in size and scrollability,
@@ -459,7 +504,7 @@ export async function compileAndRunQueryAgainstDatabase(
// However we don't show quick eval errors there so we need to display them anyway.
qs.logger.log(`Failed to compile query ${query.program.queryPath} against database scheme ${query.program.dbschemePath}:`);
let formattedMessages: string[] = [];
const formattedMessages: string[] = [];
for (const error of errors) {
const message = error.message || "[no error message available]";
@@ -486,7 +531,7 @@ function createSyntheticResult(
historyItemOptions: QueryHistoryItemOptions,
message: string,
resultType: number
) {
): QueryWithResults {
return {
query,
@@ -502,5 +547,6 @@ function createSyntheticResult(
databaseUri: db.databaseUri.toString(true)
},
options: historyItemOptions,
dispose: () => { /**/ },
};
}

View File

@@ -3,8 +3,8 @@ import * as path from "path"
import { LocationStyle, ResolvableLocationValue } from "semmle-bqrs";
export interface SarifLink {
dest: number
text: string
dest: number;
text: string;
}
@@ -15,7 +15,7 @@ type ParsedSarifLocation =
// doesn't really need to see. We ensure that `userVisibleFile` will not contain
// that, and is appropriate for display in the UI.
& { userVisibleFile: string }
| { t: 'NoLocation', hint: string };
| { t: 'NoLocation'; hint: string };
export type SarifMessageComponent = string | SarifLink
@@ -27,7 +27,7 @@ export function unescapeSarifText(message: string): string {
}
export function parseSarifPlainTextMessage(message: string): SarifMessageComponent[] {
let results: SarifMessageComponent[] = [];
const results: SarifMessageComponent[] = [];
// We want something like "[linkText](4)", except that "[" and "]" may be escaped. The lookbehind asserts
// that the initial [ is not escaped. Then we parse a link text with "[" and "]" escaped. Then we parse the numerical target.

View File

@@ -88,7 +88,7 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
new EventEmitter<TestLoadStartedEvent | TestLoadFinishedEvent>());
private readonly _testStates = this.push(
new EventEmitter<TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent |
TestEvent>());
TestEvent>());
private readonly _autorun = this.push(new EventEmitter<void>());
private runningTask?: vscode.CancellationTokenSource = undefined;
@@ -109,7 +109,7 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
}
public get testStates(): Event<TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent |
TestEvent> {
TestEvent> {
return this._testStates.event;
}
@@ -119,7 +119,7 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
}
private static createTestOrSuiteInfos(testNodes: readonly QLTestNode[]):
(TestSuiteInfo | TestInfo)[] {
(TestSuiteInfo | TestInfo)[] {
return testNodes.map((childNode) => {
return QLTestAdapter.createTestOrSuiteInfo(childNode);
@@ -149,7 +149,7 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
}
private static createTestSuiteInfo(testDirectory: QLTestDirectory, label: string):
TestSuiteInfo {
TestSuiteInfo {
return {
type: 'suite',
@@ -165,7 +165,7 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
}
private discoverTests(): void {
this._tests.fire(<TestLoadStartedEvent>{ type: 'started' });
this._tests.fire({ type: 'started' } as TestLoadStartedEvent);
const testDirectories = this.qlTestDiscovery.testDirectories;
const children = testDirectories.map(
@@ -178,10 +178,10 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
children
};
this._tests.fire(<TestLoadFinishedEvent>{
this._tests.fire({
type: 'finished',
suite: children.length > 0 ? testSuite : undefined
});
} as TestLoadFinishedEvent);
}
public async run(tests: string[]): Promise<void> {
@@ -194,17 +194,16 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
this.runningTask = this.track(new CancellationTokenSource());
this._testStates.fire(<TestRunStartedEvent>{ type: 'started', tests: tests });
const testAdapter = this;
this._testStates.fire({ type: 'started', tests: tests } as TestRunStartedEvent);
try {
await this.runTests(tests, this.runningTask.token);
}
catch (e) {
/**/
}
testAdapter._testStates.fire(<TestRunFinishedEvent>{ type: 'finished' });
testAdapter.clearTask();
this._testStates.fire({ type: 'finished' } as TestRunFinishedEvent);
this.clearTask();
}
private clearTask(): void {

View File

@@ -6,6 +6,8 @@ import { DisposableObject, UIService } from 'semmle-vscode-utils';
import { TestHub, TestController, TestAdapter, TestRunStartedEvent, TestRunFinishedEvent, TestEvent, TestSuiteEvent } from 'vscode-test-adapter-api';
import { QLTestAdapter, getExpectedFile, getActualFile } from './test-adapter';
type VSCodeTestEvent = TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | TestEvent;
/**
* Test event listener. Currently unused, but left in to keep the plumbing hooked up for future use.
*/
@@ -16,7 +18,8 @@ class QLTestListener extends DisposableObject {
this.push(adapter.testStates(this.onTestStatesEvent, this));
}
private onTestStatesEvent(_e: TestRunStartedEvent | TestRunFinishedEvent | TestSuiteEvent | TestEvent): void {
private onTestStatesEvent(_e: VSCodeTestEvent): void {
/**/
}
}

View File

@@ -19,8 +19,9 @@ const MAX_UPGRADE_MESSAGE_LINES = 10;
* Reports errors to both the user and the console.
* @returns the `UpgradeParams` needed to start the upgrade, if the upgrade is possible and was confirmed by the user, or `undefined` otherwise.
*/
async function checkAndConfirmDatabaseUpgrade(qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]):
Promise<messages.UpgradeParams | undefined> {
async function checkAndConfirmDatabaseUpgrade(
qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]
): Promise<messages.UpgradeParams | undefined> {
if (db.contents === undefined || db.contents.dbSchemeUri === undefined) {
helpers.showAndLogErrorMessage("Database is invalid, and cannot be upgraded.");
return;
@@ -80,7 +81,7 @@ async function checkAndConfirmDatabaseUpgrade(qs: qsClient.QueryServerClient, db
const showLogItem: vscode.MessageItem = { title: 'No, Show Changes', isCloseAffordance: true };
const yesItem = { title: 'Yes', isCloseAffordance: false };
const noItem = { title: 'No', isCloseAffordance: true }
let dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
const dialogOptions: vscode.MessageItem[] = [yesItem, noItem];
let messageLines = descriptionMessage.split('\n');
if (messageLines.length > MAX_UPGRADE_MESSAGE_LINES) {
@@ -110,8 +111,9 @@ async function checkAndConfirmDatabaseUpgrade(qs: qsClient.QueryServerClient, db
* First performs a dry-run and prompts the user to confirm the upgrade.
* Reports errors during compilation and evaluation of upgrades to the user.
*/
export async function upgradeDatabase(qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]):
Promise<messages.RunUpgradeResult | undefined> {
export async function upgradeDatabase(
qs: qsClient.QueryServerClient, db: DatabaseItem, targetDbScheme: vscode.Uri, upgradesDirectories: vscode.Uri[]
): Promise<messages.RunUpgradeResult | undefined> {
const upgradeParams = await checkAndConfirmDatabaseUpgrade(qs, db, targetDbScheme, upgradesDirectories);
if (upgradeParams === undefined) {
@@ -150,8 +152,9 @@ export async function upgradeDatabase(qs: qsClient.QueryServerClient, db: Databa
}
}
async function checkDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams):
Promise<messages.CheckUpgradeResult> {
async function checkDatabaseUpgrade(
qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams
): Promise<messages.CheckUpgradeResult> {
return helpers.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Checking for database upgrades",
@@ -159,8 +162,9 @@ async function checkDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradeParam
}, (progress, token) => qs.sendRequest(messages.checkUpgrade, upgradeParams, token, progress));
}
async function compileDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams):
Promise<messages.CompileUpgradeResult> {
async function compileDatabaseUpgrade(
qs: qsClient.QueryServerClient, upgradeParams: messages.UpgradeParams
): Promise<messages.CompileUpgradeResult> {
const params: messages.CompileUpgradeParams = {
upgrade: upgradeParams,
upgradeTempDir: upgradesTmpDir.name
@@ -173,8 +177,9 @@ async function compileDatabaseUpgrade(qs: qsClient.QueryServerClient, upgradePar
}, (progress, token) => qs.sendRequest(messages.compileUpgrade, params, token, progress));
}
async function runDatabaseUpgrade(qs: qsClient.QueryServerClient, db: DatabaseItem, upgrades: messages.CompiledUpgrades):
Promise<messages.RunUpgradeResult> {
async function runDatabaseUpgrade(
qs: qsClient.QueryServerClient, db: DatabaseItem, upgrades: messages.CompiledUpgrades
): Promise<messages.RunUpgradeResult> {
if (db.contents === undefined || db.contents.datasetUri === undefined) {
throw new Error('Can\'t upgrade an invalid database.');

View File

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

View File

@@ -84,7 +84,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
function renderRelatedLocations(msg: string, relatedLocations: Sarif.Location[]): JSX.Element[] {
const relatedLocationsById: { [k: string]: Sarif.Location } = {};
for (let loc of relatedLocations) {
for (const loc of relatedLocations) {
relatedLocationsById[loc.id!] = loc;
}
@@ -167,7 +167,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
const text = result.message.text || '[no text]';
const msg: JSX.Element[] =
result.relatedLocations === undefined ?
[<span>{text}</span>] :
[<span key="0">{text}</span>] :
renderRelatedLocations(text, result.relatedLocations);
const currentResultExpanded = this.state.expanded[expansionIndex];
@@ -239,7 +239,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
const additionalMsg = step.location !== undefined ?
renderSarifLocation(step.location, pathNodeKey) :
'';
let isSelected = Keys.equalsNotUndefined(this.state.selectedPathNode, pathNodeKey);
const isSelected = Keys.equalsNotUndefined(this.state.selectedPathNode, pathNodeKey);
const stepIndex = pathNodeIndex + 1; // Convert to 1-based
const zebraIndex = resultIndex + stepIndex;
rows.push(
@@ -271,23 +271,23 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
private handleNavigationEvent(event: NavigationEvent) {
this.setState(prevState => {
let { selectedPathNode } = prevState;
const { selectedPathNode } = prevState;
if (selectedPathNode === undefined) return prevState;
let path = Keys.getPath(this.props.resultSet.sarif, selectedPathNode);
const path = Keys.getPath(this.props.resultSet.sarif, selectedPathNode);
if (path === undefined) return prevState;
let nextIndex = selectedPathNode.pathNodeIndex + event.direction;
const nextIndex = selectedPathNode.pathNodeIndex + event.direction;
if (nextIndex < 0 || nextIndex >= path.locations.length) return prevState;
let sarifLoc = path.locations[nextIndex].location;
const sarifLoc = path.locations[nextIndex].location;
if (sarifLoc === undefined) return prevState;
let loc = parseSarifLocation(sarifLoc, this.props.resultSet.sourceLocationPrefix);
const loc = parseSarifLocation(sarifLoc, this.props.resultSet.sourceLocationPrefix);
if (loc.t === 'NoLocation') return prevState;
jumpToLocation(loc, this.props.databaseUri);
let newSelection = { ...selectedPathNode, pathNodeIndex: nextIndex };
const newSelection = { ...selectedPathNode, pathNodeIndex: nextIndex };
return { ...prevState, selectedPathNode: newSelection };
});
}

View File

@@ -4,22 +4,22 @@ export type EventHandler<T> = (event: T) => void;
* A set of listeners for events of type `T`.
*/
export class EventHandlers<T> {
private handlers: EventHandler<T>[] = [];
private handlers: EventHandler<T>[] = [];
public addListener(handler: EventHandler<T>) {
this.handlers.push(handler);
}
public addListener(handler: EventHandler<T>) {
this.handlers.push(handler);
}
public removeListener(handler: EventHandler<T>) {
let index = this.handlers.indexOf(handler);
if (index !== -1) {
this.handlers.splice(index, 1);
}
public removeListener(handler: EventHandler<T>) {
const index = this.handlers.indexOf(handler);
if (index !== -1) {
this.handlers.splice(index, 1);
}
}
public fire(event: T) {
for (let handler of this.handlers) {
handler(event);
}
public fire(event: T) {
for (const handler of this.handlers) {
handler(event);
}
}
}

View File

@@ -63,7 +63,7 @@ async function* getChunkIterator(response: Response): AsyncIterableIterator<Uint
}
function translatePrimitiveValue(value: PrimitiveColumnValue, type: PrimitiveTypeKind):
ResultValue {
ResultValue {
switch (type) {
case 'i':

View File

@@ -1,6 +1,12 @@
import * as assert from 'assert';
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import 'mocha';
import * as path from 'path';
import * as vscode from 'vscode';
import * as determiningSelectedQueryTest from './determining-selected-query-test';
chai.use(chaiAsPromised);
describe('launching with a minimal workspace', async () => {
const ext = vscode.extensions.getExtension('GitHub.vscode-codeql');
@@ -24,3 +30,5 @@ describe('launching with a minimal workspace', async () => {
}, 1000);
});
});
determiningSelectedQueryTest.run();

View File

@@ -0,0 +1,44 @@
import { expect } from 'chai';
import * as path from 'path';
import * as vscode from 'vscode';
import { Uri } from 'vscode';
import { determineSelectedQuery } from '../../run-queries';
async function showQlDocument(name: string): Promise<vscode.TextDocument> {
const folderPath = vscode.workspace.workspaceFolders![0].uri.fsPath;
const documentPath = path.resolve(folderPath, name);
const document = await vscode.workspace.openTextDocument(documentPath);
await vscode.window.showTextDocument(document!);
return document;
}
export function run() {
describe('Determining selected query', async () => {
it('should allow ql files to be queried', async () => {
const q = await determineSelectedQuery(Uri.parse('file:///tmp/queryname.ql'), false);
expect(q.queryPath).to.equal(path.join('/', 'tmp', 'queryname.ql'));
expect(q.quickEvalPosition).to.equal(undefined);
});
it('should allow ql files to be quick-evaled', async () => {
const doc = await showQlDocument('query.ql');
const q = await determineSelectedQuery(doc.uri, true);
expect(q.queryPath).to.satisfy((p: string) => p.endsWith(path.join('ql-vscode', 'test', 'data', 'query.ql')));
});
it('should allow qll files to be quick-evaled', async () => {
const doc = await showQlDocument('library.qll');
const q = await determineSelectedQuery(doc.uri, true);
expect(q.queryPath).to.satisfy((p: string) => p.endsWith(path.join('ql-vscode', 'test', 'data', 'library.qll')));
});
it('should reject non-ql files when running a query', async () => {
await expect(determineSelectedQuery(Uri.parse('file:///tmp/queryname.txt'), false)).to.be.rejectedWith(Error, 'The selected resource is not a CodeQL query file');
await expect(determineSelectedQuery(Uri.parse('file:///tmp/queryname.qll'), false)).to.be.rejectedWith(Error, 'The selected resource is not a CodeQL query file');
});
it('should reject non-ql[l] files when running a quick eval', async () => {
await expect(determineSelectedQuery(Uri.parse('file:///tmp/queryname.txt'), true)).to.be.rejectedWith(Error, 'The selected resource is not a CodeQL file');
});
});
}

View File

@@ -15,7 +15,7 @@ describe("archive filesystem provider", () => {
});
describe('source archive uri encoding', function() {
const testCases: { name: string, input: ZipFileReference }[] = [
const testCases: { name: string; input: ZipFileReference }[] = [
{
name: 'mixed case and unicode',
input: { sourceArchiveZipPath: "/I-\u2665-codeql.zip", pathWithinSourceArchive: "/foo/bar" }

View File

@@ -1,8 +1,18 @@
import { expect } from "chai";
import * as chai from "chai";
import * as path from "path";
import * as fetch from "node-fetch";
import 'chai/register-should';
import * as sinonChai from 'sinon-chai';
import * as sinon from 'sinon';
import * as pq from "proxyquire";
import "mocha";
import { Version } from "../../cli-version";
import { GithubRelease, GithubReleaseAsset, ReleasesApiConsumer, versionCompare } from "../../distribution"
import { GithubRelease, GithubReleaseAsset, ReleasesApiConsumer, versionCompare } from "../../distribution";
const proxyquire = pq.noPreserveCache();
chai.use(sinonChai);
const expect = chai.expect;
describe("Releases API consumer", () => {
const owner = "someowner";
@@ -176,3 +186,146 @@ describe("Release version ordering", () => {
expect(versionCompare(createVersion(2, 1, 0, "alpha.1", "abcdef0"), createVersion(2, 1, 0, "alpha.1", "bcdef01"))).to.equal(0);
});
});
describe('Launcher path', () => {
const pathToCmd = `abc${path.sep}codeql.cmd`;
const pathToExe = `abc${path.sep}codeql.exe`;
let sandbox: sinon.SinonSandbox;
let warnSpy: sinon.SinonSpy;
let errorSpy: sinon.SinonSpy;
let logSpy: sinon.SinonSpy;
let fsSpy: sinon.SinonSpy;
let platformSpy: sinon.SinonSpy;
let getExecutableFromDirectory: Function;
let launcherThatExists = '';
beforeEach(() => {
getExecutableFromDirectory = createModule().getExecutableFromDirectory;
});
beforeEach(() => {
sandbox.restore();
});
it('should not warn with proper launcher name', async () => {
launcherThatExists = 'codeql.exe';
const result = await getExecutableFromDirectory('abc');
expect(fsSpy).to.have.been.calledWith(pathToExe);
// correct launcher has been found, so alternate one not looked for
expect(fsSpy).not.to.have.been.calledWith(pathToCmd);
// no warning message
expect(warnSpy).not.to.have.been.calledWith(sinon.match.string);
// No log message
expect(logSpy).not.to.have.been.calledWith(sinon.match.string);
expect(result).to.equal(pathToExe);
});
it('should warn when using a hard-coded deprecated launcher name', async () => {
launcherThatExists = 'codeql.cmd';
path.sep;
const result = await getExecutableFromDirectory('abc');
expect(fsSpy).to.have.been.calledWith(pathToExe);
expect(fsSpy).to.have.been.calledWith(pathToCmd);
// Should have opened a warning message
expect(warnSpy).to.have.been.calledWith(sinon.match.string);
// No log message
expect(logSpy).not.to.have.been.calledWith(sinon.match.string);
expect(result).to.equal(pathToCmd);
});
it('should avoid warn when no launcher is found', async () => {
launcherThatExists = 'xxx';
const result = await getExecutableFromDirectory('abc', false);
expect(fsSpy).to.have.been.calledWith(pathToExe);
expect(fsSpy).to.have.been.calledWith(pathToCmd);
// no warning message
expect(warnSpy).not.to.have.been.calledWith(sinon.match.string);
// log message sent out
expect(logSpy).not.to.have.been.calledWith(sinon.match.string);
expect(result).to.equal(undefined);
});
it('should warn when no launcher is found', async () => {
launcherThatExists = 'xxx';
const result = await getExecutableFromDirectory('abc', true);
expect(fsSpy).to.have.been.calledWith(pathToExe);
expect(fsSpy).to.have.been.calledWith(pathToCmd);
// no warning message
expect(warnSpy).not.to.have.been.calledWith(sinon.match.string);
// log message sent out
expect(logSpy).to.have.been.calledWith(sinon.match.string);
expect(result).to.equal(undefined);
});
it('should not warn when deprecated launcher is used, but no new launcher is available', async () => {
const manager = new (createModule().DistributionManager)(undefined as any, { customCodeQlPath: pathToCmd } as any, undefined as any);
launcherThatExists = 'codeql.cmd';
const result = await manager.getCodeQlPathWithoutVersionCheck();
expect(result).to.equal(pathToCmd);
// no warning or error message
expect(warnSpy).to.have.callCount(0);
expect(errorSpy).to.have.callCount(0);
});
it('should warn when deprecated launcher is used, and new launcher is available', async () => {
const manager = new (createModule().DistributionManager)(undefined as any, { customCodeQlPath: pathToCmd } as any, undefined as any);
launcherThatExists = ''; // pretend both launchers exist
const result = await manager.getCodeQlPathWithoutVersionCheck();
expect(result).to.equal(pathToCmd);
// has warning message
expect(warnSpy).to.have.callCount(1);
expect(errorSpy).to.have.callCount(0);
});
it('should warn when launcher path is incorrect', async () => {
const manager = new (createModule().DistributionManager)(undefined as any, { customCodeQlPath: pathToCmd } as any, undefined as any);
launcherThatExists = 'xxx'; // pretend neither launcher exists
const result = await manager.getCodeQlPathWithoutVersionCheck();
expect(result).to.equal(undefined);
// no error message
expect(warnSpy).to.have.callCount(0);
expect(errorSpy).to.have.callCount(1);
});
function createModule() {
sandbox = sinon.createSandbox();
warnSpy = sandbox.spy();
errorSpy = sandbox.spy();
logSpy = sandbox.spy();
// pretend that only the .cmd file exists
fsSpy = sandbox.stub().callsFake(arg => arg.endsWith(launcherThatExists) ? true : false);
platformSpy = sandbox.stub().returns('win32');
return proxyquire('../../distribution', {
'./helpers': {
showAndLogWarningMessage: warnSpy,
showAndLogErrorMessage: errorSpy
},
'./logging': {
'logger': {
log: logSpy
}
},
'fs-extra': {
pathExists: fsSpy
},
os: {
platform: platformSpy
}
});
}
});

View File

@@ -87,16 +87,16 @@ describe("Invocation rate limiter", () => {
});
class MockExtensionContext implements ExtensionContext {
subscriptions: { dispose(): unknown; }[] = [];
subscriptions: { dispose(): unknown }[] = [];
workspaceState: Memento = new MockMemento();
globalState: Memento = new MockMemento();
extensionPath: string = "";
extensionPath = "";
asAbsolutePath(_relativePath: string): string {
throw new Error("Method not implemented.");
}
storagePath: string = "";
globalStoragePath: string = "";
logPath: string = "";
storagePath = "";
globalStoragePath = "";
logPath = "";
}
class MockMemento implements Memento {

View File

@@ -5,9 +5,9 @@ import { runTests } from 'vscode-test';
// would simply use instead, but for the fact that it doesn't export
// it.
type Suite = {
extensionDevelopmentPath: string,
extensionTestsPath: string,
launchArgs: string[]
extensionDevelopmentPath: string;
extensionTestsPath: string;
launchArgs: string[];
};
/**

View File

@@ -422,6 +422,11 @@ repository:
keyword: 'true'
name: constant.language.boolean.true.ql
unique:
match:
keyword: 'unique'
name: keyword.aggregate.unique.ql
where:
match:
keyword: 'where'
@@ -478,6 +483,8 @@ repository:
- include: '#then'
- include: '#this'
- include: '#true'
# `unique` is not really a keyword, but we'll highlight it as if it is.
- include: '#unique'
- include: '#where'
# A keyword that can be the first token of a predicate declaration.

View File

@@ -1,5 +1,8 @@
module.exports = {
env: {
mocha: true
}
},
parserOptions: {
project: './test/tsconfig.json',
},
}

View File

@@ -0,0 +1,3 @@
predicate foo() {
1 == 1
}

View File

@@ -0,0 +1,154 @@
import 'chai/register-should';
import * as chai from 'chai';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as tmp from 'tmp';
import 'mocha';
import * as sinonChai from 'sinon-chai';
import * as sinon from 'sinon';
import * as pq from 'proxyquire';
const proxyquire = pq.noPreserveCache().noCallThru();
chai.use(sinonChai);
const expect = chai.expect;
describe('OutputChannelLogger tests', () => {
let OutputChannelLogger;
const tempFolders: Record<string, tmp.DirResult> = {};
let logger: any;
let mockOutputChannel: Record<string, sinon.SinonStub>;
beforeEach(async () => {
OutputChannelLogger = createModule().OutputChannelLogger;
tempFolders.globalStoragePath = tmp.dirSync({ prefix: 'logging-tests-global' });
tempFolders.storagePath = tmp.dirSync({ prefix: 'logging-tests-workspace' });
logger = new OutputChannelLogger('test-logger');
});
afterEach(() => {
tempFolders.globalStoragePath.removeCallback();
tempFolders.storagePath.removeCallback();
});
it('should log to the output channel', async () => {
await logger.log('xxx');
expect(mockOutputChannel.appendLine).to.have.been.calledWith('xxx');
expect(mockOutputChannel.append).not.to.have.been.calledWith('xxx');
await logger.log('yyy', { trailingNewline: false });
expect(mockOutputChannel.appendLine).not.to.have.been.calledWith('yyy');
expect(mockOutputChannel.append).to.have.been.calledWith('yyy');
// additionalLogLocation ignored since not initialized
await logger.log('zzz', { additionalLogLocation: 'hucairz' });
// should not have created any side logs
expect(fs.readdirSync(tempFolders.globalStoragePath.name).length).to.equal(0);
expect(fs.readdirSync(tempFolders.storagePath.name).length).to.equal(0);
});
it('should create a side log in the workspace area', async () => {
await sideLogTest('storagePath', 'globalStoragePath');
});
it('should create a side log in the global area', async () => {
await sideLogTest('globalStoragePath', 'storagePath');
});
async function sideLogTest(expectedArea: string, otherArea: string): Promise<void> {
logger.init({
[expectedArea]: tempFolders[expectedArea].name,
[otherArea]: undefined
});
await logger.log('xxx', { additionalLogLocation: 'first' });
await logger.log('yyy', { additionalLogLocation: 'second' });
await logger.log('zzz', { additionalLogLocation: 'first', trailingNewline: false });
await logger.log('aaa');
// expect 2 side logs
const testLoggerFolder = path.join(tempFolders[expectedArea].name, 'test-logger');
expect(fs.readdirSync(testLoggerFolder).length).to.equal(2);
expect(fs.readdirSync(tempFolders[otherArea].name).length).to.equal(0);
// contents
expect(fs.readFileSync(path.join(testLoggerFolder, 'first'), 'utf8')).to.equal('xxx\nzzz');
expect(fs.readFileSync(path.join(testLoggerFolder, 'second'), 'utf8')).to.equal('yyy\n');
}
it('should delete side logs on dispose', async () => {
logger.init({
storagePath: tempFolders.storagePath.name
});
await logger.log('xxx', { additionalLogLocation: 'first' });
await logger.log('yyy', { additionalLogLocation: 'second' });
const testLoggerFolder = path.join(tempFolders.storagePath.name, 'test-logger');
expect(fs.readdirSync(testLoggerFolder).length).to.equal(2);
await logger.dispose();
// need to wait for disposable-object to dispose
await waitABit();
expect(fs.readdirSync(testLoggerFolder).length).to.equal(0);
expect(mockOutputChannel.dispose).to.have.been.calledWith();
});
it('should remove an additional log location', async () => {
logger.init({
storagePath: tempFolders.storagePath.name
});
await logger.log('xxx', { additionalLogLocation: 'first' });
await logger.log('yyy', { additionalLogLocation: 'second' });
const testLoggerFolder = path.join(tempFolders.storagePath.name, 'test-logger');
expect(fs.readdirSync(testLoggerFolder).length).to.equal(2);
await logger.removeAdditionalLogLocation('first');
// need to wait for disposable-object to dispose
await waitABit();
expect(fs.readdirSync(testLoggerFolder).length).to.equal(1);
expect(fs.readFileSync(path.join(testLoggerFolder, 'second'), 'utf8')).to.equal('yyy\n');
});
it('should delete an existing folder on init', async () => {
fs.createFileSync(path.join(tempFolders.storagePath.name, 'test-logger', 'xxx'));
logger.init({
storagePath: tempFolders.storagePath.name
});
// should be empty dir
const testLoggerFolder = path.join(tempFolders.storagePath.name, 'test-logger');
expect(fs.readdirSync(testLoggerFolder).length).to.equal(1);
});
it('should show the output channel', () => {
logger.show(true);
expect(mockOutputChannel.show).to.have.been.calledWith(true);
});
function createModule(): any {
mockOutputChannel = {
append: sinon.stub(),
appendLine: sinon.stub(),
show: sinon.stub(),
dispose: sinon.stub(),
};
return proxyquire('../../src/logging', {
vscode: {
window: {
createOutputChannel: () => mockOutputChannel
},
Disposable: function() {
/**/
},
'@noCallThru': true,
'@global': true
}
});
}
function waitABit(ms = 50): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
});

View File

@@ -31,31 +31,31 @@ class Checkpoint<T> {
private promise: Promise<T>;
constructor() {
this.res = () => { };
this.rej = () => { };
this.res = () => { /**/ };
this.rej = () => { /**/ };
this.promise = new Promise((res, rej) => { this.res = res; this.rej = rej; })
}
async done() {
async done(): Promise<T> {
return this.promise;
}
async resolve() {
(this.res)();
async resolve(): Promise<void> {
await (this.res)();
}
async reject(e: Error) {
(this.rej)(e);
async reject(e: Error): Promise<void> {
await (this.rej)(e);
}
}
type ResultSets = {
[name: string]: bqrs.ColumnValue[][]
[name: string]: bqrs.ColumnValue[][];
}
type QueryTestCase = {
queryPath: string,
expectedResultSets: ResultSets
queryPath: string;
expectedResultSets: ResultSets;
}
// Test cases: queries to run and their expected results.
@@ -105,9 +105,10 @@ describe('using the query server', function() {
report: (v: { message: string }) => console.log(`progress reporter says ${v.message}`)
};
const logger: Logger = {
log: (s: string) => console.log('logger says', s),
logWithoutTrailingNewline: (s: string) => console.log('logger says', s),
show: () => { },
log: async (s: string) => console.log('logger says', s),
show: () => { /**/ },
removeAdditionalLogLocation: async () => { /**/ },
getBaseLocation: () => ''
};
cliServer = new cli.CodeQLCliServer({
async getCodeQlPathWithoutVersionCheck(): Promise<string | undefined> {
@@ -161,7 +162,7 @@ describe('using the query server', function() {
resultPath: COMPILED_QUERY_PATH,
target: { query: {} }
};
const result = await qs.sendRequest(messages.compileQuery, params, token, () => { });
const result = await qs.sendRequest(messages.compileQuery, params, token, () => { /**/ });
expect(result.messages!.length).to.equal(0);
compilationSucceeded.resolve();
}
@@ -194,7 +195,7 @@ describe('using the query server', function() {
stopOnError: false,
useSequenceHint: false
};
await qs.sendRequest(messages.runQueries, params, token, () => { });
await qs.sendRequest(messages.runQueries, params, token, () => { /**/ });
}
catch (e) {
evaluationSucceeded.reject(e);

View File

@@ -0,0 +1,8 @@
{
"include": [
"**/*.ts"
],
"compilerOptions": {
"noEmit": true
}
}

1
tsconfig.json Normal file
View File

@@ -0,0 +1 @@
{}