Compare commits

...

40 Commits

Author SHA1 Message Date
jcreedcmu
379b69a0e9 Merge pull request #489 from jcreedcmu/jcreed/v1.3.1
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
update CHANGELOG for release
2020-07-07 14:34:28 -04:00
Jason Reed
c4353981fa update CHANGELOG for release 2020-07-07 14:28:53 -04:00
jcreedcmu
cc7fb39be7 Merge pull request #488 from jcreedcmu/jcreed/query-text
Fix display of quick-query query text
2020-07-07 11:57:07 -04:00
Jason Reed
d8266b7bc1 Update CHANGELOG. 2020-07-07 08:40:07 -04:00
Jason Reed
d50277380b Fix display of quick-query query text 2020-07-07 08:39:03 -04:00
Andrew Eisenberg
3e149e7bb3 Update changelog 2020-07-06 07:32:35 -07:00
Andrew Eisenberg
00e252d48a Change styling on pagination section 2020-07-06 07:32:35 -07:00
Andrew Eisenberg
6a2832fcc7 Update changelog 2020-07-06 07:21:12 -07:00
Andrew Eisenberg
a7d99cc7e2 Fix nested problem adding database starting with db-* 2020-07-06 07:21:12 -07:00
jcreedcmu
454e8471a4 Merge pull request #481 from jcreedcmu/jcreed/interpreted-pagination
Allow pagination for interpreted results
2020-07-06 07:53:06 -04:00
Jason Reed
e2d125a558 Fix broken raw queries. 2020-07-02 10:32:38 -04:00
Jason Reed
e345425051 Fix wrong number of pages shown on initial display. 2020-07-02 10:27:15 -04:00
Jason Reed
0b32961f6d Unbreak typing page number into field 2020-07-02 09:39:47 -04:00
Jason Reed
e0a58a86fc Inline now-trivial function paginationEnabled 2020-07-01 09:20:31 -04:00
Jason Reed
ec45db3bc3 Appease linter. 2020-06-30 11:13:10 -04:00
Jason Reed
94d230308c Allow switching to alerts table 2020-06-30 10:17:46 -04:00
Jason Reed
96688e3379 Fix listing of tables when in alerts view 2020-06-30 10:14:14 -04:00
Jason Reed
88c27618b1 Show number of results correctly 2020-06-30 09:54:13 -04:00
Jason Reed
11c538a99d Teach webview to do pagination in interpreted results 2020-06-30 09:39:04 -04:00
Jason Reed
0e3b7a8eb5 Also show pagination interface for interpreted results 2020-06-29 12:52:19 -04:00
Jason Reed
65aa6928e4 Show number of pages in pagination interface 2020-06-29 12:38:48 -04:00
Jason Reed
fe02a58e45 Teach extension to accept ShowInterpretedPageMsg 2020-06-29 12:36:09 -04:00
Jason Reed
4030ddbdc2 Teach extension how to request display of alerts page 2020-06-29 11:57:44 -04:00
Jason Reed
b3642bd62e Compute truncation functionally rather than imperative update 2020-06-29 11:44:17 -04:00
Jason Reed
addddb0095 Factor out truncation of results from interpretation itself 2020-06-29 11:10:32 -04:00
Jason Reed
d7732c4ed6 Add interpreted results page size 2020-06-29 11:02:30 -04:00
Andrew Eisenberg
6e34c03b05 Update changelog 2020-06-26 11:40:40 -07:00
Andrew Eisenberg
75518a5d01 Ensure source folders are zipped
Zips source folders of databases when they are added. Only if
the databases are fully controlled by VS Code.

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

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

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

There is a corner case when there are 3 or more selected queries
and a user *unselects* a query. We do not track the selection
order of the remaining two queries.
2020-06-23 13:27:20 -07:00
Andrew Eisenberg
f4e983e214 Change command name 2020-06-23 09:58:07 -07:00
Andrew Eisenberg
60620a5618 Update changelog 2020-06-23 08:14:05 -07:00
Andrew Eisenberg
25bac72ac5 Use Open instead of Extract to open zip files
This allows opening zip files whose local headers are not correct.
2020-06-23 08:14:05 -07:00
github-actions[bot]
e7ee1f86a8 Bump version to v1.3.1 2020-06-22 16:07:21 -07:00
17 changed files with 537 additions and 139 deletions

View File

@@ -13,7 +13,7 @@ To see what has changed in the last few versions of the extension, see the [Chan
* Enables you to use CodeQL to query databases and discover problems in codebases.
* Shows the flow of data through the results of path queries, which is essential for triaging security results.
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/Semmle/ql).
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/github/codeql).
* Adds IntelliSense to support you writing and editing your own CodeQL query and library files.

View File

@@ -75,6 +75,7 @@ dependencies:
style-loader: 0.23.1
through2: 3.0.1
tmp: 0.1.0
tmp-promise: 3.0.2
tree-kill: 1.2.2
ts-loader: 5.4.5_typescript@3.8.3
ts-node: 8.6.2_typescript@3.8.3
@@ -91,6 +92,7 @@ dependencies:
vscode-test-adapter-util: 0.7.0
webpack: 4.42.0_webpack@4.42.0
webpack-cli: 3.3.11_webpack@4.42.0
zip-a-folder: 0.0.12
lockfileVersion: 5.1
packages:
/@babel/code-frame/7.8.3:
@@ -1034,6 +1036,37 @@ packages:
dev: false
resolution:
integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
/archiver-utils/2.1.0:
dependencies:
glob: 7.1.6
graceful-fs: 4.2.3
lazystream: 1.0.0
lodash.defaults: 4.2.0
lodash.difference: 4.5.0
lodash.flatten: 4.4.0
lodash.isplainobject: 4.0.6
lodash.union: 4.6.0
normalize-path: 3.0.0
readable-stream: 2.3.7
dev: false
engines:
node: '>= 6'
resolution:
integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==
/archiver/3.1.1:
dependencies:
archiver-utils: 2.1.0
async: 2.6.3
buffer-crc32: 0.2.13
glob: 7.1.6
readable-stream: 3.6.0
tar-stream: 2.1.2
zip-stream: 2.1.3
dev: false
engines:
node: '>= 6'
resolution:
integrity: sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==
/archy/1.0.0:
dev: false
resolution:
@@ -1201,6 +1234,12 @@ packages:
node: '>= 0.10'
resolution:
integrity: sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=
/async/2.6.3:
dependencies:
lodash: 4.17.15
dev: false
resolution:
integrity: sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
/asynckit/0.4.0:
dev: false
resolution:
@@ -1297,6 +1336,14 @@ packages:
optional: true
resolution:
integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
/bl/4.0.2:
dependencies:
buffer: 5.6.0
inherits: 2.0.4
readable-stream: 3.6.0
dev: false
resolution:
integrity: sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==
/bluebird/3.4.7:
dev: false
resolution:
@@ -1438,6 +1485,13 @@ packages:
dev: false
resolution:
integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
/buffer/5.6.0:
dependencies:
base64-js: 1.3.1
ieee754: 1.1.13
dev: false
resolution:
integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
/buffers/0.1.1:
dev: false
engines:
@@ -1844,6 +1898,17 @@ packages:
dev: false
resolution:
integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
/compress-commons/2.1.1:
dependencies:
buffer-crc32: 0.2.13
crc32-stream: 3.0.1
normalize-path: 3.0.0
readable-stream: 2.3.7
dev: false
engines:
node: '>= 6'
resolution:
integrity: sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==
/concat-map/0.0.1:
dev: false
resolution:
@@ -1918,6 +1983,21 @@ packages:
node: '>=8'
resolution:
integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
/crc/3.8.0:
dependencies:
buffer: 5.6.0
dev: false
resolution:
integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==
/crc32-stream/3.0.1:
dependencies:
crc: 3.8.0
readable-stream: 3.6.0
dev: false
engines:
node: '>= 6.9.0'
resolution:
integrity: sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==
/create-ecdh/4.0.3:
dependencies:
bn.js: 4.11.8
@@ -3015,6 +3095,10 @@ packages:
dev: false
resolution:
integrity: sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
/fs-constants/1.0.0:
dev: false
resolution:
integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
/fs-extra/7.0.1:
dependencies:
graceful-fs: 4.2.3
@@ -4328,6 +4412,18 @@ packages:
node: '>=8'
resolution:
integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
/lodash.defaults/4.2.0:
dev: false
resolution:
integrity: sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
/lodash.difference/4.5.0:
dev: false
resolution:
integrity: sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=
/lodash.flatten/4.4.0:
dev: false
resolution:
integrity: sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
/lodash.get/4.4.2:
dev: false
resolution:
@@ -4336,6 +4432,14 @@ packages:
dev: false
resolution:
integrity: sha1-QVxEePK8wwEgwizhDtMib30+GOA=
/lodash.isplainobject/4.0.6:
dev: false
resolution:
integrity: sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
/lodash.union/4.6.0:
dev: false
resolution:
integrity: sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=
/lodash/4.17.15:
dev: false
resolution:
@@ -6116,6 +6220,13 @@ packages:
hasBin: true
resolution:
integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
/rimraf/3.0.2:
dependencies:
glob: 7.1.6
dev: false
hasBin: true
resolution:
integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
/ripemd160/2.0.2:
dependencies:
hash-base: 3.0.4
@@ -6806,6 +6917,16 @@ packages:
node: '>=6'
resolution:
integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
/tar-stream/2.1.2:
dependencies:
bl: 4.0.2
end-of-stream: 1.4.4
fs-constants: 1.0.0
inherits: 2.0.4
readable-stream: 3.6.0
dev: false
resolution:
integrity: sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==
/tar/4.4.13:
dependencies:
chownr: 1.1.4
@@ -6917,6 +7038,12 @@ packages:
dev: false
resolution:
integrity: sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
/tmp-promise/3.0.2:
dependencies:
tmp: 0.2.1
dev: false
resolution:
integrity: sha512-OyCLAKU1HzBjL6Ev3gxUeraJNlbNingmi8IrHHEsYH8LTmEuhvYfqvhn2F/je+mjf4N58UmZ96OMEy1JanSCpA==
/tmp/0.0.29:
dependencies:
os-tmpdir: 1.0.2
@@ -6941,6 +7068,14 @@ packages:
node: '>=6'
resolution:
integrity: sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==
/tmp/0.2.1:
dependencies:
rimraf: 3.0.2
dev: false
engines:
node: '>=8.17.0'
resolution:
integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
/to-absolute-glob/2.0.2:
dependencies:
is-absolute: 1.0.0
@@ -7822,6 +7957,22 @@ packages:
commander: 2.20.3
resolution:
integrity: sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw==
/zip-a-folder/0.0.12:
dependencies:
archiver: 3.1.1
dev: false
resolution:
integrity: sha512-wZGiWgp3z2TocBlzx3S5tsLgPbT39qG2uIZmn2MhYLVjhKIr2nMhg7i4iPDL4W3XvMDaOEEVU5ZB0Y/Pt6BLvA==
/zip-stream/2.1.3:
dependencies:
archiver-utils: 2.1.0
compress-commons: 2.1.1
readable-stream: 3.6.0
dev: false
engines:
node: '>= 6'
resolution:
integrity: sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==
'file:projects/codeql-gulp-tasks.tgz':
dependencies:
'@microsoft/node-core-library': 3.13.0
@@ -7853,7 +8004,7 @@ packages:
dev: false
name: '@rush-temp/codeql-gulp-tasks'
resolution:
integrity: sha512-eWHB3hxiYH/b5wdcG0S1CqTBmqtNTu3VVvdEo/qrZxMoPVfXOT630ocQ8CxrqTfT1kyx1ZpKoqzzaR0hxgDqCg==
integrity: sha512-fqE1VOiN1SmB17tolFDAlApTd/oXFfl1wh+WkQfajdOsHLxDSMPYAcWnt7FdSaQSp/EgIIZ3lNnDBTXWlPkE/g==
tarball: 'file:projects/codeql-gulp-tasks.tgz'
version: 0.0.0
'file:projects/codeql-vscode-utils.tgz_typescript@3.8.3':
@@ -7867,7 +8018,7 @@ packages:
peerDependencies:
typescript: '*'
resolution:
integrity: sha512-OC9Yx7t16ZzrGmGu1MIV8H4A2RZRFaNonIwQqlWEJ56LGVkNL+gX0RxAPwtT76AbEzhsyb4VUQ609QRt98rPlw==
integrity: sha512-UdaU/jwMCYYysISj/rwLWXxZKhSYo/uIkUBM5LpQhm4vy4z/0FPrEqqZWOKk+ldxUPMi8lx+8U7WkqXnTY5PFg==
tarball: 'file:projects/codeql-vscode-utils.tgz'
version: 0.0.0
'file:projects/semmle-bqrs.tgz_typescript@3.8.3':
@@ -7883,7 +8034,7 @@ packages:
peerDependencies:
typescript: '*'
resolution:
integrity: sha512-l46HYR+5UlVb0XIQMNRBjADvVVzFolw3KrPPHuIRWEXhkdnFXLTOHEXBFcnijNZIduePBNqPrII+wqMmRcJVvg==
integrity: sha512-xXKv5YnDihfs9L8fWDfHY4IndM8A/cievN+teT/bStvPQ53P3RJikeF2R11+ovg/UNg8YaCQYAHovi4z5KEx4g==
tarball: 'file:projects/semmle-bqrs.tgz'
version: 0.0.0
'file:projects/semmle-io-node.tgz_typescript@3.8.3':
@@ -7899,7 +8050,7 @@ packages:
peerDependencies:
typescript: '*'
resolution:
integrity: sha512-O034F8DYa59REsFjAeP4qxtoGBslO8RiGJK8bdMaxWqOX52IJyVqx6ybNgz3cWzX0Q+KI3UjZTCdk3vvF+Wsbg==
integrity: sha512-sB1AHo/3SHXobocIOTOOF4A6RFD7PPr8n8CSv6Qq4ZiOP/8ULNmwgoxEAksp6x4Uf6OSgJ9Puawk/v0DeeK1UQ==
tarball: 'file:projects/semmle-io-node.tgz'
version: 0.0.0
'file:projects/semmle-io.tgz_typescript@3.8.3':
@@ -7914,14 +8065,14 @@ packages:
peerDependencies:
typescript: '*'
resolution:
integrity: sha512-40cXVM0J2z7vTtvon31m8QAd/W7c3P4yLqpoGnbtLcmb2/1d881IdZbe/8dMsUOor/Qv/VsxcL9ndz+Av+eClw==
integrity: sha512-LmAIuLFIfrPg81Hv9UZ7VxfiM3jpXJ7HkH8w2L1t8jUZt6piibQbwMo3R8oz1v1F21QnL8DxBG4v7PCHgbf72w==
tarball: 'file:projects/semmle-io.tgz'
version: 0.0.0
'file:projects/typescript-config.tgz':
dev: false
name: '@rush-temp/typescript-config'
resolution:
integrity: sha512-kSFyvKy63jUHFVXQEzALiYfsTdn7J+Y7PcqtUVo9GndU5b5Xh3rBpVbZD1QN8+y8GfT0m/sdZZQVyH0h+On11Q==
integrity: sha512-XuUIySaNoooIduvehnlKYaHqZJmmQoCqB1RtKhNszjCYZaSSJAnKVucViWBf5oNLKSNP7NchrD7gcoBlQ3xYvw==
tarball: 'file:projects/typescript-config.tgz'
version: 0.0.0
'file:projects/vscode-codeql.tgz':
@@ -7985,6 +8136,7 @@ packages:
style-loader: 0.23.1
through2: 3.0.1
tmp: 0.1.0
tmp-promise: 3.0.2
tree-kill: 1.2.2
ts-loader: 5.4.5_typescript@3.8.3
ts-node: 8.6.2_typescript@3.8.3
@@ -8000,10 +8152,11 @@ packages:
vscode-test-adapter-util: 0.7.0
webpack: 4.42.0_webpack@4.42.0
webpack-cli: 3.3.11_webpack@4.42.0
zip-a-folder: 0.0.12
dev: false
name: '@rush-temp/vscode-codeql'
resolution:
integrity: sha512-oGd4/XeaRctrSAFlpSZ3yhqnaTUQ6ZT2gi5iVuDWAB4FMiwJ2RCk/eFPlXrI6d+cl3l9wKHgCQeaLsbx2BfWjw==
integrity: sha512-0Y23lFB67k0+FFH/+TkoZDMBgeScx4E73Q/j+oDIsu7G+Dsx0SLp3Lzb/1nKJma9i2t/OFVYbTZiRYPUisIBmA==
tarball: 'file:projects/vscode-codeql.tgz'
version: 0.0.0
registry: ''
@@ -8084,6 +8237,7 @@ specifiers:
style-loader: ~0.23.1
through2: ^3.0.1
tmp: ^0.1.0
tmp-promise: ~3.0.2
tree-kill: ~1.2.2
ts-loader: ^5.4.5
ts-node: ^8.3.0
@@ -8100,3 +8254,4 @@ specifiers:
vscode-test-adapter-util: ~0.7.0
webpack: ^4.38.0
webpack-cli: ^3.3.2
zip-a-folder: ~0.0.12

View File

@@ -1,11 +1,20 @@
# CodeQL for Visual Studio Code: Changelog
## 1.3.1 - 7 July 2020
- Fix unzipping of large files.
- Ensure compare order is consistent when selecting two queries to compare. The first query selected is always the _from_ query and the query selected later is always the _to_ query.
- Ensure added databases have zipped source locations for databases added as archives or downloaded from the internet.
- Fix bug where it is not possible to add databases starting with `db-*`.
- Change styling of pagination section of the results page.
- Fix display of query text for stored quick queries.
## 1.3.0 - 22 June 2020
- Report error when selecting invalid database.
- Add descriptive message for database archive import failure.
- Respect VSCode's i18n locale setting when formatting dates and sorting strings.
- Allow the opening of large Sarif files externally from VSCode.
- Respect VS Code's i18n locale setting when formatting dates and sorting strings.
- Allow the opening of large SARIF files externally from VS Code.
- Add new 'CodeQL: Compare Query' command that shows the differences between two queries.
- Allow multiple items in the query history view to be removed in one operation.
- Allow multiple items in the databases view to be removed in one operation.

View File

@@ -4,7 +4,7 @@
"description": "CodeQL for Visual Studio Code",
"author": "GitHub",
"private": true,
"version": "1.3.0",
"version": "1.3.1",
"publisher": "GitHub",
"license": "MIT",
"icon": "media/VS-marketplace-CodeQL-icon.png",
@@ -312,7 +312,7 @@
},
{
"command": "codeQLQueryHistory.compareWith",
"title": "Compare with..."
"title": "Compare Results"
},
{
"command": "codeQLQueryResults.nextPathStep",
@@ -612,7 +612,9 @@
"vscode-test-adapter-util": "~0.7.0",
"minimist": "~1.2.5",
"semver": "~7.3.2",
"@types/semver": "~7.2.0"
"@types/semver": "~7.2.0",
"tmp-promise": "~3.0.2",
"zip-a-folder": "~0.0.12"
},
"devDependencies": {
"@types/chai": "^4.1.7",

View File

@@ -1,5 +1,6 @@
import { DecodedBqrsChunk, ResultSetSchema, ColumnKind, Column, ColumnValue } from './bqrs-cli-types';
import { LocationValue, ResultSetSchema as AdaptedSchema, ColumnSchema, ColumnType, LocationStyle } from 'semmle-bqrs';
import { ResultSet } from './interface-types';
// FIXME: This is a temporary bit of impedance matching to convert
// from the types provided by ./bqrs-cli-types, to the types used by
@@ -128,7 +129,8 @@ export interface ExtensionParsedResultSets {
t: 'ExtensionParsed';
pageNumber: number;
numPages: number;
numInterpretedPages: number;
selectedTable?: string; // when undefined, means 'show default table'
resultSetNames: string[];
resultSet: RawResultSet;
resultSet: ResultSet;
}

View File

@@ -1,5 +1,6 @@
import fetch, { Response } from 'node-fetch';
import * as unzipper from 'unzipper';
import { zip } from 'zip-a-folder';
import {
Uri,
ProgressOptions,
@@ -9,6 +10,7 @@ import {
} from 'vscode';
import * as fs from 'fs-extra';
import * as path from 'path';
import { DatabaseManager, DatabaseItem } from './databases';
import {
ProgressCallback,
@@ -183,9 +185,9 @@ async function databaseArchiveFetcher(
progressCallback?: ProgressCallback
): Promise<DatabaseItem> {
progressCallback?.({
maxStep: 3,
message: 'Getting database',
step: 1,
maxStep: 4,
});
if (!storagePath) {
throw new Error('No storage path specified.');
@@ -200,9 +202,9 @@ async function databaseArchiveFetcher(
}
progressCallback?.({
maxStep: 3,
message: 'Opening database',
step: 3,
maxStep: 4,
});
// find the path to the database. The actual database might be in a sub-folder
@@ -212,6 +214,13 @@ async function databaseArchiveFetcher(
'codeql-database.yml'
);
if (dbPath) {
progressCallback?.({
message: 'Validating and fixing source location',
step: 4,
maxStep: 4,
});
await ensureZippedSourceLocation(dbPath);
const item = await databasesManager.openDatabase(Uri.file(dbPath));
databasesManager.setCurrentDatabaseItem(item);
return item;
@@ -261,19 +270,9 @@ function validateHttpsUrl(databaseUrl: string) {
}
async function readAndUnzip(databaseUrl: string, unzipPath: string) {
const unzipStream = unzipper.Extract({
path: unzipPath,
});
await new Promise((resolve, reject) => {
// we already know this is a file scheme
const databaseFile = Uri.parse(databaseUrl).fsPath;
const stream = fs.createReadStream(databaseFile);
stream.on('error', reject);
unzipStream.on('error', reject);
unzipStream.on('close', resolve);
stream.pipe(unzipStream);
});
const databaseFile = Uri.parse(databaseUrl).fsPath;
const directory = await unzipper.Open.file(databaseFile);
await directory.extract({ path: unzipPath });
}
async function fetchAndUnzip(
@@ -288,6 +287,7 @@ async function fetchAndUnzip(
const unzipStream = unzipper.Extract({
path: unzipPath,
});
progressCallback?.({
maxStep: 3,
message: 'Unzipping database',
@@ -444,3 +444,24 @@ async function promptForLanguage(
}
);
}
/**
* Databases created by the old odasa tool will not have a zipped
* source location. However, this extension works better if sources
* are zipped.
*
* This function ensures that the source location is zipped. If the
* `src` folder exists and the `src.zip` file does not, the `src`
* folder will be zipped and then deleted.
*
* @param databasePath The full path to the unzipped database
*/
async function ensureZippedSourceLocation(databasePath: string): Promise<void> {
const srcFolderPath = path.join(databasePath, 'src');
const srcZipPath = srcFolderPath + '.zip';
if ((await fs.pathExists(srcFolderPath)) && !(await fs.pathExists(srcZipPath))) {
await zip(srcFolderPath, srcZipPath);
await fs.remove(srcFolderPath);
}
}

View File

@@ -594,7 +594,8 @@ export class DatabaseUI extends DisposableObject {
if ((await fs.stat(dbPath)).isFile()) {
dbPath = path.dirname(dbPath);
}
if (path.basename(dbPath).startsWith('db-')) {
if (isLikelyDbFolder(dbPath)) {
dbPath = path.dirname(dbPath);
}
return Uri.file(dbPath);
@@ -609,3 +610,8 @@ export class DatabaseUI extends DisposableObject {
}
}
}
const dbRegeEx = /^db-(javascript|go|cpp|java|python)$/;
function isLikelyDbFolder(dbPath: string) {
return path.basename(dbPath).match(dbRegeEx);
}

View File

@@ -133,7 +133,7 @@ async function findSourceArchive(
async function resolveDatabase(
databasePath: string
): Promise<DatabaseContents | undefined> {
): Promise<DatabaseContents> {
const name = path.basename(databasePath);
@@ -155,20 +155,6 @@ async function getDbSchemeFiles(dbDirectory: string): Promise<string[]> {
return await glob('*.dbscheme', { cwd: dbDirectory });
}
async function resolveRawDataset(datasetPath: string): Promise<DatabaseContents | undefined> {
if ((await getDbSchemeFiles(datasetPath)).length > 0) {
return {
kind: DatabaseKind.RawDataset,
name: path.basename(datasetPath),
datasetUri: vscode.Uri.file(datasetPath),
sourceArchiveUri: undefined
};
}
else {
return undefined;
}
}
async function resolveDatabaseContents(uri: vscode.Uri): Promise<DatabaseContents> {
if (uri.scheme !== 'file') {
throw new Error(`Database URI scheme '${uri.scheme}' not supported; only 'file' URIs are supported.`);
@@ -178,7 +164,7 @@ async function resolveDatabaseContents(uri: vscode.Uri): Promise<DatabaseContent
throw new InvalidDatabaseError(`Database '${databasePath}' does not exist.`);
}
const contents = await resolveDatabase(databasePath) || await resolveRawDataset(databasePath);
const contents = await resolveDatabase(databasePath);
if (contents === undefined) {
throw new InvalidDatabaseError(`'${databasePath}' is not a valid database.`);

View File

@@ -1,6 +1,6 @@
import * as fs from 'fs-extra';
import * as yaml from 'js-yaml';
import * as tmp from 'tmp';
import * as tmp from 'tmp-promise';
import * as vscode from 'vscode';
import { decodeSourceArchiveUri, zipArchiveScheme } from './archive-filesystem-provider';
import { ColumnKindCode, EntityValue, getResultSetSchema, LineColumnLocation, UrlValue } from './bqrs-cli-types';
@@ -42,7 +42,9 @@ function nameOfKeyType(keyType: KeyType): string {
}
async function resolveQueries(cli: CodeQLCliServer, qlpack: string, keyType: KeyType): Promise<string[]> {
const suiteFile = tmp.fileSync({ postfix: '.qls' }).name;
const suiteFile = (await tmp.file({
postfix: '.qls'
})).path;
const suiteYaml = { qlpack, include: { kind: 'definitions', 'tags contain': tagOfKeyType(keyType) } };
await fs.writeFile(suiteFile, yaml.safeDump(suiteYaml), 'utf8');

View File

@@ -23,11 +23,6 @@ export type PathTableResultSet = {
export type ResultSet = RawTableResultSet | PathTableResultSet;
/**
* Only ever show this many results per run in interpreted results.
*/
export const INTERPRETED_RESULTS_PER_RUN_LIMIT = 100;
/**
* Only ever show this many rows in a raw result table.
*/
@@ -38,6 +33,11 @@ export const RAW_RESULTS_LIMIT = 10000;
*/
export const RAW_RESULTS_PAGE_SIZE = 100;
/**
* Show this many rows in an interpreted results table at a time.
*/
export const INTERPRETED_RESULTS_PAGE_SIZE = 100;
export interface DatabaseInfo {
name: string;
databaseUri: string;
@@ -61,6 +61,7 @@ export interface PreviousExecution {
export interface Interpretation {
sourceLocationPrefix: string;
numTruncatedResults: number;
numTotalResults: number;
/**
* sortState being undefined means don't sort, just present results in the order
* they appear in the sarif file.
@@ -113,6 +114,16 @@ export interface SetStateMsg {
parsedResultSets: ParsedResultSets;
}
export interface ShowInterpretedPageMsg {
t: 'showInterpretedPage';
interpretation: Interpretation;
database: DatabaseInfo;
metadata?: QueryMetadata;
pageNumber: number;
numPages: number;
resultSetNames: string[];
}
/** Advance to the next or previous path no in the path viewer */
export interface NavigatePathMsg {
t: 'navigatePath';
@@ -124,6 +135,7 @@ export interface NavigatePathMsg {
export type IntoResultsViewMsg =
| ResultsUpdatingMsg
| SetStateMsg
| ShowInterpretedPageMsg
| NavigatePathMsg;
export type FromResultsViewMsg =

View File

@@ -19,7 +19,6 @@ import { assertNever } from './helpers-pure';
import {
FromResultsViewMsg,
Interpretation,
INTERPRETED_RESULTS_PER_RUN_LIMIT,
IntoResultsViewMsg,
QueryMetadata,
ResultsPaths,
@@ -28,6 +27,8 @@ import {
InterpretedResultsSortState,
SortDirection,
RAW_RESULTS_PAGE_SIZE,
INTERPRETED_RESULTS_PAGE_SIZE,
ALERTS_TABLE_NAME,
} from './interface-types';
import { Logger } from './logging';
import * as messages from './messages';
@@ -51,6 +52,7 @@ import {
jumpToLocation,
} from './interface-utils';
import { getDefaultResultSetName } from './interface-types';
import { ResultSetSchema } from './bqrs-cli-types';
/**
* interface.ts
@@ -95,6 +97,10 @@ function numPagesOfResultSet(resultSet: RawResultSet): number {
return Math.ceil(resultSet.schema.tupleCount / RAW_RESULTS_PAGE_SIZE);
}
function numInterpretedPages(interpretation: Interpretation | undefined): number {
return Math.ceil((interpretation?.sarif.runs[0].results?.length || 0) / INTERPRETED_RESULTS_PAGE_SIZE);
}
export class InterfaceManager extends DisposableObject {
private _displayedQuery?: CompletedQuery;
private _interpretation?: Interpretation;
@@ -244,7 +250,12 @@ export class InterfaceManager extends DisposableObject {
);
break;
case 'changePage':
await this.showPageOfResults(msg.selectedTable, msg.pageNumber);
if (msg.selectedTable === ALERTS_TABLE_NAME) {
await this.showPageOfInterpretedResults(msg.pageNumber);
}
else {
await this.showPageOfRawResults(msg.selectedTable, msg.pageNumber);
}
break;
default:
assertNever(msg);
@@ -283,7 +294,8 @@ export class InterfaceManager extends DisposableObject {
return;
}
const interpretation = await this.interpretResultsInfo(
this._interpretation = undefined;
const interpretationPage = await this.interpretResultsInfo(
results.query,
results.interpretedResultsSortState
);
@@ -295,7 +307,6 @@ export class InterfaceManager extends DisposableObject {
);
this._displayedQuery = results;
this._interpretation = interpretation;
const panel = this.getPanel();
await this.waitForPanelLoaded();
@@ -325,19 +336,13 @@ export class InterfaceManager extends DisposableObject {
const getParsedResultSets = async (): Promise<ParsedResultSets> => {
if (EXPERIMENTAL_BQRS_SETTING.getValue()) {
const schemas = await this.cliServer.bqrsInfo(
results.query.resultsPaths.resultsPath,
RAW_RESULTS_PAGE_SIZE
);
const resultSetNames = schemas['result-sets'].map(
(resultSet) => resultSet.name
);
const resultSetSchemas = await this.getResultSetSchemas(results);
const resultSetNames = resultSetSchemas.map(schema => schema.name);
// This may not wind up being the page we actually show, if there are interpreted results,
// but speculatively send it anyway.
const selectedTable = getDefaultResultSetName(resultSetNames);
const schema = schemas['result-sets'].find(
const schema = resultSetSchemas.find(
(resultSet) => resultSet.name == selectedTable
)!;
if (schema === undefined) {
@@ -352,12 +357,12 @@ export class InterfaceManager extends DisposableObject {
);
const adaptedSchema = adaptSchema(schema);
const resultSet = adaptBqrs(adaptedSchema, chunk);
return {
t: 'ExtensionParsed',
pageNumber: 0,
numPages: numPagesOfResultSet(resultSet),
resultSet,
numInterpretedPages: numInterpretedPages(this._interpretation),
resultSet: { t: 'RawResultSet', ...resultSet },
selectedTable: undefined,
resultSetNames,
};
@@ -368,7 +373,7 @@ export class InterfaceManager extends DisposableObject {
await this.postMessage({
t: 'setState',
interpretation,
interpretation: interpretationPage,
origResultsPaths: results.query.resultsPaths,
resultsPath: this.convertPathToWebviewUri(
results.query.resultsPaths.resultsPath
@@ -381,10 +386,48 @@ export class InterfaceManager extends DisposableObject {
});
}
/**
* Show a page of interpreted results
*/
public async showPageOfInterpretedResults(
pageNumber: number
): Promise<void> {
if (this._displayedQuery === undefined) {
throw new Error('Trying to show interpreted results but displayed query was undefined');
}
if (this._interpretation === undefined) {
throw new Error('Trying to show interpreted results but interpretation was undefined');
}
if (this._interpretation.sarif.runs[0].results === undefined) {
throw new Error('Trying to show interpreted results but results were undefined');
}
const resultSetSchemas = await this.getResultSetSchemas(this._displayedQuery);
const resultSetNames = resultSetSchemas.map(schema => schema.name);
await this.postMessage({
t: 'showInterpretedPage',
interpretation: this.getPageOfInterpretedResults(pageNumber),
database: this._displayedQuery.database,
metadata: this._displayedQuery.query.metadata,
pageNumber,
resultSetNames,
numPages: numInterpretedPages(this._interpretation),
});
}
private async getResultSetSchemas(results: CompletedQuery): Promise<ResultSetSchema[]> {
const schemas = await this.cliServer.bqrsInfo(
results.query.resultsPaths.resultsPath,
RAW_RESULTS_PAGE_SIZE
);
return schemas['result-sets'];
}
/**
* Show a page of raw results from the chosen table.
*/
public async showPageOfResults(
public async showPageOfRawResults(
selectedTable: string,
pageNumber: number
): Promise<void> {
@@ -399,16 +442,10 @@ export class InterfaceManager extends DisposableObject {
(sortedResultsMap[k] = this.convertPathPropertiesToWebviewUris(v))
);
const schemas = await this.cliServer.bqrsInfo(
results.query.resultsPaths.resultsPath,
RAW_RESULTS_PAGE_SIZE
);
const resultSetSchemas = await this.getResultSetSchemas(results);
const resultSetNames = resultSetSchemas.map(schema => schema.name);
const resultSetNames = schemas['result-sets'].map(
(resultSet) => resultSet.name
);
const schema = schemas['result-sets'].find(
const schema = resultSetSchemas.find(
(resultSet) => resultSet.name == selectedTable
)!;
if (schema === undefined)
@@ -426,8 +463,9 @@ export class InterfaceManager extends DisposableObject {
const parsedResultSets: ParsedResultSets = {
t: 'ExtensionParsed',
pageNumber,
resultSet,
resultSet: { t: 'RawResultSet', ...resultSet },
numPages: numPagesOfResultSet(resultSet),
numInterpretedPages: numInterpretedPages(this._interpretation),
selectedTable: selectedTable,
resultSetNames,
};
@@ -447,7 +485,7 @@ export class InterfaceManager extends DisposableObject {
});
}
private async getTruncatedResults(
private async _getInterpretedResults(
metadata: QueryMetadata | undefined,
resultsPaths: ResultsPaths,
sourceInfo: cli.SourceInfo | undefined,
@@ -460,37 +498,58 @@ export class InterfaceManager extends DisposableObject {
resultsPaths,
sourceInfo
);
// For performance reasons, limit the number of results we try
// to serialize and send to the webview. TODO: possibly also
// limit number of paths per result, number of steps per path,
// or throw an error if we are in aggregate trying to send
// massively too much data, as it can make the extension
// unresponsive.
let numTruncatedResults = 0;
sarif.runs.forEach((run) => {
if (run.results !== undefined) {
sarif.runs.forEach(run => {
if (run.results !== undefined)
sortInterpretedResults(run.results, sortState);
if (run.results.length > INTERPRETED_RESULTS_PER_RUN_LIMIT) {
numTruncatedResults +=
run.results.length - INTERPRETED_RESULTS_PER_RUN_LIMIT;
run.results = run.results.slice(0, INTERPRETED_RESULTS_PER_RUN_LIMIT);
}
}
});
return {
const numTotalResults = (() => {
if (sarif.runs.length === 0) return 0;
if (sarif.runs[0].results === undefined) return 0;
return sarif.runs[0].results.length;
})();
const interpretation: Interpretation = {
sarif,
sourceLocationPrefix,
numTruncatedResults,
numTruncatedResults: 0,
numTotalResults,
sortState,
};
this._interpretation = interpretation;
return interpretation;
}
private getPageOfInterpretedResults(
pageNumber: number
): Interpretation {
function getPageOfRun(run: Sarif.Run): Sarif.Run {
return {
...run, results: run.results?.slice(
INTERPRETED_RESULTS_PAGE_SIZE * pageNumber,
INTERPRETED_RESULTS_PAGE_SIZE * (pageNumber + 1)
)
};
}
if (this._interpretation === undefined) {
throw new Error('Tried to get interpreted results before interpretation finished');
}
if (this._interpretation.sarif.runs.length !== 1) {
this.logger.log(`Warning: SARIF file had ${this._interpretation.sarif.runs.length} runs, expected 1`);
}
const interp = this._interpretation;
return {
...interp,
sarif: { ...interp.sarif, runs: [getPageOfRun(interp.sarif.runs[0])] },
};
}
private async interpretResultsInfo(
query: QueryInfo,
sortState: InterpretedResultsSortState | undefined
): Promise<Interpretation | undefined> {
let interpretation: Interpretation | undefined = undefined;
if (
(await query.canHaveInterpretedResults()) &&
query.quickEvalPosition === undefined // never do results interpretation if quickEval
@@ -507,7 +566,7 @@ export class InterfaceManager extends DisposableObject {
sourceArchive: sourceArchiveUri.fsPath,
sourceLocationPrefix,
};
interpretation = await this.getTruncatedResults(
await this._getInterpretedResults(
query.metadata,
query.resultsPaths,
sourceInfo,
@@ -522,7 +581,7 @@ export class InterfaceManager extends DisposableObject {
);
}
}
return interpretation;
return this._interpretation && this.getPageOfInterpretedResults(0);
}
private async showResultsAsDiagnostics(
@@ -541,7 +600,8 @@ export class InterfaceManager extends DisposableObject {
sourceArchive: sourceArchiveUri.fsPath,
sourceLocationPrefix,
};
const interpretation = await this.getTruncatedResults(
// TODO: Performance-testing to determine whether this truncation is necessary.
const interpretation = await this._getInterpretedResults(
metadata,
resultsInfo,
sourceInfo,

View File

@@ -167,7 +167,7 @@ export class QueryHistoryManager {
treeDataProvider: HistoryTreeDataProvider;
treeView: vscode.TreeView<CompletedQuery>;
lastItemClick: { time: Date; item: CompletedQuery } | undefined;
compareWithItem: CompletedQuery | undefined;
constructor(
ctx: ExtensionContext,
@@ -185,6 +185,7 @@ export class QueryHistoryManager {
treeDataProvider,
canSelectMany: true,
});
// Lazily update the tree view selection due to limitations of TreeView API (see
// `updateTreeViewSelectionIfVisible` doc for details)
this.treeView.onDidChangeVisibility(async (_ev) =>
@@ -195,6 +196,7 @@ export class QueryHistoryManager {
if (ev.selection.length == 0) {
this.updateTreeViewSelectionIfVisible();
}
this.updateCompareWith(ev.selection);
});
logger.log('Registering query history panel commands.');
ctx.subscriptions.push(
@@ -349,8 +351,8 @@ export class QueryHistoryManager {
throw new Error('Please select a successful query.');
}
const from = singleItem;
const to = await this.findOtherQueryToCompare(singleItem, multiSelect);
const from = this.compareWithItem || singleItem;
const to = await this.findOtherQueryToCompare(from, multiSelect);
if (from && to) {
this.doCompareCallback(from, to);
@@ -415,7 +417,7 @@ export class QueryHistoryManager {
: singleItem.queryName + '.ql';
const params = new URLSearchParams({
isQuickEval: String(!!singleItem.query.quickEvalPosition),
queryText: await this.getQueryText(singleItem),
queryText: encodeURIComponent(await this.getQueryText(singleItem)),
});
const uri = vscode.Uri.parse(
`codeql:${singleItem.query.queryID}-${queryName}?${params.toString()}`
@@ -588,4 +590,33 @@ the file in the file explorer and dragging it into the workspace.`
}
return true;
}
/**
* Updates the compare with source query. This ensures that all compare command invocations
* when exactly 2 queries are selected always have the proper _from_ query. Always use
* compareWithItem as the _from_ query.
*
* The heuristic is this:
*
* 1. If selection is empty or has length > 2 delete compareWithItem.
* 2. If selection is length 1, then set that item to compareWithItem.
* 3. If selection is length 2, then make sure compareWithItem is one of the selected items
* if not, then delete compareWithItem. If it is then, do nothing.
*
* This ensures that compareWithItem is always the first item selected if there are only
* two selected items.
*
* @param newSelection the new selection after the most recent selection change
*/
private updateCompareWith(newSelection: CompletedQuery[]) {
if (newSelection.length === 1) {
this.compareWithItem = newSelection[0];
} else if (
newSelection.length !== 2 ||
!this.compareWithItem ||
!newSelection.includes(this.compareWithItem)
) {
this.compareWithItem = undefined;
}
}
}

View File

@@ -50,9 +50,7 @@ function getResultCount(resultSet: ResultSet): number {
case 'RawResultSet':
return resultSet.schema.tupleCount;
case 'SarifResultSet':
if (resultSet.sarif.runs.length === 0) return 0;
if (resultSet.sarif.runs[0].results === undefined) return 0;
return resultSet.sarif.runs[0].results.length + resultSet.numTruncatedResults;
return resultSet.numTotalResults;
}
}
@@ -110,22 +108,11 @@ export class ResultTables
return this.props.parsedResultSets.t === 'ExtensionParsed';
}
/**
* Holds if we actually should show pagination interface right now. This is
* still false for the time being when we're viewing alerts.
*/
paginationEnabled(): boolean {
return this.paginationAllowed() &&
this.props.parsedResultSets.selectedTable !== ALERTS_TABLE_NAME &&
this.state.selectedTable !== ALERTS_TABLE_NAME;
}
constructor(props: ResultTablesProps) {
super(props);
const selectedTable = props.parsedResultSets.selectedTable || getDefaultResultSet(this.getResultSets());
let selectedPage: string;
switch (props.parsedResultSets.t) {
case 'ExtensionParsed':
selectedPage = (props.parsedResultSets.pageNumber + 1) + '';
@@ -134,15 +121,13 @@ export class ResultTables
selectedPage = '';
break;
}
this.state = { selectedTable, selectedPage };
}
private onTableSelectionChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
const selectedTable = event.target.value;
const fetchPageFromExtension = this.paginationAllowed() && selectedTable !== ALERTS_TABLE_NAME;
if (fetchPageFromExtension) {
if (this.paginationAllowed()) {
vscode.postMessage({
t: 'changePage',
pageNumber: 0,
@@ -189,13 +174,22 @@ export class ResultTables
renderPageButtons(resultSets: ExtensionParsedResultSets): JSX.Element {
const selectedTable = this.state.selectedTable;
// FIXME: The extension, not the view, should be in charge of deciding whether to initially show
// a raw or alerts page. We have to conditionally recompute the number of pages here, because
// on initial load of query results, resultSets.numPages will have the number of *raw* pages available,
// not interpreted pages, because the extension doesn't know the view will default to showing alerts
// instead.
const numPages = selectedTable == ALERTS_TABLE_NAME ?
resultSets.numInterpretedPages : resultSets.numPages;
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ selectedPage: e.target.value });
};
const choosePage = (input: string) => {
const pageNumber = parseInt(input);
if (pageNumber !== undefined && !isNaN(pageNumber)) {
const actualPageNumber = Math.max(0, Math.min(pageNumber - 1, resultSets.numPages - 1));
const actualPageNumber = Math.max(0, Math.min(pageNumber - 1, numPages - 1));
vscode.postMessage({
t: 'changePage',
pageNumber: actualPageNumber,
@@ -214,22 +208,30 @@ export class ResultTables
const nextPage = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
vscode.postMessage({
t: 'changePage',
pageNumber: Math.min(resultSets.pageNumber + 1, resultSets.numPages - 1),
pageNumber: Math.min(resultSets.pageNumber + 1, numPages - 1),
selectedTable,
});
};
return <span>
<button onClick={prevPage} >&lt;</button>
<input value={this.state.selectedPage} onChange={onChange}
return <span className="vscode-codeql__table-selection-header">
<button onClick={prevPage} >&#xab;</button>
<input
type="number"
size={3}
value={this.state.selectedPage}
onChange={onChange}
onBlur={e => choosePage(e.target.value)}
onKeyDown={e => { if (e.keyCode === 13) choosePage((e.target as HTMLInputElement).value); }}
/>
<button value=">" onClick={nextPage} >&gt;</button>
<span>
/ {numPages}
</span>
<button value=">" onClick={nextPage} >&#xbb;</button>
</span>;
}
renderButtons(): JSX.Element {
if (this.props.parsedResultSets.t === 'ExtensionParsed' && this.paginationEnabled())
if (this.props.parsedResultSets.t === 'ExtensionParsed' && this.paginationAllowed())
return this.renderPageButtons(this.props.parsedResultSets);
else
return <span />;

View File

@@ -17,6 +17,7 @@ import {
NavigatePathMsg,
QueryMetadata,
ResultsPaths,
ALERTS_TABLE_NAME,
} from '../interface-types';
import { EventHandlers as EventHandlerList } from './event-handler-list';
import { ResultTables } from './result-tables';
@@ -193,6 +194,32 @@ class App extends React.Component<{}, ResultsViewState> {
metadata: msg.metadata,
});
this.loadResults();
break;
case 'showInterpretedPage':
this.updateStateWithNewResultsInfo({
resultsPath: '', // FIXME: Not used for interpreted, refactor so this is not needed
parsedResultSets: {
t: 'ExtensionParsed',
numPages: msg.numPages,
numInterpretedPages: msg.numPages,
resultSetNames: msg.resultSetNames,
pageNumber: msg.pageNumber,
resultSet: {
t: 'SarifResultSet',
name: ALERTS_TABLE_NAME,
schema: { name: ALERTS_TABLE_NAME, version: 0, columns: [], tupleCount: 1 },
...msg.interpretation,
},
selectedTable: ALERTS_TABLE_NAME,
},
origResultsPaths: undefined as any, // FIXME: Not used for interpreted, refactor so this is not needed
sortedResultsMap: new Map(), // FIXME: Not used for interpreted, refactor so this is not needed
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering: true,
metadata: msg.metadata,
});
this.loadResults();
break;
case 'resultsUpdating':
@@ -342,8 +369,10 @@ class App extends React.Component<{}, ResultsViewState> {
displayedResults.resultsInfo !== null
) {
const parsedResultSets = displayedResults.resultsInfo.parsedResultSets;
const key = (parsedResultSets.t === 'ExtensionParsed' ? (parsedResultSets.selectedTable || '') + parsedResultSets.pageNumber : '');
return (
<ResultTables
key={key}
parsedResultSets={parsedResultSets}
rawResultSets={displayedResults.results.resultSets}
interpretation={

View File

@@ -7,16 +7,47 @@
.vscode-codeql__table-selection-header {
display: flex;
padding: 0.5em 0;
align-items: center;
}
.vscode-codeql__table-selection-header select {
border: 0;
}
.vscode-codeql__table-selection-header button {
padding: 0.3rem;
margin: 0.2rem;
border-radius: 5px;
color: var(--vscode-editor-foreground);
background-color: var(--vscode-editorGutter-background);
cursor: pointer;
opacity: 0.8;
}
.vscode-codeql__table-selection-header button:hover {
opacity: 1;
}
.vscode-codeql__table-selection-header input[type="number"] {
border-radius: 0;
padding: 0.3rem;
margin: 0.2rem;
width: 2rem;
border-radius: 0;
color: var(--vscode-editor-foreground);
border: 0;
border-bottom: 1px solid var(--vscode-editor-foreground);
background-color: var(--vscode-editorGutter-background);
border-radius: 0;
outline: none;
}
.vscode-codeql__result-table-alert-extras {
display: inline-block;
text-align: left;
margin-left: auto;
background-color: transparent;
color: inherit;
}
.vscode-codeql__result-table-toggle-diagnostics {
@@ -34,11 +65,11 @@
margin: 3px 3px 1px 13px;
}
.vscode-codeql__result-table th {
border-top: 1px solid rgba(88, 96, 105, 0.25);
border-bottom: 1px solid rgba(88, 96, 105, 0.25);
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji;
background: rgba(225, 228, 232, 0.25);
padding: 0.25em 0.5em;
text-align: center;
@@ -146,7 +177,7 @@ td.vscode-codeql__path-index-cell {
.octicon {
fill: var(--vscode-editor-foreground);
margin-top: .25em;
margin-top: 0.25em;
}
.octicon-light {

View File

@@ -25,7 +25,7 @@ describe('databases-ui', () => {
it('should choose parent direcory when db-* is selected', async () => {
const dir = tmp.dirSync().name;
const dbDir = path.join(dir, 'db-hucairz');
const dbDir = path.join(dir, 'db-javascript');
await fs.mkdirs(dbDir);
const uri = await fixDbUri(Uri.file(dbDir));
@@ -34,7 +34,7 @@ describe('databases-ui', () => {
it('should choose parent\'s parent direcory when file selected is in db-*', async () => {
const dir = tmp.dirSync().name;
const dbDir = path.join(dir, 'db-hucairz');
const dbDir = path.join(dir, 'db-javascript');
const file = path.join(dbDir, 'nested');
await fs.mkdirs(dbDir);
await fs.createFile(file);
@@ -42,6 +42,18 @@ describe('databases-ui', () => {
const uri = await fixDbUri(Uri.file(file));
expect(uri.toString()).to.eq(Uri.file(dir).toString());
});
});
it('should handle a parent whose name is db-*', async () => {
// fixes https://github.com/github/vscode-codeql/issues/482
const dir = tmp.dirSync().name;
const parentDir = path.join(dir, 'db-hucairz');
const dbDir = path.join(parentDir, 'db-javascript');
const file = path.join(dbDir, 'nested');
await fs.mkdirs(dbDir);
await fs.createFile(file);
const uri = await fixDbUri(Uri.file(file));
expect(uri.toString()).to.eq(Uri.file(parentDir).toString());
});
});
});

View File

@@ -169,6 +169,42 @@ describe('query-history', () => {
}
});
});
describe('updateCompareWith', () => {
it('should update compareWithItem when there is a single item', () => {
const queryHistory = createMockQueryHistory([]);
queryHistory.updateCompareWith(['a']);
expect(queryHistory.compareWithItem).to.be.eq('a');
});
it('should delete compareWithItem when there are 0 items', () => {
const queryHistory = createMockQueryHistory([]);
queryHistory.compareWithItem = 'a';
queryHistory.updateCompareWith([]);
expect(queryHistory.compareWithItem).to.be.undefined;
});
it('should delete compareWithItem when there are more than 2 items', () => {
const queryHistory = createMockQueryHistory([]);
queryHistory.compareWithItem = 'a';
queryHistory.updateCompareWith(['a', 'b', 'c']);
expect(queryHistory.compareWithItem).to.be.undefined;
});
it('should delete compareWithItem when there are 2 items and disjoint from compareWithItem', () => {
const queryHistory = createMockQueryHistory([]);
queryHistory.compareWithItem = 'a';
queryHistory.updateCompareWith(['b', 'c']);
expect(queryHistory.compareWithItem).to.be.undefined;
});
it('should do nothing when compareWithItem exists and exactly 2 items', () => {
const queryHistory = createMockQueryHistory([]);
queryHistory.compareWithItem = 'a';
queryHistory.updateCompareWith(['a', 'b']);
expect(queryHistory.compareWithItem).to.be.eq('a');
});
});
});
function createMockQueryHistory(allHistory: {}[]) {
@@ -177,6 +213,8 @@ function createMockQueryHistory(allHistory: {}[]) {
findOtherQueryToCompare: (QueryHistoryManager.prototype as any).findOtherQueryToCompare,
treeDataProvider: {
allHistory
}
},
updateCompareWith: (QueryHistoryManager.prototype as any).updateCompareWith,
compareWithItem: undefined as undefined | string,
};
}