Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8efb060031 | ||
|
|
e242a8fbeb | ||
|
|
ee591e802f | ||
|
|
7df8905aa0 | ||
|
|
23b1c00179 | ||
|
|
701804b6a4 | ||
|
|
66665bf25e | ||
|
|
1c6b4a6d1e | ||
|
|
28be98411d | ||
|
|
5592a77963 | ||
|
|
a6cd08fb0b | ||
|
|
881c909540 | ||
|
|
f5e3af02e4 | ||
|
|
3eca4f6734 | ||
|
|
596ccdb722 | ||
|
|
2aeda002fa | ||
|
|
27623f3325 | ||
|
|
f3df3b9f3e | ||
|
|
5850ed3288 | ||
|
|
a2f8c85359 | ||
|
|
62d9efc4ee | ||
|
|
00026a7727 | ||
|
|
c292f58e20 | ||
|
|
6f935ae6e4 | ||
|
|
1fb65cd7e9 | ||
|
|
21500f0a5b | ||
|
|
efcf9815f0 | ||
|
|
f8635f41a5 | ||
|
|
e4df717d2b | ||
|
|
9ea4b3936a | ||
|
|
e5305ab4b5 | ||
|
|
c2c86aed0a | ||
|
|
2df512f018 | ||
|
|
ba3381fbf9 | ||
|
|
869029b856 | ||
|
|
b3ad1d6814 | ||
|
|
130d3c09e3 | ||
|
|
bb28dafc43 | ||
|
|
db6aadbf93 | ||
|
|
d97c8e864d | ||
|
|
d8a6368e60 | ||
|
|
76d6ab4e81 | ||
|
|
bdcabae60e |
2
.github/pull_request_template.md
vendored
@@ -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.
|
||||
|
||||
6
.github/workflows/main.yml
vendored
@@ -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
|
||||
|
||||
|
||||
152
common/config/rush/pnpm-lock.yaml
generated
@@ -44,6 +44,7 @@ dependencies:
|
||||
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
|
||||
@@ -101,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
|
||||
@@ -1036,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
|
||||
@@ -1773,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:
|
||||
@@ -2079,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
|
||||
@@ -2323,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
|
||||
@@ -3397,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:
|
||||
@@ -3630,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
|
||||
@@ -3765,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:
|
||||
@@ -4607,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
|
||||
@@ -4642,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
|
||||
@@ -5399,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
|
||||
@@ -5408,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:
|
||||
@@ -5715,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:
|
||||
@@ -5983,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
|
||||
@@ -6992,6 +7134,12 @@ packages:
|
||||
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:
|
||||
@@ -7256,6 +7404,7 @@ packages:
|
||||
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
|
||||
@@ -7294,7 +7443,7 @@ packages:
|
||||
dev: false
|
||||
name: '@rush-temp/vscode-codeql'
|
||||
resolution:
|
||||
integrity: sha512-PvC3L2Tp+VYm+hMzTgXfdBJPLJopSQpVsT8Ym7kdIxZj/cyTWzO3A+n7HnrH5q/B4DJSRfiDwjp73GwjGhbteQ==
|
||||
integrity: sha512-R6CO9tHF5naHATAvu28jP69lPvY3WvU2zeF41QZMU5OIieppNnoWEAlLjcRLScZxCtA70356Jzk90hK/LHvMpA==
|
||||
tarball: 'file:projects/vscode-codeql.tgz'
|
||||
version: 0.0.0
|
||||
registry: ''
|
||||
@@ -7344,6 +7493,7 @@ specifiers:
|
||||
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
|
||||
|
||||
@@ -27,4 +27,4 @@
|
||||
"../../test",
|
||||
"../../**/view"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
},
|
||||
project: ['tsconfig.json', './src/**/tsconfig.json'],
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
@@ -33,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
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# 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.
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
17
extensions/ql-vscode/media/dark/sort-alpha.svg
Normal 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 |
3
extensions/ql-vscode/media/dark/sort-date.svg
Normal 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 |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
17
extensions/ql-vscode/media/light/sort-alpha.svg
Normal 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 |
3
extensions/ql-vscode/media/light/sort-date.svg
Normal 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 |
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.1.1",
|
||||
"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": [
|
||||
@@ -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"
|
||||
@@ -212,6 +242,10 @@
|
||||
"command": "codeQLQueryHistory.showQueryLog",
|
||||
"title": "Show Query Log"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryText",
|
||||
"title": "Show Query Text"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryResults.nextPathStep",
|
||||
"title": "CodeQL: Show Next Step on Path"
|
||||
@@ -239,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",
|
||||
@@ -281,6 +325,11 @@
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryText",
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLTests.showOutputDifferences",
|
||||
"group": "qltest@1",
|
||||
@@ -321,6 +370,14 @@
|
||||
"command": "codeQLDatabases.setCurrentDatabase",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.sortByName",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.sortByDateAdded",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.removeDatabase",
|
||||
"when": "false"
|
||||
@@ -341,6 +398,10 @@
|
||||
"command": "codeQLQueryHistory.showQueryLog",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryText",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"when": "false"
|
||||
@@ -389,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",
|
||||
@@ -465,6 +526,7 @@
|
||||
"sinon-chai": "~3.5.0",
|
||||
"@types/sinon-chai": "~3.2.3",
|
||||
"proxyquire": "~2.1.3",
|
||||
"@types/proxyquire": "~1.3.28"
|
||||
"@types/proxyquire": "~1.3.28",
|
||||
"eslint-plugin-react": "~7.19.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
@@ -614,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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,6 +78,7 @@ 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];
|
||||
|
||||
@@ -15,8 +15,8 @@ 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);
|
||||
}
|
||||
|
||||
@@ -35,10 +35,12 @@ 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 {
|
||||
@@ -209,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;
|
||||
/**
|
||||
@@ -292,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;
|
||||
}
|
||||
@@ -430,21 +442,19 @@ class DatabaseItemImpl implements DatabaseItem {
|
||||
*/
|
||||
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();
|
||||
}, timeoutMs);
|
||||
const disposable = event(e => {
|
||||
res(e);
|
||||
dispose();
|
||||
});
|
||||
function dispose() {
|
||||
clearTimeout(timeout);
|
||||
disposable.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -468,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 || {};
|
||||
@@ -478,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);
|
||||
@@ -528,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;
|
||||
@@ -540,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) => {
|
||||
@@ -613,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();
|
||||
|
||||
203
extensions/ql-vscode/src/definitions.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,13 @@ export class DistributionManager implements DistributionProvider {
|
||||
"that a CodeQL executable exists at the specified path or remove the setting.");
|
||||
return undefined;
|
||||
}
|
||||
if (deprecatedCodeQlLauncherName() && this._config.customCodeQlPath.endsWith(deprecatedCodeQlLauncherName()!)) {
|
||||
|
||||
// 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;
|
||||
@@ -173,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>;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -337,6 +335,17 @@ 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 {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -244,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
|
||||
* 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> {
|
||||
async log(message: string, options = {} as LogOptions): Promise<void> {
|
||||
if (options.trailingNewline === undefined) {
|
||||
options.trailingNewline = true;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ class AdditionalLogLocation extends Disposable {
|
||||
super(() => { /**/ });
|
||||
}
|
||||
|
||||
async log(message: string, options = { } as LogOptions): Promise<void> {
|
||||
async log(message: string, options = {} as LogOptions): Promise<void> {
|
||||
if (options.trailingNewline === undefined) {
|
||||
options.trailingNewline = true;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
/**/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
@@ -18,9 +19,32 @@ import { logger } from './logging';
|
||||
|
||||
export type QueryHistoryItemOptions = {
|
||||
label?: string; // user-settable label
|
||||
queryText?: string; // stored query for quick query
|
||||
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.
|
||||
*/
|
||||
@@ -137,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)
|
||||
);
|
||||
@@ -192,13 +216,13 @@ export class QueryHistoryManager {
|
||||
|
||||
async handleShowQueryLog(queryHistoryItem: CompletedQuery) {
|
||||
if (queryHistoryItem.logFileLocation) {
|
||||
const uri = vscode.Uri.parse(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 exterally?');
|
||||
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);
|
||||
@@ -218,6 +242,36 @@ export class QueryHistoryManager {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -240,12 +294,24 @@ export class QueryHistoryManager {
|
||||
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 {
|
||||
|
||||
@@ -14,7 +14,7 @@ export class CompletedQuery implements QueryWithResults {
|
||||
readonly query: QueryInfo;
|
||||
readonly result: messages.EvaluationResult;
|
||||
readonly database: DatabaseInfo;
|
||||
readonly logFileLocation?: string
|
||||
readonly logFileLocation?: string;
|
||||
options: QueryHistoryItemOptions;
|
||||
dispose: () => void;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -70,23 +42,26 @@ function getQuickQueriesDir(ctx: ExtensionContext): 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",
|
||||
|
||||
@@ -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,11 +140,7 @@ export class QueryInfo {
|
||||
},
|
||||
queryToCheck: this.program,
|
||||
resultPath: this.compiledQueryPath,
|
||||
target: this.quickEvalPosition ? {
|
||||
quickEval: { quickEvalPos: this.quickEvalPosition }
|
||||
} : {
|
||||
query: {}
|
||||
}
|
||||
target,
|
||||
};
|
||||
|
||||
compiled = await helpers.withProgress({
|
||||
@@ -239,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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -259,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);
|
||||
const hash = async function (filename: string): Promise<string> {
|
||||
const hash = async function(filename: string): Promise<string> {
|
||||
return crypto.createHash('sha256').update(await fs.readFile(filename)).digest('hex');
|
||||
}
|
||||
|
||||
@@ -294,19 +296,39 @@ async function checkDbschemeCompatibility(
|
||||
}
|
||||
}
|
||||
|
||||
/** Prompts the user to save `document` if it has unsaved changes. */
|
||||
async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<void> {
|
||||
/**
|
||||
* 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;
|
||||
quickEvalText?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -357,10 +379,13 @@ export async function determineSelectedQuery(selectedResourceUri: vscode.Uri | u
|
||||
// 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.');
|
||||
@@ -371,9 +396,10 @@ export async function determineSelectedQuery(selectedResourceUri: vscode.Uri | u
|
||||
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(
|
||||
@@ -390,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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
/**/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
module.exports = {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
env: {
|
||||
browser: true
|
||||
},
|
||||
extends: [
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -188,8 +188,12 @@ describe("Release version ordering", () => {
|
||||
});
|
||||
|
||||
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;
|
||||
@@ -209,37 +213,37 @@ describe('Launcher path', () => {
|
||||
it('should not warn with proper launcher name', async () => {
|
||||
launcherThatExists = 'codeql.exe';
|
||||
const result = await getExecutableFromDirectory('abc');
|
||||
expect(fsSpy).to.have.been.calledWith(`abc${path.sep}codeql.exe`);
|
||||
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(`abc${path.sep}codeql.cmd`);
|
||||
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(`abc${path.sep}codeql.exe`);
|
||||
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(`abc${path.sep}codeql.exe`);
|
||||
expect(fsSpy).to.have.been.calledWith(`abc${path.sep}codeql.cmd`);
|
||||
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(`abc${path.sep}codeql.cmd`);
|
||||
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(`abc${path.sep}codeql.exe`);
|
||||
expect(fsSpy).to.have.been.calledWith(`abc${path.sep}codeql.cmd`);
|
||||
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);
|
||||
@@ -251,8 +255,8 @@ describe('Launcher path', () => {
|
||||
it('should warn when no launcher is found', async () => {
|
||||
launcherThatExists = 'xxx';
|
||||
const result = await getExecutableFromDirectory('abc', true);
|
||||
expect(fsSpy).to.have.been.calledWith(`abc${path.sep}codeql.exe`);
|
||||
expect(fsSpy).to.have.been.calledWith(`abc${path.sep}codeql.cmd`);
|
||||
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);
|
||||
@@ -261,9 +265,46 @@ describe('Launcher path', () => {
|
||||
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);
|
||||
@@ -271,7 +312,8 @@ describe('Launcher path', () => {
|
||||
|
||||
return proxyquire('../../distribution', {
|
||||
'./helpers': {
|
||||
showAndLogWarningMessage: warnSpy
|
||||
showAndLogWarningMessage: warnSpy,
|
||||
showAndLogErrorMessage: errorSpy
|
||||
},
|
||||
'./logging': {
|
||||
'logger': {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -3,6 +3,6 @@ module.exports = {
|
||||
mocha: true
|
||||
},
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
project: './test/tsconfig.json',
|
||||
},
|
||||
}
|
||||
|
||||
8
extensions/ql-vscode/test/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||