Compare commits
898 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c7c1cb0e6 | ||
|
|
a3c4c8fb61 | ||
|
|
73b0a0565f | ||
|
|
8fbde9fba1 | ||
|
|
aca0489fdc | ||
|
|
dae472ca05 | ||
|
|
20f85f2b81 | ||
|
|
81cce9fa8b | ||
|
|
a27ca2e177 | ||
|
|
3e9c2c85d3 | ||
|
|
297fa2ebd3 | ||
|
|
844e58a1b1 | ||
|
|
9948b93b76 | ||
|
|
e1d5a4ddaa | ||
|
|
7059f46141 | ||
|
|
fe6ff6801a | ||
|
|
be3459c1aa | ||
|
|
5ac5de8a5b | ||
|
|
322c1a8835 | ||
|
|
5d6a2e6d7f | ||
|
|
0e79b92829 | ||
|
|
59378daff3 | ||
|
|
125af1139b | ||
|
|
6afdf6357b | ||
|
|
fd7013f754 | ||
|
|
408c042b3b | ||
|
|
b2fceb9b2d | ||
|
|
819e596b9b | ||
|
|
31af28e73b | ||
|
|
c6d8a09f19 | ||
|
|
39d4675b44 | ||
|
|
e74a2e4a15 | ||
|
|
9f85f56055 | ||
|
|
ac57f5005d | ||
|
|
88a9ecbeab | ||
|
|
9f7c7b2ed8 | ||
|
|
1f8070c8b5 | ||
|
|
bc29231fec | ||
|
|
e724577d82 | ||
|
|
b914b97be7 | ||
|
|
9d4b19f91f | ||
|
|
8a66bb4017 | ||
|
|
e55fb8c7a7 | ||
|
|
a6fefdbabb | ||
|
|
2334e4e7b2 | ||
|
|
5c06bcc6bd | ||
|
|
3240809d11 | ||
|
|
3e66e7aaf3 | ||
|
|
71831fe460 | ||
|
|
0983733a67 | ||
|
|
0166f9a557 | ||
|
|
5af0ebcb24 | ||
|
|
9d6c78b656 | ||
|
|
56c83eb480 | ||
|
|
aa0d011daa | ||
|
|
76558b8d41 | ||
|
|
eb5659a628 | ||
|
|
11b4de1820 | ||
|
|
2c8c7cec8f | ||
|
|
1c6d9f3f22 | ||
|
|
f196e34fa5 | ||
|
|
7c7a64ca5b | ||
|
|
dfff7ae8de | ||
|
|
ef267f87bb | ||
|
|
a7800ce3bc | ||
|
|
168af11e00 | ||
|
|
17bab1c09c | ||
|
|
b3092be5d3 | ||
|
|
1909fee91f | ||
|
|
6ff2670ec2 | ||
|
|
0379575256 | ||
|
|
8fd9ebf2d8 | ||
|
|
bed56ef648 | ||
|
|
7ab986fabe | ||
|
|
15d30d5342 | ||
|
|
71f22b9a7a | ||
|
|
32b6ad53cf | ||
|
|
ac2f4475c0 | ||
|
|
108943d135 | ||
|
|
bb0c53d65d | ||
|
|
82d03091d0 | ||
|
|
c1a515ed82 | ||
|
|
0368d537ad | ||
|
|
7059802a25 | ||
|
|
0f4fcdf676 | ||
|
|
ce413a6385 | ||
|
|
8db9f52df3 | ||
|
|
782e413c64 | ||
|
|
fade710f95 | ||
|
|
737a1f5c37 | ||
|
|
7950c1c982 | ||
|
|
f37a6c5e9e | ||
|
|
649179f62e | ||
|
|
8a2630d1b7 | ||
|
|
fa29bcc5fd | ||
|
|
5cd50a67e7 | ||
|
|
f7b6d4c4a4 | ||
|
|
58bffe1edf | ||
|
|
0c9df6edba | ||
|
|
ac0d920156 | ||
|
|
5f2a8fa1d5 | ||
|
|
b55910d2b9 | ||
|
|
e586f3de53 | ||
|
|
08849c1df4 | ||
|
|
aa64459353 | ||
|
|
3c229d244e | ||
|
|
fb70382929 | ||
|
|
f49314f6f3 | ||
|
|
9044d11d2b | ||
|
|
5444a9e55e | ||
|
|
4fa3c459a1 | ||
|
|
2e9a22e86d | ||
|
|
b76bef4246 | ||
|
|
e603de41c1 | ||
|
|
dbd832f1a0 | ||
|
|
5b2093df8f | ||
|
|
af63e5094f | ||
|
|
94db1dff73 | ||
|
|
a44ecadae5 | ||
|
|
a375afd61b | ||
|
|
42ce27b112 | ||
|
|
f86c0b826a | ||
|
|
2d46365406 | ||
|
|
50e89ba1a3 | ||
|
|
7914403da0 | ||
|
|
5be08d7c92 | ||
|
|
df24a705b0 | ||
|
|
fce51ca9a2 | ||
|
|
8a5273058b | ||
|
|
f3274b39d2 | ||
|
|
38e551bb2a | ||
|
|
cc53cd54c4 | ||
|
|
61cc9191f9 | ||
|
|
f2f1b1d6a3 | ||
|
|
64531f5a6f | ||
|
|
29bb7ce01e | ||
|
|
9045253624 | ||
|
|
34ab409050 | ||
|
|
e6f543670a | ||
|
|
04c24d0996 | ||
|
|
41ca0ffba6 | ||
|
|
84f60ccb8c | ||
|
|
0fa0fa3523 | ||
|
|
fe554ee2a7 | ||
|
|
019af98f40 | ||
|
|
c853bafb91 | ||
|
|
c632c38220 | ||
|
|
fd570abdcd | ||
|
|
e81d585336 | ||
|
|
dc55ef9985 | ||
|
|
3fe069975a | ||
|
|
5303ec67cb | ||
|
|
969fdb6337 | ||
|
|
47fa752c5c | ||
|
|
639c8728dd | ||
|
|
3d9f55ffea | ||
|
|
ebd18cd245 | ||
|
|
7571304fb2 | ||
|
|
764830e69d | ||
|
|
9d59abd0a8 | ||
|
|
165542d115 | ||
|
|
d8371708b3 | ||
|
|
c3f4a012a9 | ||
|
|
e2e197f4d9 | ||
|
|
c366342f95 | ||
|
|
b05977516e | ||
|
|
444de8cc3d | ||
|
|
ee759abea9 | ||
|
|
a8f67d72f5 | ||
|
|
118be4a19a | ||
|
|
8d3ae78db2 | ||
|
|
ada5f51e85 | ||
|
|
82f4941415 | ||
|
|
23e1715c4a | ||
|
|
250dc15fe5 | ||
|
|
c3b023cf4b | ||
|
|
52b00fe434 | ||
|
|
64d97aaf7e | ||
|
|
964640c757 | ||
|
|
3d354e1fb4 | ||
|
|
d92708e4a4 | ||
|
|
6c3698bfb3 | ||
|
|
a1d8aac391 | ||
|
|
56095d365c | ||
|
|
fc8b13b8be | ||
|
|
3aa24ebb2c | ||
|
|
205327d8aa | ||
|
|
4969a08531 | ||
|
|
2646716261 | ||
|
|
79d15cc602 | ||
|
|
a7bb74190f | ||
|
|
d990f316d1 | ||
|
|
fe123b3187 | ||
|
|
5d85da5526 | ||
|
|
b3e642a2b4 | ||
|
|
0d8df9ad88 | ||
|
|
2457d4bd9d | ||
|
|
df86adbbfa | ||
|
|
e97ffd2f27 | ||
|
|
088e9aa958 | ||
|
|
e3d8dbc484 | ||
|
|
6daa780fbe | ||
|
|
c0f3adc5ff | ||
|
|
0ab482a389 | ||
|
|
103017d717 | ||
|
|
9048dfd251 | ||
|
|
807069e0c1 | ||
|
|
1fa976757c | ||
|
|
694dcea49a | ||
|
|
83d14501fd | ||
|
|
555d99ca33 | ||
|
|
9dc3df74bb | ||
|
|
877d11dbe6 | ||
|
|
c0c7574891 | ||
|
|
4fda4f71dd | ||
|
|
6356149e54 | ||
|
|
7f32439786 | ||
|
|
27434862c3 | ||
|
|
275c16d5b0 | ||
|
|
5586a02b44 | ||
|
|
baba68d0df | ||
|
|
790a152f42 | ||
|
|
1db2bc048a | ||
|
|
d9917cbe8b | ||
|
|
2bd9c8a732 | ||
|
|
c8ec661dce | ||
|
|
1158dfdb89 | ||
|
|
61974a7664 | ||
|
|
155b83b540 | ||
|
|
bc8d07bc33 | ||
|
|
d87911a803 | ||
|
|
2d5c339e0e | ||
|
|
d151b2f5f9 | ||
|
|
7bc6276115 | ||
|
|
a5021dc4c9 | ||
|
|
77dd9bff94 | ||
|
|
56111b39fc | ||
|
|
0b6d828fa0 | ||
|
|
d22be729be | ||
|
|
9490642522 | ||
|
|
643c106fbd | ||
|
|
9a308f6602 | ||
|
|
582c917541 | ||
|
|
e3625c982f | ||
|
|
6f37f176e4 | ||
|
|
90b0911ed3 | ||
|
|
84506d7340 | ||
|
|
d8c2562bb1 | ||
|
|
d480056c68 | ||
|
|
c3d28e395c | ||
|
|
840cfbf3f6 | ||
|
|
d3466c3a72 | ||
|
|
0ecde78d6e | ||
|
|
e07208b089 | ||
|
|
7176f690f3 | ||
|
|
22aa77ff4c | ||
|
|
a6f189b144 | ||
|
|
2217d3f21f | ||
|
|
9703d10b32 | ||
|
|
53492b5202 | ||
|
|
082a00e81b | ||
|
|
e9bbf112f3 | ||
|
|
9386817727 | ||
|
|
68ce7c3b53 | ||
|
|
a0ba1126cb | ||
|
|
d574f3d94c | ||
|
|
b0cc4c28ed | ||
|
|
11b63f39b4 | ||
|
|
216413e5d7 | ||
|
|
5555fcaded | ||
|
|
68fb744eab | ||
|
|
3de6a110ce | ||
|
|
dd19ebdfdb | ||
|
|
6bbb14edd4 | ||
|
|
fb5675a7c5 | ||
|
|
82a2db9fec | ||
|
|
25d85c3e61 | ||
|
|
81e6d8583a | ||
|
|
ea9dede453 | ||
|
|
e9062551ee | ||
|
|
dd2e79477f | ||
|
|
e9787c2702 | ||
|
|
304330074d | ||
|
|
074229e2a0 | ||
|
|
b679c18b0b | ||
|
|
c3799bdb5a | ||
|
|
daaeb5be3f | ||
|
|
2b346b6873 | ||
|
|
1c6ecf4a5c | ||
|
|
59cc93f94f | ||
|
|
db0fea3af5 | ||
|
|
56d283f6d5 | ||
|
|
fd6cd1f2d2 | ||
|
|
b3d9804842 | ||
|
|
5843c40a37 | ||
|
|
57a4a2f717 | ||
|
|
ff0425d889 | ||
|
|
5fd902257e | ||
|
|
d3e64539d0 | ||
|
|
55761aa4ee | ||
|
|
1bf7fc148a | ||
|
|
590b839166 | ||
|
|
06463a25e6 | ||
|
|
7b2ef6bf76 | ||
|
|
fd57133a41 | ||
|
|
f39bbd325c | ||
|
|
9a53b637e8 | ||
|
|
eb25f31b9f | ||
|
|
7188d8df41 | ||
|
|
badbee1bfb | ||
|
|
2f92ea396a | ||
|
|
1f4790bbb7 | ||
|
|
28abc1e3a6 | ||
|
|
9b7c3bc2bf | ||
|
|
4d73e1a068 | ||
|
|
6f5ac5df4f | ||
|
|
4c880dfb19 | ||
|
|
56e8d8aac7 | ||
|
|
ced9f60949 | ||
|
|
4fa530d69d | ||
|
|
c0a65c994a | ||
|
|
921d9d22e4 | ||
|
|
ead1869a7e | ||
|
|
b1ddf89fe3 | ||
|
|
c37096bf2c | ||
|
|
4127be2905 | ||
|
|
ce29768796 | ||
|
|
571d9d1424 | ||
|
|
65e652b5e4 | ||
|
|
fd2b91d4d4 | ||
|
|
dd4df012e9 | ||
|
|
af167c6d6e | ||
|
|
551ed95fc8 | ||
|
|
9b1ca5136e | ||
|
|
fa5bad6946 | ||
|
|
6229de8634 | ||
|
|
4c14db951b | ||
|
|
bed4e8a060 | ||
|
|
2c6dc24525 | ||
|
|
8d5a00dcc7 | ||
|
|
3ea3cd8e9b | ||
|
|
a32b0736db | ||
|
|
50b0926390 | ||
|
|
4be573c805 | ||
|
|
8bf8adfc74 | ||
|
|
14f58df2fc | ||
|
|
c712b069b1 | ||
|
|
7b4b491a9d | ||
|
|
2a43ffb49a | ||
|
|
5e51bb57f5 | ||
|
|
70ae7284f3 | ||
|
|
396fdb8c5b | ||
|
|
51570c3ba3 | ||
|
|
403e893f93 | ||
|
|
ffd27d4207 | ||
|
|
94b81bae71 | ||
|
|
61f4ce27da | ||
|
|
c2f7d38018 | ||
|
|
b635d914d0 | ||
|
|
8b8912bc40 | ||
|
|
7a5bbb228a | ||
|
|
269af3ed23 | ||
|
|
50f50f59b4 | ||
|
|
abbc13033d | ||
|
|
9508b629f5 | ||
|
|
aabeba067a | ||
|
|
73838ffc6c | ||
|
|
1ab34967a0 | ||
|
|
0f90319691 | ||
|
|
8afdabdd53 | ||
|
|
624e24579e | ||
|
|
c6481ca6ff | ||
|
|
3fbfa840ac | ||
|
|
90ba3d4f29 | ||
|
|
146f23f929 | ||
|
|
207c37c8de | ||
|
|
9aff6ee5a9 | ||
|
|
39d91788dc | ||
|
|
c309bdc1b7 | ||
|
|
f9851a3dbf | ||
|
|
1fad9f6b5e | ||
|
|
d618a4e026 | ||
|
|
feac82f52e | ||
|
|
2c81d3cbb7 | ||
|
|
64f1c8e0aa | ||
|
|
bc51e7462b | ||
|
|
2ebfea1292 | ||
|
|
defc3816e7 | ||
|
|
6ff8aad2e9 | ||
|
|
4d1e61a94d | ||
|
|
8f9e420402 | ||
|
|
6856d1b6a5 | ||
|
|
ebe2f89ca6 | ||
|
|
397be15b89 | ||
|
|
5933f097e7 | ||
|
|
0ec30ee854 | ||
|
|
1b81e8662e | ||
|
|
993bebe213 | ||
|
|
ef8aa14307 | ||
|
|
c6ee20b7fb | ||
|
|
38eb9bbd89 | ||
|
|
5f20ef3df1 | ||
|
|
fc7d48bf3a | ||
|
|
f69a6c523b | ||
|
|
95f46b3b3a | ||
|
|
47c7e9e101 | ||
|
|
c0dc710b30 | ||
|
|
a96265643c | ||
|
|
ab29fb759f | ||
|
|
754f6b2d49 | ||
|
|
3a188faf40 | ||
|
|
b20aeb3e2c | ||
|
|
256b806cd4 | ||
|
|
086df15357 | ||
|
|
c0c30b48af | ||
|
|
3e87a2d53c | ||
|
|
f1227dd2eb | ||
|
|
a59a008d8e | ||
|
|
950a218c29 | ||
|
|
1ae52ef1cc | ||
|
|
851a42f5e3 | ||
|
|
d6ccc11135 | ||
|
|
24eb8fd307 | ||
|
|
ce3e19a2d7 | ||
|
|
c4bed4e8aa | ||
|
|
3464cd0cda | ||
|
|
b04b3bf33e | ||
|
|
a8f36ee9e8 | ||
|
|
6cda6534d1 | ||
|
|
5a9d12ea60 | ||
|
|
f9ef9567cf | ||
|
|
547e18bab9 | ||
|
|
7d7a7060fa | ||
|
|
16464d5e3a | ||
|
|
c0ffd795e0 | ||
|
|
7508ef2e26 | ||
|
|
fb4d6db03d | ||
|
|
39e6b27676 | ||
|
|
d8435d113a | ||
|
|
d386d42f34 | ||
|
|
89860d4f84 | ||
|
|
53ffdc30ce | ||
|
|
e5a48fb46f | ||
|
|
f6d7ddf5b3 | ||
|
|
dbe9807ccc | ||
|
|
b2ea12023c | ||
|
|
255f1b6a5d | ||
|
|
dcdb303da7 | ||
|
|
096e8f6196 | ||
|
|
f7826c3254 | ||
|
|
ad03dfa0a9 | ||
|
|
930def851d | ||
|
|
46670cc7d6 | ||
|
|
b412896d54 | ||
|
|
14eb6b4f89 | ||
|
|
682a1e12bc | ||
|
|
366965adbf | ||
|
|
3917b14a58 | ||
|
|
722f5c5035 | ||
|
|
6f435300e8 | ||
|
|
57f9ff68f8 | ||
|
|
b599885b1c | ||
|
|
514dd7afe3 | ||
|
|
ce56e6f038 | ||
|
|
0138df5da8 | ||
|
|
212273791b | ||
|
|
6ed5c2a583 | ||
|
|
752b6a7f8a | ||
|
|
34bdbd158d | ||
|
|
c71238c6d2 | ||
|
|
202c93b580 | ||
|
|
b03efa4f24 | ||
|
|
1198a05776 | ||
|
|
642b8e5960 | ||
|
|
2b816f9f0f | ||
|
|
708af3cbb9 | ||
|
|
6b8467fc4e | ||
|
|
a9c36ea699 | ||
|
|
ef1fe756cb | ||
|
|
977e3c099d | ||
|
|
7d6461a10a | ||
|
|
f3f8de0e60 | ||
|
|
81502fefec | ||
|
|
ac127b3a3d | ||
|
|
465da69ecf | ||
|
|
648e6745a8 | ||
|
|
5b9c09c958 | ||
|
|
943229f065 | ||
|
|
c42cd9059d | ||
|
|
f7fb6a4920 | ||
|
|
5e8c2fa6db | ||
|
|
f7ab8b7ba0 | ||
|
|
b2fa85f5c5 | ||
|
|
72f3847a3e | ||
|
|
f8d0f689a6 | ||
|
|
235fb83e8c | ||
|
|
5c89952999 | ||
|
|
397aa2befe | ||
|
|
63add84bf3 | ||
|
|
037596cfb4 | ||
|
|
a85281e365 | ||
|
|
99f3d28640 | ||
|
|
41256ec9fd | ||
|
|
74581fb8e3 | ||
|
|
483d674da1 | ||
|
|
cff3d9508e | ||
|
|
24e4708477 | ||
|
|
4f0032d77b | ||
|
|
420fd8d48a | ||
|
|
a901369c5e | ||
|
|
b05697c327 | ||
|
|
0cbc9916fc | ||
|
|
f57b811585 | ||
|
|
0b9dcb44d9 | ||
|
|
a5b95944fc | ||
|
|
6d1c66b121 | ||
|
|
740cbc8dee | ||
|
|
c9f0d1b525 | ||
|
|
66fd8ad6cc | ||
|
|
d0d48dfd08 | ||
|
|
0dd741fa9b | ||
|
|
9c2aa8c7fa | ||
|
|
09e7a84597 | ||
|
|
6e53ed020c | ||
|
|
0eff27731e | ||
|
|
f276a3414a | ||
|
|
0ed03c14bf | ||
|
|
92e2975c24 | ||
|
|
bc181c6c9b | ||
|
|
beb0058365 | ||
|
|
7a9b8bbdde | ||
|
|
d2c795bec0 | ||
|
|
d144874e77 | ||
|
|
cac0add788 | ||
|
|
c493425046 | ||
|
|
302091f2c5 | ||
|
|
05d9f9b3a6 | ||
|
|
69b7e04ec0 | ||
|
|
9184bde8a6 | ||
|
|
99d794c2a8 | ||
|
|
b6aa41d19b | ||
|
|
6257608433 | ||
|
|
6ebeb2b201 | ||
|
|
9f240c8500 | ||
|
|
d4b061115b | ||
|
|
15382a02bc | ||
|
|
c3050fc324 | ||
|
|
4c54778515 | ||
|
|
72412c92c8 | ||
|
|
d780c24994 | ||
|
|
fa8def3c8c | ||
|
|
ad38d6d1ce | ||
|
|
23a8468a26 | ||
|
|
9e54e5eced | ||
|
|
dbca4456a8 | ||
|
|
ecc5fa1e4e | ||
|
|
7ec4b4bc96 | ||
|
|
ba11781165 | ||
|
|
3fa7bec323 | ||
|
|
1120f2fca8 | ||
|
|
2f573aba0c | ||
|
|
a3c6b362f8 | ||
|
|
5f382a5677 | ||
|
|
6114b591c5 | ||
|
|
87465a65ae | ||
|
|
b3c4979358 | ||
|
|
3ba13a32e3 | ||
|
|
3dc2cec5b6 | ||
|
|
72c8e0bcb6 | ||
|
|
160a6cca76 | ||
|
|
4edb83163d | ||
|
|
fbe0df59be | ||
|
|
ddddd2f0b9 | ||
|
|
5098382afb | ||
|
|
7dd5b0a826 | ||
|
|
94968f41e7 | ||
|
|
5ca409797d | ||
|
|
c7bd9f9394 | ||
|
|
db0d41a305 | ||
|
|
2264b6b983 | ||
|
|
012157a57a | ||
|
|
940169e2a0 | ||
|
|
6ebb4affec | ||
|
|
0efbbbfef1 | ||
|
|
610a623bc5 | ||
|
|
37c9414dad | ||
|
|
6cf351f332 | ||
|
|
07f5d2b20d | ||
|
|
51a72227e9 | ||
|
|
e9345dc588 | ||
|
|
2acaf31287 | ||
|
|
9a572a3c71 | ||
|
|
1f52642308 | ||
|
|
af7ec57b72 | ||
|
|
c993d30b34 | ||
|
|
13dd5298bf | ||
|
|
de640e347d | ||
|
|
ca1d63a3fc | ||
|
|
5c5c2e485d | ||
|
|
2dc45ae5f6 | ||
|
|
ad6cbe60fd | ||
|
|
c1392e5c68 | ||
|
|
dcfa990adb | ||
|
|
89483a7b43 | ||
|
|
bc6b829d17 | ||
|
|
2c701e0663 | ||
|
|
0771e93c1e | ||
|
|
64e73c6426 | ||
|
|
cf2dec9577 | ||
|
|
9e25d19924 | ||
|
|
5342b5b83a | ||
|
|
c5494b6fd2 | ||
|
|
1ec39053cb | ||
|
|
0936185d2f | ||
|
|
ec9b5fe1d3 | ||
|
|
3c56c549bc | ||
|
|
407aa14d4d | ||
|
|
76a35deb66 | ||
|
|
d6ab2292e0 | ||
|
|
8e80106b4f | ||
|
|
697a004005 | ||
|
|
4f2b1af811 | ||
|
|
8ed826c78c | ||
|
|
f7a9830455 | ||
|
|
1381cb67d6 | ||
|
|
cc76ca80da | ||
|
|
3316d1712d | ||
|
|
140d369098 | ||
|
|
2c19b32cb3 | ||
|
|
3ce6735475 | ||
|
|
c7f72c5aa9 | ||
|
|
a17e41cbf1 | ||
|
|
3b5b85caed | ||
|
|
708e55244e | ||
|
|
b58d82e53a | ||
|
|
a2df1eaee4 | ||
|
|
d7fae741ae | ||
|
|
dadd3499ac | ||
|
|
526c4f3ab7 | ||
|
|
65623c07a2 | ||
|
|
8a6b361e84 | ||
|
|
bd74b410f6 | ||
|
|
03993f376f | ||
|
|
4dc1b12785 | ||
|
|
c2dcfe92bc | ||
|
|
1bd749dee0 | ||
|
|
e97a7c4993 | ||
|
|
13389fd35f | ||
|
|
9c011c5fd1 | ||
|
|
f97dbb136d | ||
|
|
f154b59b14 | ||
|
|
ec1cf015bf | ||
|
|
79289802c0 | ||
|
|
e1bc8ae95a | ||
|
|
0bd6ba7898 | ||
|
|
84c6c1ef6b | ||
|
|
5b6587cc18 | ||
|
|
e230bf47d0 | ||
|
|
6c523db0ad | ||
|
|
37e3e041e8 | ||
|
|
3a4ce9abb1 | ||
|
|
b0168aa04a | ||
|
|
4009f03dd2 | ||
|
|
160ee22d1b | ||
|
|
fe17f61b5e | ||
|
|
c5722d7aa2 | ||
|
|
f1dbc22a9a | ||
|
|
10776759c7 | ||
|
|
e6c6e8a2be | ||
|
|
07868d90e9 | ||
|
|
40f08393b2 | ||
|
|
c6c7b71b8a | ||
|
|
0bccdbb8d6 | ||
|
|
611f6e39e0 | ||
|
|
a1f5e5b277 | ||
|
|
7b5e5a6407 | ||
|
|
2957979b15 | ||
|
|
e8799ad275 | ||
|
|
bfa6780529 | ||
|
|
80f588c5c8 | ||
|
|
57b6c9bb2c | ||
|
|
80dfe4ba69 | ||
|
|
06bc4c8fe3 | ||
|
|
c62f381a2f | ||
|
|
3547fdfd54 | ||
|
|
ab0e67c251 | ||
|
|
fa1f355b93 | ||
|
|
3777eb382f | ||
|
|
4cb3c4cb50 | ||
|
|
44a0cad146 | ||
|
|
293ec1f204 | ||
|
|
38cbb95ecc | ||
|
|
18ddb3a297 | ||
|
|
2453038387 | ||
|
|
856e516b20 | ||
|
|
6f78fffd10 | ||
|
|
957f6a023c | ||
|
|
afcb84fcd8 | ||
|
|
c19f58b0e3 | ||
|
|
7f1bcedf9b | ||
|
|
817db85c36 | ||
|
|
b4bb1b6ad7 | ||
|
|
ecbb0fb232 | ||
|
|
365519af63 | ||
|
|
ef1444617d | ||
|
|
c72734e49a | ||
|
|
7f05af942b | ||
|
|
20a06238a2 | ||
|
|
91dc39b465 | ||
|
|
f6f8b68ce9 | ||
|
|
47f63f6df6 | ||
|
|
98a29d2459 | ||
|
|
f82fde35ee | ||
|
|
e9830ee854 | ||
|
|
277da3c971 | ||
|
|
78e01e6f92 | ||
|
|
4ae77351c8 | ||
|
|
5c40293c06 | ||
|
|
ba830dd2e5 | ||
|
|
c36da4ba7e | ||
|
|
b9740fe582 | ||
|
|
09c60ecfc8 | ||
|
|
8b661c7a6d | ||
|
|
94823a6c75 | ||
|
|
010e85c28b | ||
|
|
ed11b20cb7 | ||
|
|
8cb0cd23dd | ||
|
|
9f5e9653da | ||
|
|
2b7d2d0610 | ||
|
|
e322bf829a | ||
|
|
25a3ba76ed | ||
|
|
0aa2cd6e90 | ||
|
|
67fa920d4d | ||
|
|
b54dfe9b58 | ||
|
|
4f588d9def | ||
|
|
1c3a6154c9 | ||
|
|
a21382b053 | ||
|
|
6f168459a2 | ||
|
|
77c6706320 | ||
|
|
3fe3117c39 | ||
|
|
e163e0625d | ||
|
|
f1a8564db4 | ||
|
|
34e98c4cc5 | ||
|
|
f325eeb5ab | ||
|
|
bdfb2f29da | ||
|
|
9d457304b1 | ||
|
|
bb63dc52c9 | ||
|
|
033d653d44 | ||
|
|
6bf19eb52f | ||
|
|
477b32662f | ||
|
|
65696997de | ||
|
|
d79b105751 | ||
|
|
1f9e28e09d | ||
|
|
0d8c90a7b5 | ||
|
|
f13f0b3bc3 | ||
|
|
0b6f2c248b | ||
|
|
9cf508837e | ||
|
|
ed2bdd8a43 | ||
|
|
82ce7bc874 | ||
|
|
7bbbf8b986 | ||
|
|
aa950eed6a | ||
|
|
f9777016a7 | ||
|
|
c4df8bf7b2 | ||
|
|
29c29f0b77 | ||
|
|
b5f865432e | ||
|
|
8346eda8b8 | ||
|
|
2a6d581de4 | ||
|
|
b3fcd47930 | ||
|
|
8c558f04f4 | ||
|
|
51d1d3226c | ||
|
|
d69a048a38 | ||
|
|
971fb6edee | ||
|
|
13db377334 | ||
|
|
61c3bef8ff | ||
|
|
0959cb8bbd | ||
|
|
d385a9e7a4 | ||
|
|
1c705da444 | ||
|
|
4dba169412 | ||
|
|
afc6ce5211 | ||
|
|
db9bc5bf5c | ||
|
|
3227935078 | ||
|
|
0902e187c8 | ||
|
|
35a49b018f | ||
|
|
a82f40fb1e | ||
|
|
4db3cd6b1b | ||
|
|
abe5b3c0fc | ||
|
|
7738041ee7 | ||
|
|
f3d0773085 | ||
|
|
1a08ae4df2 | ||
|
|
5e864ae8e0 | ||
|
|
f716032235 | ||
|
|
8e848e3403 | ||
|
|
ae3ed9402d | ||
|
|
306529b4a9 | ||
|
|
48a51fe10b | ||
|
|
bbbbb3a1ee | ||
|
|
bbdbe01e03 | ||
|
|
35d1d8ba6e | ||
|
|
a7be6cf31a | ||
|
|
40a7dd04fb | ||
|
|
24a7348fc2 | ||
|
|
31b80efc56 | ||
|
|
3234f0afe1 | ||
|
|
ae0dfd8089 | ||
|
|
1a6f532c8b | ||
|
|
beb5e37f3a | ||
|
|
9fe4c8cfa9 | ||
|
|
9e1da8f142 | ||
|
|
f1b17d1c46 | ||
|
|
097c048edd | ||
|
|
531f32c1f6 | ||
|
|
46a54a623a | ||
|
|
1b76159c06 | ||
|
|
30e614e26d | ||
|
|
8ca9391115 | ||
|
|
ec8066a11c | ||
|
|
ac27a80769 | ||
|
|
c9087ffaf0 | ||
|
|
25dd679b7d | ||
|
|
16c688f9c8 | ||
|
|
0f3936900c | ||
|
|
3c9c6dc324 | ||
|
|
f11cd17f83 | ||
|
|
824f56d614 | ||
|
|
eddc228a00 | ||
|
|
812205cecf | ||
|
|
a839206846 | ||
|
|
2f07f417fa | ||
|
|
dc6bd07518 | ||
|
|
e890e6cc1f | ||
|
|
6285ba7632 | ||
|
|
be79d68271 | ||
|
|
19a9ad38d5 | ||
|
|
1e84bc9116 | ||
|
|
28abd40963 | ||
|
|
abc025cb39 | ||
|
|
551f76cc4e | ||
|
|
8f34f6af2e | ||
|
|
8c05b3a508 | ||
|
|
7004e94b32 | ||
|
|
74f10a306e | ||
|
|
7e8ce35485 | ||
|
|
758c182a33 | ||
|
|
0c483d1e29 | ||
|
|
4bb3be9fd1 | ||
|
|
f99e5587d0 | ||
|
|
2493b0fd3c | ||
|
|
dbdb4ba57a | ||
|
|
55869e8033 | ||
|
|
47ac9c631e | ||
|
|
decbd52d1b | ||
|
|
952faf5efc | ||
|
|
eb3ba1e229 | ||
|
|
5a3248647b | ||
|
|
4b43b9a140 | ||
|
|
467d43c68c | ||
|
|
fe7d14b136 | ||
|
|
f332e6145a | ||
|
|
6350ac7f66 | ||
|
|
7241e317af | ||
|
|
22ec4b0b6a | ||
|
|
5a42b8b890 | ||
|
|
f2076df73d | ||
|
|
c89d267d55 | ||
|
|
09d744a98e | ||
|
|
2493ddd39a | ||
|
|
0bf1fae2fe | ||
|
|
f7d48916d9 | ||
|
|
ec9078d616 | ||
|
|
8971bee31d | ||
|
|
2723816a5a | ||
|
|
281fe56d2b | ||
|
|
54204167b6 | ||
|
|
c6c6d55bed | ||
|
|
2b1a2cddb1 | ||
|
|
0c2e15a176 | ||
|
|
1aebd895b1 | ||
|
|
8665a81ec1 | ||
|
|
fecefe4468 | ||
|
|
727da3d78c | ||
|
|
468c4a2539 | ||
|
|
18423ca518 | ||
|
|
091d793f13 | ||
|
|
b1bf82d432 | ||
|
|
602289eb6d | ||
|
|
3f39af2840 | ||
|
|
bce5d420d6 | ||
|
|
dc2e17d2f9 | ||
|
|
b2285499a3 | ||
|
|
5ba2a5af1d | ||
|
|
0f1881d2bc | ||
|
|
e18a33074c | ||
|
|
3b197a21e7 | ||
|
|
e987a3535d | ||
|
|
060b7c6099 | ||
|
|
674a126078 | ||
|
|
ed9592c2d7 | ||
|
|
35ce928068 |
2
.gitattributes
vendored
@@ -22,7 +22,7 @@ CHANGELOG.md merge=union
|
||||
|
||||
# Mark some JSON files containing test data as generated so they are not included
|
||||
# as part of diffs or language statistics.
|
||||
extensions/ql-vscode/src/stories/remote-queries/data/*.json linguist-generated
|
||||
extensions/ql-vscode/src/stories/variant-analysis/data/*.json linguist-generated
|
||||
|
||||
# Always use LF line endings, also on Windows
|
||||
* text=auto eol=lf
|
||||
|
||||
82
.github/actions/create-pr/action.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: Create a PR if one doesn't exists
|
||||
description: >
|
||||
Creates a commit with the current changes to the repo, and opens a PR for that commit. If
|
||||
any PR with the same title exists, then this action is marked as succeeded.
|
||||
inputs:
|
||||
commit-message:
|
||||
description: >
|
||||
The message for the commit to be created.
|
||||
required: true
|
||||
|
||||
title:
|
||||
description: >
|
||||
The title of the PR. If empty, the title and body will be determined from the commit message.
|
||||
default: ''
|
||||
required: false
|
||||
|
||||
body:
|
||||
description: >
|
||||
The body (description) of the PR. The `title` input must be specified in order for this input to be used.
|
||||
default: ''
|
||||
required: false
|
||||
|
||||
head-branch:
|
||||
description: >
|
||||
The name of the branch to hold the new commit. If an existing open PR with the same head
|
||||
branch exists, the new branch will be force-pushed to that PR instead of creating a new PR.
|
||||
required: true
|
||||
|
||||
base-branch:
|
||||
description: >
|
||||
The base branch to target with the new PR.
|
||||
required: true
|
||||
|
||||
token:
|
||||
description: |
|
||||
The GitHub token to use. It must have enough privileges to
|
||||
make API calls to create and close pull requests.
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Update git config
|
||||
shell: bash
|
||||
run: |
|
||||
git config --global user.email "github-actions@github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
- name: Commit, Push and Open PR
|
||||
shell: bash
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ inputs.commit-message }}
|
||||
HEAD_BRANCH: ${{ inputs.head-branch }}
|
||||
BASE_BRANCH: ${{ inputs.base-branch }}
|
||||
GH_TOKEN: ${{ inputs.token }}
|
||||
TITLE: ${{ inputs.title }}
|
||||
BODY: ${{ inputs.body }}
|
||||
run: |
|
||||
set -exu
|
||||
if ! [[ $(git diff --stat) != '' ]]; then
|
||||
exit 0 # exit early
|
||||
fi
|
||||
# stage changes in the working tree
|
||||
git add .
|
||||
git commit -m "$COMMIT_MESSAGE"
|
||||
git checkout -b "$HEAD_BRANCH"
|
||||
# CAUTION: gits history changes with the following
|
||||
git push --force origin "$HEAD_BRANCH"
|
||||
PR_JSON=$(gh pr list --state open --json number --head "$HEAD_BRANCH")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to fetch existing PRs."
|
||||
exit 1
|
||||
fi
|
||||
PR_NUMBERS=$(echo $PR_JSON | jq '. | length')
|
||||
if [[ $PR_NUMBERS -ne 0 ]]; then
|
||||
echo "Found existing open PR: $PR_NUMBERS"
|
||||
exit 0
|
||||
fi
|
||||
gh pr create --head "$HEAD_BRANCH" --base "$BASE_BRANCH" --title "$TITLE" --body "$BODY" --assignee ${{ github.actor }} --draft
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to create new PR."
|
||||
exit 1
|
||||
fi
|
||||
2
.github/codeql/codeql-config.yml
vendored
@@ -2,6 +2,8 @@ name: "CodeQL config"
|
||||
queries:
|
||||
- name: Run standard queries
|
||||
uses: security-and-quality
|
||||
- name: Experimental queries
|
||||
uses: security-experimental
|
||||
- name: Run custom javascript queries
|
||||
uses: ./.github/codeql/queries
|
||||
paths:
|
||||
|
||||
134
.github/codeql/queries/unique-command-use.ql
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @name A VS Code command should not be used in multiple locations
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id vscode-codeql/unique-command-use
|
||||
* @description Using each VS Code command from only one location makes
|
||||
* our telemetry more useful, because we can differentiate more user
|
||||
* interactions and know which features of the UI our users are using.
|
||||
* To fix this alert, new commands will need to be made so that each one
|
||||
* is only used from one location. The commands should share the same
|
||||
* implementation so we do not introduce duplicate code.
|
||||
* When fixing this alert, search the codebase for all other references
|
||||
* to the command name. The location of the alert is an arbitrarily
|
||||
* chosen usage of the command, and may not necessarily be the location
|
||||
* that should be changed to fix the alert.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* The name of a VS Code command.
|
||||
*/
|
||||
class CommandName extends string {
|
||||
CommandName() { exists(CommandUsage e | e.getCommandName() = this) }
|
||||
|
||||
/**
|
||||
* In how many ways is this command used. Will always be at least 1.
|
||||
*/
|
||||
int getNumberOfUsages() { result = count(this.getAUse()) }
|
||||
|
||||
/**
|
||||
* Get a usage of this command.
|
||||
*/
|
||||
CommandUsage getAUse() { result.getCommandName() = this }
|
||||
|
||||
/**
|
||||
* Get the canonical first usage of this command, to use for the location
|
||||
* of the alert. The implementation of this ordering of usages is arbitrary
|
||||
* and the usage given may not be the one that should be changed when fixing
|
||||
* the alert.
|
||||
*/
|
||||
CommandUsage getFirstUsage() {
|
||||
result =
|
||||
max(CommandUsage use |
|
||||
use = this.getAUse()
|
||||
|
|
||||
use
|
||||
order by
|
||||
use.getFile().getRelativePath(), use.getLocation().getStartLine(),
|
||||
use.getLocation().getStartColumn()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single usage of a command, either from within code or
|
||||
* from the command's definition in package.json
|
||||
*/
|
||||
abstract class CommandUsage extends Locatable {
|
||||
abstract string getCommandName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from the typescript code, by calling `executeCommand`.
|
||||
*/
|
||||
class CommandUsageCallExpr extends CommandUsage, CallExpr {
|
||||
CommandUsageCallExpr() {
|
||||
this.getCalleeName() = "executeCommand" and
|
||||
this.getArgument(0).(StringLiteral).getValue().matches("%codeQL%") and
|
||||
not this.getFile().getRelativePath().matches("extensions/ql-vscode/test/%")
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A usage of a command from any menu that isn't the command palette.
|
||||
* This means a user could invoke the command by clicking on a button in
|
||||
* something like a menu or a dropdown.
|
||||
*/
|
||||
class CommandUsagePackageJsonMenuItem extends CommandUsage, JsonObject {
|
||||
CommandUsagePackageJsonMenuItem() {
|
||||
exists(this.getPropValue("command")) and
|
||||
exists(PackageJson packageJson, string menuName |
|
||||
packageJson
|
||||
.getPropValue("contributes")
|
||||
.getPropValue("menus")
|
||||
.getPropValue(menuName)
|
||||
.getElementValue(_) = this and
|
||||
menuName != "commandPalette"
|
||||
)
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given command disabled for use in the command palette by
|
||||
* a block with a `"when": "false"` field.
|
||||
*/
|
||||
predicate isDisabledInCommandPalette(string commandName) {
|
||||
exists(PackageJson packageJson, JsonObject commandPaletteObject |
|
||||
packageJson
|
||||
.getPropValue("contributes")
|
||||
.getPropValue("menus")
|
||||
.getPropValue("commandPalette")
|
||||
.getElementValue(_) = commandPaletteObject and
|
||||
commandPaletteObject.getPropValue("command").getStringValue() = commandName and
|
||||
commandPaletteObject.getPropValue("when").getStringValue() = "false"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a command being usable from the command palette.
|
||||
* This means that a user could choose to manually invoke the command.
|
||||
*/
|
||||
class CommandUsagePackageJsonCommandPalette extends CommandUsage, JsonObject {
|
||||
CommandUsagePackageJsonCommandPalette() {
|
||||
this.getFile().getBaseName() = "package.json" and
|
||||
exists(this.getPropValue("command")) and
|
||||
exists(PackageJson packageJson |
|
||||
packageJson.getPropValue("contributes").getPropValue("commands").getElementValue(_) = this
|
||||
) and
|
||||
not isDisabledInCommandPalette(this.getPropValue("command").getStringValue())
|
||||
}
|
||||
|
||||
override string getCommandName() { result = this.getPropValue("command").getStringValue() }
|
||||
}
|
||||
|
||||
from CommandName c
|
||||
where c.getNumberOfUsages() > 1
|
||||
select c.getFirstUsage(),
|
||||
"The " + c + " command is used from " + c.getNumberOfUsages() + " locations"
|
||||
|
||||
2
.github/dependabot.yml
vendored
@@ -11,7 +11,7 @@ updates:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: ".github"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "thursday" # Thursday is arbitrary
|
||||
|
||||
61
.github/workflows/bump-cli.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Bump CLI version
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
option:
|
||||
description: "Option"
|
||||
required: true
|
||||
default: 'replace'
|
||||
type: choice
|
||||
options:
|
||||
- prepend
|
||||
- replace
|
||||
version:
|
||||
description: |
|
||||
The version to prepend to the supported versions file. This should be in the form: `vA.B.C`.
|
||||
required: false
|
||||
type: string
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- .github/actions/create-pr/action.yml
|
||||
- .github/workflows/bump-cli.yml
|
||||
schedule:
|
||||
- cron: 0 0 */14 * * # run every 14 days
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Bump CLI
|
||||
if: ${{ inputs.option == 'replace' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
scripts/replace-cli-version.sh
|
||||
- name: Prepend another version
|
||||
if: ${{ inputs.option == 'prepend' }}
|
||||
run: |
|
||||
cat extensions/ql-vscode/supported_cli_versions.json | jq '. |= ["${{ inputs.version }}"] + .' > supported_cli_versions_temp.json
|
||||
mv supported_cli_versions_temp.json extensions/ql-vscode/supported_cli_versions.json
|
||||
echo "LATEST_VERSION=${{ inputs.version }}" >> $GITHUB_ENV
|
||||
echo "PREVIOUS_VERSION=`jq -r '.[1]' extensions/ql-vscode/supported_cli_versions.json`" >> $GITHUB_ENV
|
||||
- name: Commit, Push and Open a PR
|
||||
uses: ./.github/actions/create-pr
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
base-branch: main
|
||||
head-branch: github-action/bump-cli
|
||||
commit-message: Bump CLI version from ${{ env.PREVIOUS_VERSION }} to ${{ env.LATEST_VERSION }} for integration tests
|
||||
title: Bump CLI Version to ${{ env.LATEST_VERSION }} for integration tests
|
||||
body: >
|
||||
Bumps CLI version from ${{ env.PREVIOUS_VERSION }} to ${{ env.LATEST_VERSION }}
|
||||
2
.github/workflows/dependency-review.yml
vendored
@@ -13,4 +13,4 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v1
|
||||
uses: actions/dependency-review-action@v3
|
||||
|
||||
30
.github/workflows/main.yml
vendored
@@ -132,7 +132,12 @@ jobs:
|
||||
- name: Run unit tests
|
||||
working-directory: extensions/ql-vscode
|
||||
run: |
|
||||
npm run test
|
||||
npm run test:unit
|
||||
|
||||
- name: Run view tests
|
||||
working-directory: extensions/ql-vscode
|
||||
run: |
|
||||
npm run test:view
|
||||
|
||||
test:
|
||||
name: Test
|
||||
@@ -173,7 +178,7 @@ jobs:
|
||||
VSCODE_CODEQL_GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
run: |
|
||||
unset DBUS_SESSION_BUS_ADDRESS
|
||||
/usr/bin/xvfb-run npm run integration
|
||||
/usr/bin/xvfb-run npm run test:vscode-integration
|
||||
|
||||
- name: Run integration tests (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -181,16 +186,27 @@ jobs:
|
||||
env:
|
||||
VSCODE_CODEQL_GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
run: |
|
||||
npm run integration
|
||||
npm run test:vscode-integration
|
||||
|
||||
set-matrix:
|
||||
name: Set Matrix for cli-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set the variables
|
||||
id: set-variables
|
||||
run: echo "cli-versions=$(cat ./extensions/ql-vscode/supported_cli_versions.json | jq -rc)" >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
cli-versions: ${{ steps.set-variables.outputs.cli-versions }}
|
||||
cli-test:
|
||||
name: CLI Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [find-nightly]
|
||||
needs: [find-nightly, set-matrix]
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
version: ['v2.7.6', 'v2.8.5', 'v2.9.4', 'v2.10.5', 'v2.11.5', 'nightly']
|
||||
version: ${{ fromJson(needs.set-matrix.outputs.cli-versions) }}
|
||||
fail-fast: false
|
||||
env:
|
||||
CLI_VERSION: ${{ matrix.version }}
|
||||
@@ -243,10 +259,10 @@ jobs:
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
unset DBUS_SESSION_BUS_ADDRESS
|
||||
/usr/bin/xvfb-run npm run cli-integration
|
||||
/usr/bin/xvfb-run npm run test:cli-integration
|
||||
|
||||
- name: Run CLI tests (Windows)
|
||||
working-directory: extensions/ql-vscode
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
npm run cli-integration
|
||||
npm run test:cli-integration
|
||||
|
||||
32
.github/workflows/release.yml
vendored
@@ -54,11 +54,17 @@ jobs:
|
||||
echo "ref_name=$REF_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: vscode-codeql-extension
|
||||
path: artifacts
|
||||
|
||||
- name: Upload source maps
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: vscode-codeql-sourcemaps
|
||||
path: dist/vscode-codeql/out/*.map
|
||||
|
||||
# TODO Run tests, or check that a test run on the same branch succeeded.
|
||||
|
||||
- name: Create release
|
||||
@@ -88,6 +94,23 @@ jobs:
|
||||
asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Create sourcemap ZIP file
|
||||
run: |
|
||||
cd dist/vscode-codeql/out
|
||||
zip -r ../../vscode-codeql-sourcemaps.zip *.map
|
||||
|
||||
- name: Upload sourcemap ZIP file
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
if: success()
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# Get the `upload_url` from the `create-release` step above.
|
||||
upload_url: ${{ steps.create-release.outputs.upload_url }}
|
||||
asset_path: dist/vscode-codeql-sourcemaps.zip
|
||||
asset_name: ${{ format('vscode-codeql-sourcemaps-{0}.zip', steps.prepare-artifacts.outputs.ref_name) }}
|
||||
asset_content_type: application/zip
|
||||
|
||||
###
|
||||
# Do Post release work: version bump and changelog PR
|
||||
# Only do this if we are running from a PR (ie- this is part of the release process)
|
||||
@@ -116,16 +139,15 @@ jobs:
|
||||
perl -i -pe 's/^/## \[UNRELEASED\]\n\n/ if($.==3)' CHANGELOG.md
|
||||
|
||||
- name: Create version bump PR
|
||||
uses: peter-evans/create-pull-request@c7f493a8000b8aeb17a1332e326ba76b57cb83eb # v3.4.1
|
||||
uses: ./.github/actions/create-pr
|
||||
if: success()
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Bump version to ${{ steps.bump-patch-version.outputs.next_version }}
|
||||
title: Bump version to ${{ steps.bump-patch-version.outputs.next_version }}
|
||||
body: This PR was automatically generated by the GitHub Actions release workflow in this repository.
|
||||
branch: ${{ format('version/bump-to-{0}', steps.bump-patch-version.outputs.next_version) }}
|
||||
base: main
|
||||
draft: true
|
||||
head-branch: ${{ format('version/bump-to-{0}', steps.bump-patch-version.outputs.next_version) }}
|
||||
base-branch: main
|
||||
|
||||
vscode-publish:
|
||||
name: Publish to VS Code Marketplace
|
||||
|
||||
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
cd extensions/ql-vscode && npm run format-staged
|
||||
4
.husky/pre-push
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
cd extensions/ql-vscode && ./scripts/forbid-test-only
|
||||
9
.vscode/launch.json
vendored
@@ -11,6 +11,7 @@
|
||||
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
|
||||
// Add a reference to a workspace to open. Eg-
|
||||
// "${workspaceRoot}/../vscode-codeql-starter/vscode-codeql-starter.code-workspace"
|
||||
// "${workspaceRoot}/../codespaces-codeql/tutorial.code-workspace"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
@@ -38,7 +39,7 @@
|
||||
},
|
||||
"args": [
|
||||
"--projects",
|
||||
"test"
|
||||
"test/unit-tests"
|
||||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
@@ -94,7 +95,7 @@
|
||||
"cwd": "${workspaceFolder}/extensions/ql-vscode",
|
||||
"args": [
|
||||
"--projects",
|
||||
"src/vscode-tests/no-workspace"
|
||||
"test/vscode-tests/no-workspace"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
@@ -110,7 +111,7 @@
|
||||
"cwd": "${workspaceFolder}/extensions/ql-vscode",
|
||||
"args": [
|
||||
"--projects",
|
||||
"src/vscode-tests/minimal-workspace"
|
||||
"test/vscode-tests/minimal-workspace"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"console": "integratedTerminal",
|
||||
@@ -126,7 +127,7 @@
|
||||
"cwd": "${workspaceFolder}/extensions/ql-vscode",
|
||||
"args": [
|
||||
"--projects",
|
||||
"src/vscode-tests/cli-integration"
|
||||
"test/vscode-tests/cli-integration"
|
||||
],
|
||||
"env": {
|
||||
// Optionally, set the version to use for the integration tests.
|
||||
|
||||
19
.vscode/settings.json
vendored
@@ -42,14 +42,29 @@
|
||||
"LANG": "en-US",
|
||||
"TZ": "UTC"
|
||||
},
|
||||
|
||||
// These options are used by the `jestrunner.debug` command.
|
||||
// They are not used by the `jestrunner.run` command.
|
||||
// After clicking "debug" over a test, continually invoke the
|
||||
// "Debug: Attach to Node Process" command until you see a
|
||||
// process named "Code Helper (Plugin)". Then click "attach".
|
||||
// This will attach the debugger to the test process.
|
||||
"jestrunner.debugOptions": {
|
||||
// Uncomment to debug integration tests
|
||||
// "attachSimplePort": 9223,
|
||||
"attachSimplePort": 9223,
|
||||
"env": {
|
||||
"LANG": "en-US",
|
||||
"TZ": "UTC",
|
||||
|
||||
// Uncomment to set a custom path to a CodeQL checkout.
|
||||
// "TEST_CODEQL_PATH": "/absolute/path/to/checkout/of/codeql",
|
||||
|
||||
// Uncomment to set a custom path to a CodeQL CLI executable.
|
||||
// This is the CodeQL version that will be used in the tests.
|
||||
// "CLI_PATH": "/absolute/path/to/custom/codeql",
|
||||
|
||||
// Uncomment to debug integration tests
|
||||
// "VSCODE_WAIT_FOR_DEBUGGER": "true",
|
||||
"VSCODE_WAIT_FOR_DEBUGGER": "true",
|
||||
}
|
||||
},
|
||||
"terminal.integrated.env.linux": {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
**/* @github/codeql-vscode-reviewers
|
||||
**/remote-queries/ @github/code-scanning-secexp-reviewers
|
||||
**/variant-analysis/ @github/code-scanning-secexp-reviewers
|
||||
**/databases/ @github/code-scanning-secexp-reviewers
|
||||
|
||||
199
CONTRIBUTING.md
@@ -2,7 +2,7 @@
|
||||
|
||||
[fork]: https://github.com/github/vscode-codeql/fork
|
||||
[pr]: https://github.com/github/vscode-codeql/compare
|
||||
[style]: https://primer.style
|
||||
[style]: https://github.com/microsoft/vscode-webview-ui-toolkit
|
||||
[code-of-conduct]: CODE_OF_CONDUCT.md
|
||||
|
||||
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
|
||||
@@ -93,202 +93,7 @@ More information about Storybook can be found inside the **Overview** page once
|
||||
|
||||
### Testing
|
||||
|
||||
We have several types of tests:
|
||||
|
||||
* Unit tests: these live in the `tests/pure-tests/` directory
|
||||
* View tests: these live in `src/view/variant-analysis/__tests__/`
|
||||
* VSCode integration tests: these live in `src/vscode-tests/no-workspace` and `src/vscode-tests/minimal-workspace`
|
||||
* CLI integration tests: these live in `src/vscode-tests/cli-integration`
|
||||
|
||||
The CLI integration tests require an instance of the CodeQL CLI to run so they will require some extra setup steps. When adding new tests to our test suite, please be mindful of whether they need to be in the cli-integration folder. If the tests don't depend on the CLI, they are better suited to being a VSCode integration test.
|
||||
|
||||
Any test data you're using (sample projects, config files, etc.) must go in a `src/vscode-tests/*/data` directory. When you run the tests, the test runner will copy the data directory to `out/vscode-tests/*/data`.
|
||||
|
||||
#### Running the tests
|
||||
|
||||
Pre-requisites:
|
||||
1. Run `npm run build`.
|
||||
2. You will need to have `npm run watch` running in the background.
|
||||
|
||||
##### 1. From the terminal
|
||||
|
||||
Then, from the `extensions/ql-vscode` directory, use the appropriate command to run the tests:
|
||||
|
||||
* Unit tests: `npm run test:unit`
|
||||
* View Tests: `npm test:view`
|
||||
* VSCode integration tests: `npm run integration`
|
||||
|
||||
###### CLI integration tests
|
||||
|
||||
The CLI integration tests require the CodeQL standard libraries in order to run so you will need to clone a local copy of the `github/codeql` repository.
|
||||
|
||||
1. Set the `TEST_CODEQL_PATH` environment variable: running from a terminal, you _must_ set the `TEST_CODEQL_PATH` variable to point to a checkout of the `github/codeql` repository. The appropriate CLI version will be downloaded as part of the test.
|
||||
|
||||
2. Run your test command:
|
||||
|
||||
```shell
|
||||
cd extensions/ql-vscode && npm run cli-integration
|
||||
```
|
||||
|
||||
##### 2. From VSCode
|
||||
|
||||
Alternatively, you can run the tests inside of VSCode. There are several VSCode launch configurations defined that run the unit and integration tests.
|
||||
|
||||
You will need to run tests using a task from inside of VS Code, under the "Run and Debug" view:
|
||||
|
||||
* Unit tests: run the _Launch Unit Tests - React_ task
|
||||
* View Tests: run the _Launch Unit Tests_ task
|
||||
* VSCode integration tests: run the _Launch Unit Tests - No Workspace_ and _Launch Unit Tests - Minimal Workspace_ tasks
|
||||
|
||||
###### CLI integration tests
|
||||
|
||||
The CLI integration tests require the CodeQL standard libraries in order to run so you will need to clone a local copy of the `github/codeql` repository.
|
||||
|
||||
1. Set the `TEST_CODEQL_PATH` environment variable: running from a terminal, you _must_ set the `TEST_CODEQL_PATH` variable to point to a checkout of the `github/codeql` repository. The appropriate CLI version will be downloaded as part of the test.
|
||||
|
||||
2. Set the codeql path in VSCode's launch configuration: open `launch.json` and under the _Launch Integration Tests - With CLI_ section, uncomment the `"${workspaceRoot}/../codeql"` line. If you've cloned the `github/codeql` repo to a different path, replace the value with the correct path.
|
||||
|
||||
3. Run the VSCode task from the "Run and Debug" view called _Launch Integration Tests - With CLI_.
|
||||
|
||||
#### Running a single test
|
||||
|
||||
##### 1. From the terminal
|
||||
|
||||
The easiest way to run a single test is to change the `it` of the test to `it.only` and then run the test command with some additional options
|
||||
to only run tests for this specific file. For example, to run the test `src/vscode-tests/cli-integration/run-queries.test.ts`:
|
||||
|
||||
```shell
|
||||
npm run cli-integration -- --runTestsByPath src/vscode-tests/cli-integration/run-queries.test.ts
|
||||
```
|
||||
|
||||
You can also use the `--testNamePattern` option to run a specific test within a file. For example, to run the test `src/vscode-tests/cli-integration/run-queries.test.ts`:
|
||||
|
||||
```shell
|
||||
npm run cli-integration -- --runTestsByPath src/vscode-tests/cli-integration/run-queries.test.ts --testNamePattern "should create a QueryEvaluationInfo"
|
||||
```
|
||||
|
||||
##### 2. From VSCode
|
||||
|
||||
Alternatively, you can run a single test inside VSCode. To do so, install the [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) extension. Then,
|
||||
you will have quicklinks to run a single test from within test files. To run a single unit or integration test, click the "Run" button. Debugging a single test is currently only supported
|
||||
for unit tests by default. To debug integration tests, open the `.vscode/settings.json` file and uncomment the `jestrunner.debugOptions` lines. This will allow you to debug integration tests.
|
||||
Please make sure to revert this change before committing; with this setting enabled, it is not possible to debug unit tests.
|
||||
|
||||
Without the Jest Runner extension, you can also use the "Launch Selected Unit Test (vscode-codeql)" launch configuration to run a single unit test.
|
||||
|
||||
#### Using a mock GitHub API server
|
||||
|
||||
Multi-Repo Variant Analyses (MRVA) rely on the GitHub API. In order to make development and testing easy, we have functionality that allows us to intercept requests to the GitHub API and provide mock responses.
|
||||
|
||||
##### Using a pre-recorded test scenario
|
||||
|
||||
To run a mock MRVA scenario, follow these steps:
|
||||
1. Enable the mock GitHub API server by adding the following in your VS Code user settings (which can be found by running the `Preferences: Open User Settings (JSON)` VS Code command):
|
||||
```json
|
||||
"codeQL.mockGitHubApiServer": {
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
1. Run the `CodeQL: Mock GitHub API Server: Load Scenario` command from the command pallet, and choose one of the scenarios to load.
|
||||
1. Execute a normal MRVA. At this point you should see the scenario being played out, rather than an actual MRVA running.
|
||||
1. Once you're done, you can stop using the mock scenario with `CodeQL: Mock GitHub API Server: Unload Scenario`
|
||||
|
||||
If you want to replay the same scenario you should unload and reload it so requests are replayed from the start.
|
||||
|
||||
##### Recording a new test scenario
|
||||
To record a new mock MRVA scenario, follow these steps:
|
||||
|
||||
1. Enable the mock GitHub API server by adding the following in your VS Code user settings (which can be found by running the `Preferences: Open User Settings (JSON)` VS Code command):
|
||||
```json
|
||||
"codeQL.mockGitHubApiServer": {
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
1. Run the `CodeQL: Mock GitHub API Server: Start Scenario Recording` VS Code command from the command pallet.
|
||||
1. Execute a normal MRVA.
|
||||
1. Once what you wanted to record is done (e.g. the MRVA has finished), then run the `CodeQL: Mock GitHub API Server: Save Scenario` command from the command pallet.
|
||||
1. The scenario should then be available for replaying.
|
||||
|
||||
If you want to cancel recording, run the `CodeQL: Mock GitHub API Server: Cancel Scenario Recording` command.
|
||||
|
||||
Once the scenario has been recorded, it's often useful to remove some of the requests to speed up the replay, particularly ones that fetch the variant analysis status. Once some of the request files have manually been removed, the [fix-scenario-file-numbering script](./extensions/ql-vscode/scripts/fix-scenario-file-numbering.ts) can be used to update the number of the files. See the script file for details on how to use.
|
||||
|
||||
#### Scenario data location
|
||||
|
||||
Pre-recorded scenarios are stored in `./src/mocks/scenarios`. However, it's possible to configure the location, by setting the `codeQL.mockGitHubApiServer.scenariosPath` configuration property in the VS Code user settings.
|
||||
|
||||
## Releasing (write access required)
|
||||
|
||||
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
|
||||
* Go through all recent PRs and make sure they are properly accounted for.
|
||||
* Make sure all changelog entries have links back to their PR(s) if appropriate.
|
||||
1. Double-check that the node version we're using matches the one used for VS Code. If it doesn't, you will then need to update the node version in the following files:
|
||||
* `.nvmrc` - this will enable `nvm` to automatically switch to the correct node version when you're in the project folder
|
||||
* `.github/workflows/main.yml` - all the "node-version: <version>" settings
|
||||
* `.github/workflows/release.yml` - the "node-version: <version>" setting
|
||||
1. Double-check that the extension `package.json` and `package-lock.json` have the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
|
||||
1. Create a PR for this release:
|
||||
* This PR will contain any missing bits from steps 1 and 2. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
|
||||
* Create a new branch for the release named after the new version. For example: `v1.3.6`
|
||||
* Create a new commit with a message the same as the branch name.
|
||||
* Create a PR for this branch.
|
||||
* Wait for the PR to be merged into `main`
|
||||
1. Switch to `main` and add a new tag on the `main` branch with your new version (named after the release), e.g.
|
||||
```bash
|
||||
git checkout main
|
||||
git tag v1.3.6
|
||||
```
|
||||
|
||||
If you've accidentally created a badly named tag, you can delete it via
|
||||
```bash
|
||||
git tag -d badly-named-tag
|
||||
```
|
||||
1. Push the new tag up:
|
||||
|
||||
a. If you're using a fork of the repo:
|
||||
|
||||
```bash
|
||||
git push upstream refs/tags/v1.3.6
|
||||
```
|
||||
|
||||
b. If you're working straight in this repo:
|
||||
|
||||
```bash
|
||||
git push origin refs/tags/v1.3.6
|
||||
```
|
||||
|
||||
This will trigger [a release build](https://github.com/github/vscode-codeql/releases) on Actions.
|
||||
|
||||
* **IMPORTANT** Make sure you are on the `main` branch and your local checkout is fully updated when you add the tag.
|
||||
* If you accidentally add the tag to the wrong ref, you can just force push it to the right one later.
|
||||
1. Monitor the status of the release build in the `Release` workflow in the Actions tab.
|
||||
* DO NOT approve the "publish" stages of the workflow yet.
|
||||
1. Download the VSIX from the draft GitHub release at the top of [the releases page](https://github.com/github/vscode-codeql/releases) that is created when the release build finishes.
|
||||
1. Unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
|
||||
or look at the source if there's any doubt the right code is being shipped.
|
||||
1. Install the `.vsix` file into your vscode IDE and ensure the extension can load properly. Run a single command (like run query, or add database).
|
||||
1. Go to the actions tab of the vscode-codeql repository and select the [Release workflow](https://github.com/github/vscode-codeql/actions?query=workflow%3ARelease).
|
||||
- If there is an authentication failure when publishing, be sure to check that the authentication keys haven't expired. See below.
|
||||
1. Approve the deployments of the correct Release workflow. This will automatically publish to Open VSX and VS Code Marketplace.
|
||||
1. Go to the draft GitHub release in [the releases tab of the repository](https://github.com/github/vscode-codeql/releases), click 'Edit', add some summary description, and publish it.
|
||||
1. Confirm the new release is marked as the latest release at <https://github.com/github/vscode-codeql/releases>.
|
||||
1. If documentation changes need to be published, notify documentation team that release has been made.
|
||||
1. Review and merge the version bump PR that is automatically created by Actions.
|
||||
|
||||
## Secrets and authentication for publishing
|
||||
|
||||
Repository administrators, will need to manage the authentication keys for publishing to the VS Code marketplace and Open VSX. Each requires an authentication token. The VS Code marketplace token expires yearly.
|
||||
|
||||
To regenerate the Open VSX token:
|
||||
|
||||
1. Log in to the [user settings page on Open VSX](https://open-vsx.org/user-settings/namespaces).
|
||||
1. Make sure you are a member of the GitHub namespace.
|
||||
1. Go to the [Access Tokens](https://open-vsx.org/user-settings/tokens) page and generate a new token.
|
||||
1. Update the secret in the `publish-open-vsx` environment in the project settings.
|
||||
|
||||
To regenerate the VSCode Marketplace token, please see our internal documentation. Note that Azure DevOps PATs expire every 90 days and must be regenerated.
|
||||
Information about testing can be found [here](./docs/testing.md).
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ The extension is released. You can download it from the [Visual Studio Marketpla
|
||||
To see what has changed in the last few versions of the extension, see the [Changelog](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/CHANGELOG.md).
|
||||
|
||||
[](https://github.com/github/vscode-codeql/actions?query=workflow%3A%22Build+Extension%22+branch%3Amaster)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -15,6 +15,7 @@ To see what has changed in the last few versions of the extension, see the [Chan
|
||||
* Shows the flow of data through the results of path queries, which is essential for triaging security results.
|
||||
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/github/codeql).
|
||||
* Adds IntelliSense to support you writing and editing your own CodeQL query and library files.
|
||||
* Supports you running CodeQL queries against thousands of repositories on GitHub using multi-repository variant analysis.
|
||||
|
||||
## Project goals and scope
|
||||
|
||||
|
||||
BIN
docs/images/highlighted-code-snippet.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/images/results-table.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
89
docs/releasing.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Releasing (write access required)
|
||||
|
||||
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
|
||||
* Go through all recent PRs and make sure they are properly accounted for.
|
||||
* Make sure all changelog entries have links back to their PR(s) if appropriate.
|
||||
* For picking the new version number, we default to increasing the patch version number, but make our own judgement about whether a change is big enough to warrant a minor version bump. Common reasons for a minor bump could include:
|
||||
* Making substantial new features available to all users. This can include lifting a feature flag.
|
||||
* Breakage in compatibility with recent versions of the CLI.
|
||||
* Minimum required version of VS Code is increased.
|
||||
* New telemetry events are added.
|
||||
* Deprecation or removal of commands.
|
||||
* Accumulation of many changes, none of which are individually big enough to warrant a minor bump, but which together are. This does not include changes which are purely internal to the extension, such as refactoring, or which are only available behind a feature flag.
|
||||
1. Double-check that the node version we're using matches the one used for VS Code. If it doesn't, you will then need to update the node version in the following files:
|
||||
* `.nvmrc` - this will enable `nvm` to automatically switch to the correct node version when you're in the project folder
|
||||
* `.github/workflows/main.yml` - all the "node-version: <version>" settings
|
||||
* `.github/workflows/release.yml` - the "node-version: <version>" setting
|
||||
1. Double-check that the extension `package.json` and `package-lock.json` have the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
|
||||
1. Create a PR for this release:
|
||||
* This PR will contain any missing bits from steps 1, 2 and 3. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
|
||||
* Create a new branch for the release named after the new version. For example: `v1.3.6`
|
||||
* Create a new commit with a message the same as the branch name.
|
||||
* Create a PR for this branch.
|
||||
* Wait for the PR to be merged into `main`
|
||||
1. Switch to `main` branch and pull latest changes
|
||||
1. Lock the `main` branch.
|
||||
* Go to the [branch protection rules for the `main` branch](https://github.com/github/vscode-codeql/settings/branch_protection_rules/16447115)
|
||||
* Select "Lock branch"
|
||||
* Click "Save changes"
|
||||
1. Ensure that no PRs have been merged since the release PR that you merged. If there were, you might need to unlock `main` temporarily and update the CHANGELOG again.
|
||||
1. Build the extension `npm run build` and install it on your VS Code using "Install from VSIX".
|
||||
1. Go through [our test plan](./test-plan.md) to ensure that the extension is working as expected.
|
||||
1. Switch to `main` and add a new tag on the `main` branch with your new version (named after the release), e.g.
|
||||
```bash
|
||||
git checkout main
|
||||
git tag v1.3.6
|
||||
```
|
||||
|
||||
If you've accidentally created a badly named tag, you can delete it via
|
||||
```bash
|
||||
git tag -d badly-named-tag
|
||||
```
|
||||
1. Unlock the main branch
|
||||
* Go to the [branch protection rules for the `main` branch](https://github.com/github/vscode-codeql/settings/branch_protection_rules/16447115)
|
||||
* Deselect "Lock branch"
|
||||
* Click "Save changes"
|
||||
1. Push the new tag up:
|
||||
|
||||
a. If you're using a fork of the repo:
|
||||
|
||||
```bash
|
||||
git push upstream refs/tags/v1.3.6
|
||||
```
|
||||
|
||||
b. If you're working straight in this repo:
|
||||
|
||||
```bash
|
||||
git push origin refs/tags/v1.3.6
|
||||
```
|
||||
|
||||
This will trigger [a release build](https://github.com/github/vscode-codeql/releases) on Actions.
|
||||
|
||||
* **IMPORTANT** Make sure you are on the `main` branch and your local checkout is fully updated when you add the tag.
|
||||
* If you accidentally add the tag to the wrong ref, you can just force push it to the right one later.
|
||||
1. Monitor the status of the release build in the `Release` workflow in the Actions tab.
|
||||
* DO NOT approve the "publish" stages of the workflow yet.
|
||||
1. Download the VSIX from the draft GitHub release at the top of [the releases page](https://github.com/github/vscode-codeql/releases) that is created when the release build finishes.
|
||||
1. Unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
|
||||
or look at the source if there's any doubt the right code is being shipped.
|
||||
1. Install the `.vsix` file into your vscode IDE and ensure the extension can load properly. Run a single command (like run query, or add database).
|
||||
1. Go to the actions tab of the vscode-codeql repository and select the [Release workflow](https://github.com/github/vscode-codeql/actions?query=workflow%3ARelease).
|
||||
- If there is an authentication failure when publishing, be sure to check that the authentication keys haven't expired. See below.
|
||||
1. Approve the deployments of the correct Release workflow. This will automatically publish to Open VSX and VS Code Marketplace.
|
||||
1. Go to the draft GitHub release in [the releases tab of the repository](https://github.com/github/vscode-codeql/releases), click 'Edit', add some summary description, and publish it.
|
||||
1. Confirm the new release is marked as the latest release at <https://github.com/github/vscode-codeql/releases>.
|
||||
1. If documentation changes need to be published, notify documentation team that release has been made.
|
||||
1. Review and merge the version bump PR that is automatically created by Actions.
|
||||
|
||||
## Secrets and authentication for publishing
|
||||
|
||||
Repository administrators, will need to manage the authentication keys for publishing to the VS Code marketplace and Open VSX. Each requires an authentication token. The VS Code marketplace token expires yearly.
|
||||
|
||||
To regenerate the Open VSX token:
|
||||
|
||||
1. Log in to the [user settings page on Open VSX](https://open-vsx.org/user-settings/namespaces).
|
||||
1. Make sure you are a member of the GitHub namespace.
|
||||
1. Go to the [Access Tokens](https://open-vsx.org/user-settings/tokens) page and generate a new token.
|
||||
1. Update the secret in the `publish-open-vsx` environment in the project settings.
|
||||
|
||||
To regenerate the VSCode Marketplace token, please see our internal documentation. Note that Azure DevOps PATs expire every 90 days and must be regenerated.
|
||||
337
docs/test-plan.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# Test Plan
|
||||
|
||||
This document describes the manual test plan for the QL extension for Visual Studio Code.
|
||||
|
||||
The plan will be executed manually to start with but the goal is to eventually automate parts of the process (based on
|
||||
effort vs value basis).
|
||||
|
||||
## What this doesn't cover
|
||||
|
||||
We don't need to test features (and permutations of features) that are covered by automated tests.
|
||||
|
||||
## Before releasing the VS Code extension
|
||||
|
||||
- Go through the required test cases listed below
|
||||
- Check major PRs since the previous release for specific one-off things to test. Based on that, you might want to
|
||||
choose to go through some of the Optional Test Cases.
|
||||
- Run a query using the existing version of the extension (to generate an "old" query history item)
|
||||
|
||||
## Required Test Cases
|
||||
|
||||
### Test Case 1: MRVA - Running a problem path query and viewing results
|
||||
|
||||
1. Open the [UnsafeJQueryPlugin query](https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql).
|
||||
2. Run a MRVA against the following repo list:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "test-repo-list",
|
||||
"repositories": [
|
||||
"angular-cn/ng-nice",
|
||||
"apache/hadoop",
|
||||
"apache/hive"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
3. Check that a notification message pops up and the results view is opened.
|
||||
4. Check the query history. It should:
|
||||
- Show that an item has been added to the query history
|
||||
- The item should be marked as "in progress".
|
||||
5. Once the query starts:
|
||||
- Check the results view
|
||||
- Check the code paths view, including the code paths drop down menu.
|
||||
- Check that the repository filter box works
|
||||
- Click links to files/locations on GitHub
|
||||
- Check that the query history item is updated to show the number of results
|
||||
6. Once the query completes:
|
||||
- Check that the query history item is updated to show the query status as "complete"
|
||||
|
||||
### Test Case 2: MRVA - Running a problem query and viewing results
|
||||
|
||||
1. Open the [ReDoS query](https://github.com/github/codeql/blob/main/javascript/ql/src/Performance/ReDoS.ql).
|
||||
2. Run a MRVA against the "Top 10" repositories.
|
||||
3. Check that there is a notification message.
|
||||
4. Check the query history. It should:
|
||||
- Show that an item has been added to the query history
|
||||
- The item should be marked as "in progress".
|
||||
5. Once the query starts:
|
||||
- Check that a notification is shown with a link to the results view
|
||||
- Check that the results are rendered with an alert message and a highlighted code snippet:
|
||||

|
||||
|
||||
### Test Case 3: MRVA - Running a non-problem query and viewing results
|
||||
|
||||
1. Open the [FunLinesOfCode query](https://github.com/github/codeql/blob/main/cpp/ql/src/Metrics/Functions/FunLinesOfCode.ql).
|
||||
2. Run a MRVA against a single repository (e.g. `google/brotli`).
|
||||
3. Once the query starts:
|
||||
- Open the query results
|
||||
- Check that the results show up in a table:
|
||||

|
||||
|
||||
### Test Case 4: MRVA - Interacting with query history
|
||||
|
||||
1. Click a history item (for MRVA):
|
||||
- Check that exporting results works
|
||||
- Check that sorting results works
|
||||
- Check that copying repo lists works
|
||||
2. Open the query results directory:
|
||||
- Check that the correct directory is opened and there are results in it
|
||||
3. View logs
|
||||
- Check that the correct workflow is opened
|
||||
|
||||
### Test Case 5: MRVA - Canceling a variant analysis run
|
||||
|
||||
Run one of the above MRVAs, but cancel it from within VS Code:
|
||||
- Check that the query is canceled and the query history item is updated.
|
||||
- Check that the workflow run is also canceled.
|
||||
- Check that any available results are visible in VS Code.
|
||||
|
||||
### Test Case 6: MRVA - Change to a different colour theme
|
||||
|
||||
Open one of the above MRVAs, try changing to a different colour theme and check that everything looks sensible.
|
||||
Are there any components that are not showing up?
|
||||
|
||||
## Optional Test Cases
|
||||
|
||||
These are mostly aimed at MRVA, but some of them are also applicable to non-MRVA queries.
|
||||
|
||||
### Selecting repositories to run on
|
||||
|
||||
#### Test case 1: Running a query on a single repository
|
||||
|
||||
1. When the repository exists and is public
|
||||
1. Has a CodeQL database for the correct language
|
||||
2. Has a CodeQL database for another language
|
||||
3. Does not have any CodeQL databases
|
||||
2. When the repository exists and is private
|
||||
1. Is accessible and has a CodeQL database
|
||||
2. Is not accessible
|
||||
3. When the repository does not exist
|
||||
|
||||
#### Test case 2: Running a query on a custom repository list
|
||||
|
||||
1. The repository list is non-empty
|
||||
1. All repositories in the list have a CodeQL database
|
||||
2. Some but not all repositories in the list have a CodeQL database
|
||||
3. No repositories in the list have a CodeQL database
|
||||
2. The repository list is empty
|
||||
|
||||
#### Test case 3: Running a query on all repositories in an organization
|
||||
|
||||
1. The org exists
|
||||
1. The org contains repositories that have CodeQL databases
|
||||
2. The org contains repositories of the right language but without CodeQL databases
|
||||
3. The org contains repositories not of the right language
|
||||
4. The org contains private repositories that are inaccessible
|
||||
2. The org does not exist
|
||||
|
||||
### Using different types of controller repos
|
||||
|
||||
#### Test case 1: Running a query when the controller repository is public
|
||||
|
||||
1. Can run queries on public repositories
|
||||
2. Can not run queries on private repositories
|
||||
|
||||
#### Test case 2: Running a query when the controller repository is private
|
||||
|
||||
1. Can run queries on public repositories
|
||||
2. Can run queries on private repositories
|
||||
|
||||
#### Test case 3: Running a query when the controller repo exists but you do not have write access
|
||||
|
||||
1. Cannot run queries
|
||||
|
||||
#### Test case 4: Running a query when the controller repo doesn’t exist
|
||||
|
||||
1. Cannot run queries
|
||||
|
||||
#### Test case 5: Running a query when the "config field" for the controller repo is not set
|
||||
|
||||
1. Cannot run queries
|
||||
|
||||
### Query History
|
||||
|
||||
This requires running a MRVA query and viewing the query history.
|
||||
|
||||
The first test case specifies actions that you can do when the query is first run and is in "pending" state. We start
|
||||
with this since it has quite a limited number of actions you can do.
|
||||
|
||||
#### Test case 1: When variant analysis state is "pending"
|
||||
|
||||
1. Starts monitoring variant analysis
|
||||
2. Cannot open query history item
|
||||
3. Can delete a query history item
|
||||
1. Item is removed from list in UI
|
||||
2. Files on dist are deleted (can get to files using "open query directory")
|
||||
4. Can sort query history items
|
||||
1. By name
|
||||
2. By query date
|
||||
3. By result count
|
||||
5. Cannot open query directory
|
||||
6. Can open query that produced these results
|
||||
1. When the file still exists and has not moved
|
||||
2. When the file does not exist
|
||||
7. Cannot view logs
|
||||
8. Cannot copy repository list
|
||||
9. Cannot export results
|
||||
10. Cannot select to create a gist
|
||||
11. Cannot select to save as markdown
|
||||
12. Cannot cancel analysis
|
||||
|
||||
#### Test case 2: When the variant analysis state is not "pending"
|
||||
|
||||
1. Query history is loaded when VSCode starts
|
||||
2. Handles when action workflow was canceled while VSCode was closed
|
||||
3. Can open query history item
|
||||
1. Manually by clicking on them
|
||||
2. Automatically when VSCode starts (if they were open when VSCode was last used)
|
||||
4. Can delete a query history item
|
||||
1. Item is removed from list in UI
|
||||
2. Files on dist are deleted (can get to files using "open query directory")
|
||||
5. Can sort query history items
|
||||
1. By name
|
||||
2. By query date
|
||||
3. By result count
|
||||
6. Can open query directory
|
||||
7. Can open query that produced these results
|
||||
1. When the file still exists and has not moved
|
||||
2. When the file does not exist
|
||||
8. Can view logs
|
||||
9. Can copy repository list
|
||||
1. Text is copied to clipboard
|
||||
2. Text is a valid repository list
|
||||
10. Can export results
|
||||
11. Can select to create gist
|
||||
1. A gist is created
|
||||
2. The first thing in the gist is a summary
|
||||
3. Contains a file for each repository with results
|
||||
4. A popup links you to the gist
|
||||
12. Can select to save as markdown
|
||||
1. A directory is created on disk
|
||||
2. Contains a summary file
|
||||
3. Contains a file for each repository with results
|
||||
4. A popup allows you to open the directory
|
||||
|
||||
#### Test case 3: When variant analysis state is "in_progress"
|
||||
|
||||
1. Starts monitoring variant analysis
|
||||
1. Ready results are downloaded
|
||||
2. Can cancel analysis
|
||||
1. Causes the actions run to be canceled
|
||||
|
||||
#### Test case 4: When variant analysis state is in final state ("succeeded"/"failed"/"canceled")
|
||||
|
||||
1. Stops monitoring variant analysis
|
||||
1. All results are downloaded if state is succeeded
|
||||
2. Otherwise, ready results are downloaded, if any are available
|
||||
2. Cannot cancel analysis
|
||||
|
||||
### MRVA results view
|
||||
|
||||
This requires running a MRVA query and seeing the results view.
|
||||
|
||||
#### Test case 1: When variant analysis state is "pending"
|
||||
|
||||
1. Can open a results view
|
||||
2. Results view opens automatically
|
||||
- When starting variant analysis run
|
||||
- When VSCode opens (if view was open when VSCode was closed)
|
||||
3. Results view is empty
|
||||
|
||||
#### Test case 2: When variant analysis state is not "pending"
|
||||
|
||||
1. Can open a results view
|
||||
2. Results view opens automatically
|
||||
1. When starting variant analysis run
|
||||
2. When VSCode opens (if view was open when VSCode was closed)
|
||||
3. Can copy repository list
|
||||
1. Text is copied to clipboard
|
||||
2. Text is a valid repository list
|
||||
4. Can export results
|
||||
1. Only includes repos that you have selected (also see section from query history)
|
||||
5. Can cancel analysis
|
||||
6. Can open query file
|
||||
1. When the file still exists and has not moved
|
||||
2. When the file does not exist
|
||||
7. Can open query text
|
||||
8. Can sort repos
|
||||
1. By name
|
||||
2. By results
|
||||
3. By stars
|
||||
4. By last updated
|
||||
9. Can filter repos
|
||||
10. Shows correct statistics
|
||||
1. Total number of results
|
||||
2. Total number of repositories
|
||||
3. Duration
|
||||
11. Can see live results
|
||||
1. Results appear in extension as soon as each query is completed
|
||||
12. Can view interpreted results (i.e. for a "problem" query)
|
||||
1. Can view non-path results
|
||||
2. Can view code paths for "path-problem" queries
|
||||
13. Can view raw results (i.e. for a non "problem" query)
|
||||
1. Renders a table
|
||||
14. Can see skipped repositories
|
||||
1. Can see repos with no db in a tab
|
||||
1. Shown warning that explains the tab
|
||||
2. Can see repos with no access in a tab
|
||||
1. Shown warning that explains the tab
|
||||
3. Only shows tab when there are skipped repos
|
||||
15. Result downloads
|
||||
1. All results are downloaded automatically
|
||||
2. Download status is indicated by a spinner (Not currently any indication of progress beyond "downloading" and "not downloading")
|
||||
3. Only 3 items are downloaded at a time
|
||||
4. Results for completed queries are still downloaded when
|
||||
1. Some but not all queries failed
|
||||
2. The variant analysis was canceled after some queries completed
|
||||
|
||||
#### Test case 3: When variant analysis state is in "succeeded" state
|
||||
|
||||
1. Can view logs
|
||||
2. All results are downloaded
|
||||
|
||||
#### Test case 4: When variant analysis is in "failed" or "canceled" state
|
||||
|
||||
1. Can view logs
|
||||
1. Results for finished queries are still downloaded.
|
||||
|
||||
### MRVA repositories panel
|
||||
|
||||
1. Add a list
|
||||
1. Add a database at the top level
|
||||
1. Add a database to a list
|
||||
1. Add a the same database at a top-level and in a list
|
||||
1. Delete a list
|
||||
1. Delete a database from the top level
|
||||
1. Delete a database from a list
|
||||
1. Add an owner
|
||||
1. Remove an owner
|
||||
1. Rename a list
|
||||
1. Open on GitHub
|
||||
1. Select a list (via "Select" button and via context menu action)
|
||||
1. Run MRVA against a user-defined list
|
||||
1. Run MRVA against a top-N list
|
||||
1. Run MRVA against an owner
|
||||
1. Run MRVA against a database
|
||||
1. Copy repo list
|
||||
1. Open config file
|
||||
1. Make changes via config file (ensure JSON schema is helping out)
|
||||
1. Close and re-open VS Code (ensure lists are there)
|
||||
1. Collapse/expand tree nodes
|
||||
|
||||
Error cases that trigger an error notification:
|
||||
|
||||
1. Try to add a list with a name that already exists
|
||||
1. Try to add a top-level database that already exists
|
||||
1. Try to add a database in a list that already exists in the list
|
||||
|
||||
Error cases that show an error in the panel (and only the edit button should be visible):
|
||||
|
||||
1. Edit the db config file directly and save invalid JSON
|
||||
1. Edit the db config file directly and save valid JSON but invalid config (e.g. add an unknown property)
|
||||
1. Edit the db config file directly and save two lists with the same name
|
||||
|
||||
Cases where there the welcome view is shown:
|
||||
|
||||
1. No controller repo is set in the user's settings JSON.
|
||||
131
docs/testing.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Testing
|
||||
|
||||
We have several types of tests:
|
||||
|
||||
* Unit tests: these live in the `tests/unit-tests/` directory
|
||||
* View tests: these live in `src/view/variant-analysis/__tests__/`
|
||||
* VSCode integration tests:
|
||||
* `test/vscode-tests/activated-extension` tests: These are intended to cover functionality that require the full extension to be activated but don't require the CLI. This suite is not run against multiple versions of the CLI in CI.
|
||||
* `test/vscode-tests/no-workspace` tests: These are intended to cover functionality around not having a workspace. The extension is not activated in these tests.
|
||||
* `test/vscode-tests/minimal-workspace` tests: These are intended to cover functionality that need a workspace but don't require the full extension to be activated.
|
||||
* CLI integration tests: these live in `test/vscode-tests/cli-integration`
|
||||
* These tests are intended to cover functionality that is related to the integration between the CodeQL CLI and the extension. These tests are run against each supported versions of the CLI in CI.
|
||||
|
||||
The CLI integration tests require an instance of the CodeQL CLI to run so they will require some extra setup steps. When adding new tests to our test suite, please be mindful of whether they need to be in the cli-integration folder. If the tests don't depend on the CLI, they are better suited to being a VSCode integration test.
|
||||
|
||||
Any test data you're using (sample projects, config files, etc.) must go in a `test/vscode-tests/*/data` directory. When you run the tests, the test runner will copy the data directory to `out/vscode-tests/*/data`.
|
||||
|
||||
## Running the tests
|
||||
|
||||
Pre-requisites:
|
||||
1. Run `npm run build`.
|
||||
2. You will need to have `npm run watch` running in the background.
|
||||
|
||||
### 1. From the terminal
|
||||
|
||||
Then, from the `extensions/ql-vscode` directory, use the appropriate command to run the tests:
|
||||
|
||||
* Unit tests: `npm run test:unit`
|
||||
* View Tests: `npm test:view`
|
||||
* VSCode integration tests: `npm run test:vscode-integration`
|
||||
|
||||
#### CLI integration tests
|
||||
|
||||
The CLI integration tests require the CodeQL standard libraries in order to run so you will need to clone a local copy of the `github/codeql` repository.
|
||||
|
||||
1. Set the `TEST_CODEQL_PATH` environment variable: running from a terminal, you _must_ set the `TEST_CODEQL_PATH` variable to point to a checkout of the `github/codeql` repository. The appropriate CLI version will be downloaded as part of the test.
|
||||
|
||||
2. Run your test command:
|
||||
|
||||
```shell
|
||||
cd extensions/ql-vscode && npm run test:cli-integration
|
||||
```
|
||||
|
||||
### 2. From VSCode
|
||||
|
||||
Alternatively, you can run the tests inside of VSCode. There are several VSCode launch configurations defined that run the unit and integration tests.
|
||||
|
||||
You will need to run tests using a task from inside of VS Code, under the "Run and Debug" view:
|
||||
|
||||
* Unit tests: run the _Launch Unit Tests - React_ task
|
||||
* View Tests: run the _Launch Unit Tests_ task
|
||||
* VSCode integration tests: run the _Launch Unit Tests - No Workspace_ and _Launch Unit Tests - Minimal Workspace_ tasks
|
||||
|
||||
#### CLI integration tests
|
||||
|
||||
The CLI integration tests require the CodeQL standard libraries in order to run so you will need to clone a local copy of the `github/codeql` repository.
|
||||
|
||||
1. Set the `TEST_CODEQL_PATH` environment variable: running from a terminal, you _must_ set the `TEST_CODEQL_PATH` variable to point to a checkout of the `github/codeql` repository. The appropriate CLI version will be downloaded as part of the test.
|
||||
|
||||
2. Set the codeql path in VSCode's launch configuration: open `launch.json` and under the _Launch Integration Tests - With CLI_ section, uncomment the `"${workspaceRoot}/../codeql"` line. If you've cloned the `github/codeql` repo to a different path, replace the value with the correct path.
|
||||
|
||||
3. Run the VSCode task from the "Run and Debug" view called _Launch Integration Tests - With CLI_.
|
||||
|
||||
## Running a single test
|
||||
|
||||
### 1. From the terminal
|
||||
|
||||
The easiest way to run a single test is to change the `it` of the test to `it.only` and then run the test command with some additional options
|
||||
to only run tests for this specific file. For example, to run the test `test/vscode-tests/cli-integration/run-queries.test.ts`:
|
||||
|
||||
```shell
|
||||
npm run test:cli-integration -- --runTestsByPath test/vscode-tests/cli-integration/run-queries.test.ts
|
||||
```
|
||||
|
||||
You can also use the `--testNamePattern` option to run a specific test within a file. For example, to run the test `test/vscode-tests/cli-integration/run-queries.test.ts`:
|
||||
|
||||
```shell
|
||||
npm run test:cli-integration -- --runTestsByPath test/vscode-tests/cli-integration/run-queries.test.ts --testNamePattern "should create a QueryEvaluationInfo"
|
||||
```
|
||||
|
||||
### 2. From VSCode
|
||||
|
||||
Alternatively, you can run a single test inside VSCode. To do so, install the [Jest Runner](https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner) extension. Then,
|
||||
you will have quicklinks to run a single test from within test files. To run a single unit or integration test, click the "Run" button. Debugging a single test is currently only supported
|
||||
for unit tests by default. To debug integration tests, open the `.vscode/settings.json` file and uncomment the `jestrunner.debugOptions` lines. This will allow you to debug integration tests.
|
||||
Please make sure to revert this change before committing; with this setting enabled, it is not possible to debug unit tests.
|
||||
|
||||
Without the Jest Runner extension, you can also use the "Launch Selected Unit Test (vscode-codeql)" launch configuration to run a single unit test.
|
||||
|
||||
## Using a mock GitHub API server
|
||||
|
||||
Multi-Repo Variant Analyses (MRVA) rely on the GitHub API. In order to make development and testing easy, we have functionality that allows us to intercept requests to the GitHub API and provide mock responses.
|
||||
|
||||
### Using a pre-recorded test scenario
|
||||
|
||||
To run a mock MRVA scenario, follow these steps:
|
||||
1. Enable the mock GitHub API server by adding the following in your VS Code user settings (which can be found by running the `Preferences: Open User Settings (JSON)` VS Code command):
|
||||
```json
|
||||
"codeQL.mockGitHubApiServer": {
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
1. Run the `CodeQL: Mock GitHub API Server: Load Scenario` command from the command pallet, and choose one of the scenarios to load.
|
||||
1. Execute a normal MRVA. At this point you should see the scenario being played out, rather than an actual MRVA running.
|
||||
1. Once you're done, you can stop using the mock scenario with `CodeQL: Mock GitHub API Server: Unload Scenario`
|
||||
|
||||
If you want to replay the same scenario you should unload and reload it so requests are replayed from the start.
|
||||
|
||||
### Recording a new test scenario
|
||||
To record a new mock MRVA scenario, follow these steps:
|
||||
|
||||
1. Enable the mock GitHub API server by adding the following in your VS Code user settings (which can be found by running the `Preferences: Open User Settings (JSON)` VS Code command):
|
||||
```json
|
||||
"codeQL.mockGitHubApiServer": {
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
1. Run the `CodeQL: Mock GitHub API Server: Start Scenario Recording` VS Code command from the command pallet.
|
||||
1. Execute a normal MRVA.
|
||||
1. Once what you wanted to record is done (e.g. the MRVA has finished), then run the `CodeQL: Mock GitHub API Server: Save Scenario` command from the command pallet.
|
||||
1. The scenario should then be available for replaying.
|
||||
|
||||
If you want to cancel recording, run the `CodeQL: Mock GitHub API Server: Cancel Scenario Recording` command.
|
||||
|
||||
Once the scenario has been recorded, it's often useful to remove some of the requests to speed up the replay, particularly ones that fetch the variant analysis status. Once some of the request files have manually been removed, the [fix-scenario-file-numbering script](../extensions/ql-vscode/scripts/fix-scenario-file-numbering.ts) can be used to update the number of the files. See the script file for details on how to use.
|
||||
|
||||
### Scenario data location
|
||||
|
||||
Pre-recorded scenarios are stored in `./src/mocks/scenarios`. However, it's possible to configure the location, by setting the `codeQL.mockGitHubApiServer.scenariosPath` configuration property in the VS Code user settings.
|
||||
@@ -1,6 +1,7 @@
|
||||
.vscode-test/
|
||||
node_modules/
|
||||
out/
|
||||
build/
|
||||
|
||||
# Include the Storybook config
|
||||
!.storybook
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
module.exports = {
|
||||
const { resolve } = require("path");
|
||||
|
||||
const baseConfig = {
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: "module",
|
||||
project: ["tsconfig.json", "./src/**/tsconfig.json", "./gulpfile.ts/tsconfig.json", "./scripts/tsconfig.json", "./.storybook/tsconfig.json"],
|
||||
project: [
|
||||
resolve(__dirname, "tsconfig.lint.json"),
|
||||
resolve(__dirname, "src/**/tsconfig.json"),
|
||||
resolve(__dirname, "test/**/tsconfig.json"),
|
||||
resolve(__dirname, "gulpfile.ts/tsconfig.json"),
|
||||
resolve(__dirname, "scripts/tsconfig.json"),
|
||||
resolve(__dirname, ".storybook/tsconfig.json"),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
"github",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
plugins: ["github", "@typescript-eslint", "etc"],
|
||||
env: {
|
||||
node: true,
|
||||
es6: true,
|
||||
@@ -20,9 +26,10 @@ module.exports = {
|
||||
"plugin:github/typescript",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/no-use-before-define": 0,
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
@@ -36,15 +43,15 @@ module.exports = {
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-floating-promises": [ "error", { ignoreVoid: true } ],
|
||||
"@typescript-eslint/no-floating-promises": ["error", { ignoreVoid: true }],
|
||||
"@typescript-eslint/no-invalid-this": "off",
|
||||
"@typescript-eslint/no-shadow": "off",
|
||||
"prefer-const": ["warn", { destructuring: "all" }],
|
||||
"@typescript-eslint/no-throw-literal": "error",
|
||||
"no-useless-escape": 0,
|
||||
"camelcase": "off",
|
||||
"eqeqeq": "off",
|
||||
camelcase: "off",
|
||||
"escompat/no-regexp-lookbehind": "off",
|
||||
"etc/no-implicit-any-catch": "error",
|
||||
"filenames/match-regex": "off",
|
||||
"filenames/match-regexp": "off",
|
||||
"func-style": "off",
|
||||
@@ -70,3 +77,102 @@ module.exports = {
|
||||
"github/no-then": "off",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
...baseConfig,
|
||||
overrides: [
|
||||
{
|
||||
files: ["src/stories/**/*"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, "src/stories/tsconfig.json"),
|
||||
},
|
||||
extends: [
|
||||
...baseConfig.extends,
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/view/**/*"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, "src/view/tsconfig.json"),
|
||||
},
|
||||
extends: [
|
||||
...baseConfig.extends,
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
],
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["test/**/*"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, "test/tsconfig.json"),
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["test/vscode-tests/**/*"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, "test/tsconfig.json"),
|
||||
},
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
"@typescript-eslint/ban-types": [
|
||||
"error",
|
||||
{
|
||||
// For a full list of the default banned types, see:
|
||||
// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md
|
||||
extendDefaults: true,
|
||||
types: {
|
||||
// Don't complain about the `Function` type in test files. (Default is `true`.)
|
||||
Function: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
".eslintrc.js",
|
||||
"test/**/jest-runner-vscode.config.js",
|
||||
"test/**/jest-runner-vscode.config.base.js",
|
||||
],
|
||||
parser: undefined,
|
||||
plugins: ["github"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:github/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
],
|
||||
rules: {
|
||||
"import/no-commonjs": "off",
|
||||
"prefer-template": "off",
|
||||
"filenames/match-regex": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -2,5 +2,9 @@
|
||||
node_modules/
|
||||
out/
|
||||
|
||||
# This file gets written by an actions workflow.
|
||||
# Don't try to format it.
|
||||
supported_cli_versions.json
|
||||
|
||||
# Include the Storybook config
|
||||
!.storybook
|
||||
|
||||
@@ -14,3 +14,4 @@ gulpfile.js/**
|
||||
tsconfig.json
|
||||
.prettierrc
|
||||
vsc-extension-quickstart.md
|
||||
node_modules/**
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## 1.8.1 - 23 March 2023
|
||||
|
||||
- Show data flow paths of a variant analysis in a new tab. [#2172](https://github.com/github/vscode-codeql/pull/2172) & [#2182](https://github.com/github/vscode-codeql/pull/2182)
|
||||
- Show labels of entities in exported CSV results. [#2170](https://github.com/github/vscode-codeql/pull/2170)
|
||||
|
||||
## 1.8.0 - 9 March 2023
|
||||
|
||||
- Send telemetry about unhandled errors happening within the extension. [#2125](https://github.com/github/vscode-codeql/pull/2125)
|
||||
- Enable multi-repository variant analysis. [#2144](https://github.com/github/vscode-codeql/pull/2144)
|
||||
|
||||
## 1.7.11 - 1 March 2023
|
||||
|
||||
- Enable collection of telemetry concerning interactions with UI elements, including buttons, links, and other inputs. [#2114](https://github.com/github/vscode-codeql/pull/2114)
|
||||
- Prevent the automatic installation of CodeQL CLI version 2.12.3 to avoid a bug in the language server. CodeQL CLI 2.12.2 will be used instead. [#2126](https://github.com/github/vscode-codeql/pull/2126)
|
||||
|
||||
## 1.7.10 - 23 February 2023
|
||||
|
||||
- Fix bug that was causing unwanted error notifications.
|
||||
|
||||
## 1.7.9 - 20 February 2023
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 1.7.8 - 2 February 2023
|
||||
|
||||
- Renamed command "CodeQL: Run Query" to "CodeQL: Run Query on Selected Database". [#1962](https://github.com/github/vscode-codeql/pull/1962)
|
||||
- Remove support for CodeQL CLI versions older than 2.7.6. [#1788](https://github.com/github/vscode-codeql/pull/1788)
|
||||
|
||||
## 1.7.7 - 13 December 2022
|
||||
|
||||
- Increase the required version of VS Code to 1.67.0. [#1662](https://github.com/github/vscode-codeql/pull/1662)
|
||||
|
||||
@@ -94,7 +94,7 @@ The instructions below assume that you're using the CodeQL starter workspace, or
|
||||
|
||||
1. Expand the `ql` folder and locate a query to run. The standard queries are grouped by target language and then type, for example: `ql/java/ql/src/Likely Bugs`.
|
||||
1. Open a query (`.ql`) file.
|
||||
1. Right-click in the query window and select **CodeQL: Run Query**. Alternatively, open the Command Palette (**Ctrl+Shift+P** or **Cmd+Shift+P**), type `Run Query`, then select **CodeQL: Run Query**.
|
||||
1. Right-click in the query window and select **CodeQL: Run Query on Selected Database**. Alternatively, open the Command Palette (**Ctrl+Shift+P** or **Cmd+Shift+P**), type `Run Query`, then select **CodeQL: Run Query on Selected Database**.
|
||||
|
||||
The CodeQL extension runs the query on the current database using the CLI and reports progress in the bottom right corner of the application.
|
||||
When the results are ready, they're displayed in the CodeQL Query Results view. Use the dropdown menu to choose between different forms of result output.
|
||||
|
||||
127
extensions/ql-vscode/databases-schema.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
},
|
||||
"databases": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"variantAnalysis": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repositoryLists": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"repositories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9-_\\.]+/[a-zA-Z0-9-_\\.]+$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name", "repositories"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"owners": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9-_\\.]+$"
|
||||
}
|
||||
},
|
||||
"repositories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9-_\\.]+/[a-zA-Z0-9-_\\.]+$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["repositoryLists", "owners", "repositories"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["variantAnalysis"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"selected": {
|
||||
"type": "object",
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["variantAnalysisSystemDefinedList"]
|
||||
},
|
||||
"listName": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": ["kind", "listName"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["variantAnalysisUserDefinedList"]
|
||||
},
|
||||
"listName": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": ["kind", "listName"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["variantAnalysisOwner"]
|
||||
},
|
||||
"ownerName": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": ["kind", "ownerName"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["variantAnalysisRepository"]
|
||||
},
|
||||
"repositoryName": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"listName": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": ["kind", "repositoryName"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["databases", "version"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export function injectAppInsightsKey() {
|
||||
}
|
||||
|
||||
// replace the key
|
||||
return src(["out/telemetry.js"])
|
||||
return src(["out/extension.js"])
|
||||
.pipe(replace(/REPLACE-APP-INSIGHTS-KEY/, process.env.APP_INSIGHTS_KEY))
|
||||
.pipe(dest("out/"));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
writeFile,
|
||||
} from "fs-extra";
|
||||
import { resolve, join } from "path";
|
||||
import { isDevBuild } from "./dev";
|
||||
|
||||
export interface DeployedPackage {
|
||||
distPath: string;
|
||||
@@ -22,21 +23,27 @@ const packageFiles = [
|
||||
"language-configuration.json",
|
||||
"snippets.json",
|
||||
"media",
|
||||
"node_modules",
|
||||
"out",
|
||||
"workspace-databases-schema.json",
|
||||
"databases-schema.json",
|
||||
];
|
||||
|
||||
async function copyDirectory(
|
||||
sourcePath: string,
|
||||
destPath: string,
|
||||
): Promise<void> {
|
||||
console.log(`copying ${sourcePath} to ${destPath}`);
|
||||
await copy(sourcePath, destPath);
|
||||
}
|
||||
|
||||
async function copyPackage(
|
||||
sourcePath: string,
|
||||
destPath: string,
|
||||
): Promise<void> {
|
||||
for (const file of packageFiles) {
|
||||
console.log(
|
||||
`copying ${resolve(sourcePath, file)} to ${resolve(destPath, file)}`,
|
||||
);
|
||||
await copy(resolve(sourcePath, file), resolve(destPath, file));
|
||||
}
|
||||
await Promise.all(
|
||||
packageFiles.map((file) =>
|
||||
copyDirectory(resolve(sourcePath, file), resolve(destPath, file)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function deployPackage(
|
||||
@@ -47,8 +54,6 @@ export async function deployPackage(
|
||||
await readFile(packageJsonPath, "utf8"),
|
||||
);
|
||||
|
||||
// Default to development build; use flag --release to indicate release build.
|
||||
const isDevBuild = !process.argv.includes("--release");
|
||||
const distDir = join(__dirname, "../../../dist");
|
||||
await mkdirs(distDir);
|
||||
|
||||
|
||||
2
extensions/ql-vscode/gulpfile.ts/dev.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Default to development build; use flag --release to indicate release build.
|
||||
export const isDevBuild = !process.argv.includes("--release");
|
||||
@@ -1,7 +1,13 @@
|
||||
import { series, parallel } from "gulp";
|
||||
import { compileTypeScript, watchTypeScript, cleanOutput } from "./typescript";
|
||||
import { parallel, series } from "gulp";
|
||||
import {
|
||||
compileEsbuild,
|
||||
watchEsbuild,
|
||||
checkTypeScript,
|
||||
watchCheckTypeScript,
|
||||
cleanOutput,
|
||||
copyWasmFiles,
|
||||
} from "./typescript";
|
||||
import { compileTextMateGrammar } from "./textmate";
|
||||
import { copyTestData, watchTestData } from "./tests";
|
||||
import { compileView, watchView } from "./webpack";
|
||||
import { packageExtension } from "./package";
|
||||
import { injectAppInsightsKey } from "./appInsights";
|
||||
@@ -9,21 +15,25 @@ import { injectAppInsightsKey } from "./appInsights";
|
||||
export const buildWithoutPackage = series(
|
||||
cleanOutput,
|
||||
parallel(
|
||||
compileTypeScript,
|
||||
compileEsbuild,
|
||||
copyWasmFiles,
|
||||
checkTypeScript,
|
||||
compileTextMateGrammar,
|
||||
compileView,
|
||||
copyTestData,
|
||||
),
|
||||
);
|
||||
|
||||
export const watch = parallel(watchEsbuild, watchCheckTypeScript, watchView);
|
||||
|
||||
export {
|
||||
cleanOutput,
|
||||
compileTextMateGrammar,
|
||||
watchTypeScript,
|
||||
watchEsbuild,
|
||||
watchCheckTypeScript,
|
||||
watchView,
|
||||
compileTypeScript,
|
||||
copyTestData,
|
||||
watchTestData,
|
||||
compileEsbuild,
|
||||
copyWasmFiles,
|
||||
checkTypeScript,
|
||||
injectAppInsightsKey,
|
||||
compileView,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,9 @@ import { deployPackage } from "./deploy";
|
||||
import { spawn } from "child-process-promise";
|
||||
|
||||
export async function packageExtension(): Promise<void> {
|
||||
const deployedPackage = await deployPackage(resolve("package.json"));
|
||||
const deployedPackage = await deployPackage(
|
||||
resolve(__dirname, "../package.json"),
|
||||
);
|
||||
console.log(
|
||||
`Packaging extension '${deployedPackage.name}@${deployedPackage.version}'...`,
|
||||
);
|
||||
@@ -15,8 +17,9 @@ export async function packageExtension(): Promise<void> {
|
||||
"..",
|
||||
`${deployedPackage.name}-${deployedPackage.version}.vsix`,
|
||||
),
|
||||
"--no-dependencies",
|
||||
];
|
||||
const proc = spawn("./node_modules/.bin/vsce", args, {
|
||||
const proc = spawn(resolve(__dirname, "../node_modules/.bin/vsce"), args, {
|
||||
cwd: deployedPackage.distPath,
|
||||
});
|
||||
proc.childProcess.stdout!.on("data", (data) => {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { watch, src, dest } from "gulp";
|
||||
|
||||
export function copyTestData() {
|
||||
return Promise.all([copyNoWorkspaceData(), copyCliIntegrationData()]);
|
||||
}
|
||||
|
||||
export function watchTestData() {
|
||||
return watch(["src/vscode-tests/*/data/**/*"], copyTestData);
|
||||
}
|
||||
|
||||
function copyNoWorkspaceData() {
|
||||
return src("src/vscode-tests/no-workspace/data/**/*").pipe(
|
||||
dest("out/vscode-tests/no-workspace/data"),
|
||||
);
|
||||
}
|
||||
|
||||
function copyCliIntegrationData() {
|
||||
return src("src/vscode-tests/cli-integration/data/**/*").pipe(
|
||||
dest("out/vscode-tests/cli-integration/data"),
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { src, dest } from "gulp";
|
||||
import { dest, src } from "gulp";
|
||||
import { load } from "js-yaml";
|
||||
import { obj } from "through2";
|
||||
import * as PluginError from "plugin-error";
|
||||
import PluginError from "plugin-error";
|
||||
import * as Vinyl from "vinyl";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { gray, red } from "ansi-colors";
|
||||
import { dest, watch } from "gulp";
|
||||
import { init, write } from "gulp-sourcemaps";
|
||||
import * as ts from "gulp-typescript";
|
||||
import * as del from "del";
|
||||
import { dest, src, watch } from "gulp";
|
||||
import esbuild from "gulp-esbuild";
|
||||
import ts from "gulp-typescript";
|
||||
import del from "del";
|
||||
|
||||
function goodReporter(): ts.reporter.Reporter {
|
||||
return {
|
||||
@@ -35,20 +35,46 @@ export function cleanOutput() {
|
||||
: Promise.resolve();
|
||||
}
|
||||
|
||||
export function compileTypeScript() {
|
||||
return tsProject
|
||||
.src()
|
||||
.pipe(init())
|
||||
.pipe(tsProject(goodReporter()))
|
||||
export function compileEsbuild() {
|
||||
return src("./src/extension.ts")
|
||||
.pipe(
|
||||
write(".", {
|
||||
includeContent: false,
|
||||
sourceRoot: ".",
|
||||
esbuild({
|
||||
outfile: "extension.js",
|
||||
bundle: true,
|
||||
external: ["vscode", "fsevents"],
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
target: "es2020",
|
||||
sourcemap: "linked",
|
||||
sourceRoot: "..",
|
||||
loader: {
|
||||
".node": "copy",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.pipe(dest("out"));
|
||||
}
|
||||
|
||||
export function watchTypeScript() {
|
||||
watch("src/**/*.ts", compileTypeScript);
|
||||
export function watchEsbuild() {
|
||||
watch("src/**/*.ts", compileEsbuild);
|
||||
}
|
||||
|
||||
export function checkTypeScript() {
|
||||
// This doesn't actually output the TypeScript files, it just
|
||||
// runs the TypeScript compiler and reports any errors.
|
||||
return tsProject.src().pipe(tsProject(goodReporter()));
|
||||
}
|
||||
|
||||
export function watchCheckTypeScript() {
|
||||
watch("src/**/*.ts", checkTypeScript);
|
||||
}
|
||||
|
||||
export function copyWasmFiles() {
|
||||
// We need to copy this file for the source-map package to work. Without this fie, the source-map
|
||||
// package is not able to load the WASM file because we are not including the full node_modules
|
||||
// directory. In version 0.7.4, it is not possible to call SourceMapConsumer.initialize in Node environments
|
||||
// to configure the path to the WASM file. So, source-map will always load the file from `__dirname/mappings.wasm`.
|
||||
// In version 0.8.0, it may be possible to do this properly by calling SourceMapConsumer.initialize by
|
||||
// using the "browser" field in source-map's package.json to load the WASM file from a given file path.
|
||||
return src("node_modules/source-map/lib/mappings.wasm").pipe(dest("out"));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { resolve } from "path";
|
||||
import * as webpack from "webpack";
|
||||
import * as MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import { isDevBuild } from "./dev";
|
||||
|
||||
export const config: webpack.Configuration = {
|
||||
mode: "development",
|
||||
mode: isDevBuild ? "development" : "production",
|
||||
entry: {
|
||||
webview: "./src/view/webview.tsx",
|
||||
},
|
||||
@@ -11,7 +12,7 @@ export const config: webpack.Configuration = {
|
||||
path: resolve(__dirname, "..", "out"),
|
||||
filename: "[name].js",
|
||||
},
|
||||
devtool: "inline-source-map",
|
||||
devtool: isDevBuild ? "inline-source-map" : "source-map",
|
||||
resolve: {
|
||||
extensions: [".js", ".ts", ".tsx", ".json"],
|
||||
fallback: {
|
||||
@@ -53,6 +54,9 @@ export const config: webpack.Configuration = {
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as webpack from "webpack";
|
||||
import webpack from "webpack";
|
||||
import { config } from "./webpack.config";
|
||||
|
||||
export function compileView(cb: (err?: Error) => void) {
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
module.exports = {
|
||||
projects: [
|
||||
"<rootDir>/src/view",
|
||||
"<rootDir>/test",
|
||||
"<rootDir>/src/vscode-tests/cli-integration",
|
||||
"<rootDir>/src/vscode-tests/no-workspace",
|
||||
"<rootDir>/src/vscode-tests/minimal-workspace",
|
||||
"<rootDir>/test/unit-tests",
|
||||
"<rootDir>/test/vscode-tests/activated-extension",
|
||||
"<rootDir>/test/vscode-tests/cli-integration",
|
||||
"<rootDir>/test/vscode-tests/no-workspace",
|
||||
"<rootDir>/test/vscode-tests/minimal-workspace",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="30px"
|
||||
width="30px"
|
||||
fill="#000000"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
style="enable-background:new 0 0 100 100;"
|
||||
xml:space="preserve"
|
||||
id="svg3895"
|
||||
sodipodi:docname="check-dark-mode.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
|
||||
id="metadata3901"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3899" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1179"
|
||||
inkscape:window-height="701"
|
||||
id="namedview3897"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.8666667"
|
||||
inkscape:cx="-22.881356"
|
||||
inkscape:cy="15"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="28"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3895" /><g
|
||||
transform="translate(-452.57627,-74.457627)"
|
||||
id="g3893"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"><g
|
||||
id="g3891"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"><g
|
||||
id="g3889"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"><path
|
||||
d="M 457.98136,131.82373 488.80508,163.49492 548.8661,92.416949 535.93729,81.60678 487.85763,144.41695 468.2678,120.58983 Z"
|
||||
id="path3887"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1" /></g></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.23 1H11.77L3.52002 9.25L3.35999 9.46997L1 13.59L2.41003 15L6.53003 12.64L6.75 12.48L15 4.22998V2.77002L13.23 1ZM2.41003 13.59L3.92004 10.59L5.37 12.04L2.41003 13.59ZM6.23999 11.53L4.46997 9.76001L12.47 1.76001L14.24 3.53003L6.23999 11.53Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 372 B |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<path d="M16.010 6.49c-3.885 0-7.167 0.906-9.328 2.813-0.063-0.12-0.109-0.219-0.188-0.339-0.224-0.365-0.438-0.776-1.104-1.188-0.411-0.26-0.87-0.438-1.349-0.516-0.208-0.021-0.422-0.021-0.63 0l0.135-0.016c-1.214 0-1.922 0.724-2.385 1.354-0.458 0.625-0.755 1.328-0.948 2.099-0.38 1.542-0.385 3.536 1.083 5.026 0.766 0.781 1.667 1.151 2.484 1.37 0.156 0.042 0.297 0.052 0.448 0.083 0.531 2.521 2.104 4.656 4.208 5.839v0.005c1.24 0.693 2.417 1.010 3.297 1.349 1.234 0.479 2.536 1 4.052 1.135l0.078 0.005h0.198c1.745 0 3.063-0.703 4.203-1.141 0.875-0.333 2.052-0.641 3.302-1.344 0.578-0.323 1.115-0.719 1.594-1.172 1.318-1.234 2.229-2.839 2.625-4.599 1.115-0.182 2.141-0.719 2.922-1.536 1.464-1.484 1.458-3.479 1.078-5.021-0.193-0.771-0.49-1.474-0.948-2.099-0.458-0.63-1.172-1.354-2.385-1.354l0.135 0.016c-0.208-0.021-0.422-0.021-0.63 0-0.479 0.078-0.938 0.255-1.344 0.516-0.667 0.411-0.88 0.823-1.104 1.182-0.073 0.12-0.12 0.219-0.188 0.333-2.156-1.901-5.432-2.802-9.313-2.802zM16.042 8.313c4.745 0 8.016 1.422 9.411 3.964 0.839-0.323 1.453-2.521 2.146-2.948 0.563-0.344 0.885-0.26 0.885-0.26 1.271 0 2.578 3.729 0.953 5.38-0.859 0.875-2.443 1.12-3.229 1.057-0.063 2.542-1.542 4.833-3.5 5.932-1 0.563-2.068 0.854-3.063 1.234-1.229 0.469-2.38 1.016-3.547 1.016h-0.125c-1.161-0.099-2.318-0.542-3.547-1.016-0.995-0.38-2.068-0.682-3.063-1.24-1.948-1.099-3.427-3.391-3.49-5.927-0.781 0.068-2.385-0.177-3.245-1.057-1.625-1.651-0.318-5.38 0.948-5.38 0 0 0.328-0.083 0.885 0.26 0.698 0.427 1.318 2.646 2.161 2.953 1.391-2.547 4.667-3.969 9.417-3.969zM10.875 11.422c-2.276-0.042-4.146 1.792-4.146 4.068 0 2.281 1.87 4.115 4.146 4.073 5.328-0.099 5.328-8.047 0-8.141zM21.208 11.422c-5.427 0-5.427 8.141 0 8.141s5.427-8.141 0-8.141zM11.453 13.708c2.349 0.063 2.349 3.552 0 3.615-1.182 0-2.042-1.115-1.75-2.255 0.318 0.771 1.469 0.547 1.464-0.292 0-0.406-0.318-0.745-0.729-0.76 0.302-0.203 0.656-0.313 1.016-0.307zM20.641 13.708c2.344 0.063 2.344 3.552 0 3.615-1.182 0-2.047-1.115-1.755-2.255 0.229 0.552 0.979 0.641 1.328 0.146 0.344-0.49 0.010-1.167-0.589-1.193 0.297-0.208 0.651-0.313 1.016-0.313zM15.359 19.906c-0.318 0.026-0.5 0.193-0.5 0.635 0 0.281 0.182 0.484 0.5 0.484 0.229 0 0.266-0.323 0.047-0.375-0.031-0.005-0.172-0.057-0.172-0.182 0-0.12 0-0.167 0.24-0.198 0.104-0.016 0.156-0.141 0.125-0.24s-0.125-0.135-0.24-0.125zM16.724 19.906c-0.115-0.005-0.208 0.026-0.24 0.125s0.021 0.224 0.125 0.24c0.24 0.031 0.24 0.078 0.24 0.198 0 0.125-0.141 0.177-0.172 0.182-0.219 0.052-0.182 0.375 0.042 0.375 0.323 0 0.51-0.203 0.51-0.484 0-0.443-0.188-0.609-0.505-0.635z" fill="#C5C5C5"/>
|
||||
<line y2="24" x2="16" y1="26" x1="32" stroke-width="2" stroke="green" fill="none"/>
|
||||
<line y2="16" x2="24" y1="32" x1="24" stroke-width="1" stroke="green" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 2H14L15 3V13L14 14H2L1 13V3L2 2ZM2 13H14V3H2V13ZM13 4H3V7H13V4ZM12 6H4V5H12V6ZM9 12H13V8H9V12ZM10 9H12V11H10V9ZM7 8H3V9H7V8ZM3 11H7V12H3V11Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 313 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" fill="none"
|
||||
viewBox="0 0 432 432" style="enable-background:new 0 0 432 432;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon points="234.24,9.067 183.893,59.413 284.587,59.413" fill="#C5C5C5"/>
|
||||
<path d="m 259.24622,341.40906 v -32.34375 q 13.35937,6.32812 27.07031,9.66797 13.71094,3.33984 26.89453,3.33984 35.15625,0 53.61328,-23.55469 18.63282,-23.73047 21.26953,-71.89453 -10.19531,15.11719 -25.83984,23.20313 -15.64453,8.08593 -34.62891,8.08593 -39.375,0 -62.40234,-23.73046 -22.85156,-23.90625 -22.85156,-65.21485 0,-40.42969 23.90625,-64.86328 23.90625,-24.433594 63.63281,-24.433594 45.52734,0 69.43359,34.980474 24.08204,34.80468 24.08204,101.25 0,62.05078 -29.53125,99.14062 -29.35547,36.91406 -79.10157,36.91406 -13.35937,0 -27.07031,-2.63672 -13.71094,-2.63671 -28.47656,-7.91015 z m 70.66406,-111.26953 q 23.90625,0 37.79297,-16.34766 14.0625,-16.34766 14.0625,-44.82422 0,-28.30078 -14.0625,-44.64844 -13.88672,-16.52343 -37.79297,-16.52343 -23.90625,0 -37.96875,16.52343 -13.88672,16.34766 -13.88672,44.64844 0,28.47656 13.88672,44.82422 14.0625,16.34766 37.96875,16.34766 z" fill="#C5C5C5" />
|
||||
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227" fill="#C5C5C5"/>
|
||||
<path d="M 35.300905,316.97546 H 93.308718 V 116.76062 L 30.203249,129.41687 V 97.07312 L 92.957155,84.41687 h 35.507815 v 232.55859 h 58.00781 v 29.88282 H 35.300905 Z" fill="#C5C5C5"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 3H12H13V4H12V13L11 14H4L3 13V4H2V3H5V2C5 1.73478 5.10531 1.48038 5.29285 1.29285C5.48038 1.10531 5.73478 1 6 1H9C9.26522 1 9.51962 1.10531 9.70715 1.29285C9.89469 1.48038 10 1.73478 10 2V3ZM9 2H6V3H9V2ZM4 13H11V4H4V13ZM6 5H5V12H6V5ZM7 5H8V12H7V5ZM9 5H10V12H9V5Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 435 B |
@@ -1,7 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5 12.1952C15.5 12.9126 14.9137 13.4996 14.1957 13.4996H1.80435C1.08696 13.4996 0.5 12.9126 0.5 12.1952L0.5 9.80435C0.5 9.08696 1.08696 8.5 1.80435 8.5H14.1956C14.9137 8.5 15.5 9.08696 15.5 9.80435L15.5 12.1952Z" stroke="#959DA5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.45654 11.5H13.5435" stroke="#959DA5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5 9.5C13.224 9.5 13 9.725 13 10C13 10.275 13.224 10.5 13.5 10.5C13.776 10.5 14 10.275 14 10C14 9.725 13.776 9.5 13.5 9.5" fill="#959DA5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5 9.5C11.224 9.5 11 9.725 11 10C11 10.275 11.224 10.5 11.5 10.5C11.776 10.5 12 10.275 12 10C12 9.725 11.776 9.5 11.5 9.5" fill="#959DA5"/>
|
||||
<path d="M15.5 9.81464L13.8728 2.76261C13.6922 2.06804 12.9572 1.5 12.2391 1.5H3.76087C3.04348 1.5 2.30848 2.06804 2.12783 2.76261L0.5 9.8" stroke="#959DA5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,16 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="7.5" cy="7.5" r="7" stroke="#959DA5"/>
|
||||
<mask id="mask0_394_2982" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="15" height="15">
|
||||
<circle cx="7.5" cy="7.5" r="7.5" fill="#C4C4C4"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_394_2982)">
|
||||
<path d="M14.5 7.5C14.5 9.42971 13.6822 11.1907 12.5493 12.4721C11.4035 13.7683 10.0054 14.5 8.90625 14.5C7.84644 14.5 6.81131 13.8113 6.01569 12.5383C5.22447 11.2724 4.71875 9.49235 4.71875 7.5C4.71875 5.50765 5.22447 3.72765 6.01569 2.4617C6.81131 1.1887 7.84644 0.5 8.90625 0.5C10.0054 0.5 11.4035 1.23172 12.5493 2.52786C13.6822 3.80934 14.5 5.57029 14.5 7.5Z" stroke="#959DA5"/>
|
||||
</g>
|
||||
<mask id="mask1_394_2982" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="1" y="0" width="16" height="15">
|
||||
<circle cx="9.375" cy="7.5" r="7.5" fill="#C4C4C4"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_394_2982)">
|
||||
<path d="M10.2812 7.5C10.2812 9.49235 9.77553 11.2724 8.98431 12.5383C8.18869 13.8113 7.15356 14.5 6.09375 14.5C4.99456 14.5 3.5965 13.7683 2.45067 12.4721C1.31781 11.1907 0.5 9.42971 0.5 7.5C0.5 5.57029 1.31781 3.80934 2.45067 2.52786C3.5965 1.23172 4.99456 0.5 6.09375 0.5C7.15356 0.5 8.18869 1.1887 8.98431 2.4617C9.77553 3.72765 10.2812 5.50765 10.2812 7.5Z" stroke="#959DA5"/>
|
||||
</g>
|
||||
<line y1="7.5" x2="15" y2="7.5" stroke="#959DA5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,57 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="30px"
|
||||
width="30px"
|
||||
fill="#000000"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
style="enable-background:new 0 0 100 100;"
|
||||
xml:space="preserve"
|
||||
id="svg3895"
|
||||
sodipodi:docname="check.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
|
||||
id="metadata3901"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3899" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1179"
|
||||
inkscape:window-height="701"
|
||||
id="namedview3897"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.8666667"
|
||||
inkscape:cx="15"
|
||||
inkscape:cy="15"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="28"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3895" /><g
|
||||
transform="translate(-452.57627,-74.457627)"
|
||||
id="g3893"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"><g
|
||||
id="g3891"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"><g
|
||||
id="g3889"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"><path
|
||||
d="M 457.98136,131.82373 488.80508,163.49492 548.8661,92.416949 535.93729,81.60678 487.85763,144.41695 468.2678,120.58983 Z"
|
||||
id="path3887"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" /></g></g></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.2302 1H11.7703L3.52026 9.25L3.36023 9.46997L1.00024 13.59L2.41028 15L6.53027 12.64L6.75024 12.48L15.0002 4.22998V2.77002L13.2302 1ZM2.41028 13.59L3.92029 10.59L5.37024 12.04L2.41028 13.59ZM6.24023 11.53L4.47021 9.76001L12.4702 1.76001L14.2402 3.53003L6.24023 11.53Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 399 B |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
|
||||
<path d="M16.010 6.49c-3.885 0-7.167 0.906-9.328 2.813-0.063-0.12-0.109-0.219-0.188-0.339-0.224-0.365-0.438-0.776-1.104-1.188-0.411-0.26-0.87-0.438-1.349-0.516-0.208-0.021-0.422-0.021-0.63 0l0.135-0.016c-1.214 0-1.922 0.724-2.385 1.354-0.458 0.625-0.755 1.328-0.948 2.099-0.38 1.542-0.385 3.536 1.083 5.026 0.766 0.781 1.667 1.151 2.484 1.37 0.156 0.042 0.297 0.052 0.448 0.083 0.531 2.521 2.104 4.656 4.208 5.839v0.005c1.24 0.693 2.417 1.010 3.297 1.349 1.234 0.479 2.536 1 4.052 1.135l0.078 0.005h0.198c1.745 0 3.063-0.703 4.203-1.141 0.875-0.333 2.052-0.641 3.302-1.344 0.578-0.323 1.115-0.719 1.594-1.172 1.318-1.234 2.229-2.839 2.625-4.599 1.115-0.182 2.141-0.719 2.922-1.536 1.464-1.484 1.458-3.479 1.078-5.021-0.193-0.771-0.49-1.474-0.948-2.099-0.458-0.63-1.172-1.354-2.385-1.354l0.135 0.016c-0.208-0.021-0.422-0.021-0.63 0-0.479 0.078-0.938 0.255-1.344 0.516-0.667 0.411-0.88 0.823-1.104 1.182-0.073 0.12-0.12 0.219-0.188 0.333-2.156-1.901-5.432-2.802-9.313-2.802zM16.042 8.313c4.745 0 8.016 1.422 9.411 3.964 0.839-0.323 1.453-2.521 2.146-2.948 0.563-0.344 0.885-0.26 0.885-0.26 1.271 0 2.578 3.729 0.953 5.38-0.859 0.875-2.443 1.12-3.229 1.057-0.063 2.542-1.542 4.833-3.5 5.932-1 0.563-2.068 0.854-3.063 1.234-1.229 0.469-2.38 1.016-3.547 1.016h-0.125c-1.161-0.099-2.318-0.542-3.547-1.016-0.995-0.38-2.068-0.682-3.063-1.24-1.948-1.099-3.427-3.391-3.49-5.927-0.781 0.068-2.385-0.177-3.245-1.057-1.625-1.651-0.318-5.38 0.948-5.38 0 0 0.328-0.083 0.885 0.26 0.698 0.427 1.318 2.646 2.161 2.953 1.391-2.547 4.667-3.969 9.417-3.969zM10.875 11.422c-2.276-0.042-4.146 1.792-4.146 4.068 0 2.281 1.87 4.115 4.146 4.073 5.328-0.099 5.328-8.047 0-8.141zM21.208 11.422c-5.427 0-5.427 8.141 0 8.141s5.427-8.141 0-8.141zM11.453 13.708c2.349 0.063 2.349 3.552 0 3.615-1.182 0-2.042-1.115-1.75-2.255 0.318 0.771 1.469 0.547 1.464-0.292 0-0.406-0.318-0.745-0.729-0.76 0.302-0.203 0.656-0.313 1.016-0.307zM20.641 13.708c2.344 0.063 2.344 3.552 0 3.615-1.182 0-2.047-1.115-1.755-2.255 0.229 0.552 0.979 0.641 1.328 0.146 0.344-0.49 0.010-1.167-0.589-1.193 0.297-0.208 0.651-0.313 1.016-0.313zM15.359 19.906c-0.318 0.026-0.5 0.193-0.5 0.635 0 0.281 0.182 0.484 0.5 0.484 0.229 0 0.266-0.323 0.047-0.375-0.031-0.005-0.172-0.057-0.172-0.182 0-0.12 0-0.167 0.24-0.198 0.104-0.016 0.156-0.141 0.125-0.24s-0.125-0.135-0.24-0.125zM16.724 19.906c-0.115-0.005-0.208 0.026-0.24 0.125s0.021 0.224 0.125 0.24c0.24 0.031 0.24 0.078 0.24 0.198 0 0.125-0.141 0.177-0.172 0.182-0.219 0.052-0.182 0.375 0.042 0.375 0.323 0 0.51-0.203 0.51-0.484 0-0.443-0.188-0.609-0.505-0.635z" fill="#424242"/>
|
||||
<line y2="24" x2="16" y1="26" x1="32" stroke-width="2" stroke="green" fill="none"/>
|
||||
<line y2="16" x2="24" y1="32" x1="24" stroke-width="1" stroke="green" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.00024 2H14.0002L15.0002 3V13L14.0002 14H2.00024L1.00024 13V3L2.00024 2ZM2.00024 13H14.0002V3H2.00024V13ZM13.0002 4H3.00024V7H13.0002V4ZM12.0002 6H4.00024V5H12.0002V6ZM9.00024 12H13.0002V8H9.00024V12ZM10.0002 9H12.0002V11H10.0002V9ZM7.00024 8H3.00024V9H7.00024V8ZM3.00024 11H7.00024V12H3.00024V11Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 469 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 432 432" style="enable-background:new 0 0 432 432;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon points="234.24,9.067 183.893,59.413 284.587,59.413" />
|
||||
<path d="m 259.24622,341.40906 v -32.34375 q 13.35937,6.32812 27.07031,9.66797 13.71094,3.33984 26.89453,3.33984 35.15625,0 53.61328,-23.55469 18.63282,-23.73047 21.26953,-71.89453 -10.19531,15.11719 -25.83984,23.20313 -15.64453,8.08593 -34.62891,8.08593 -39.375,0 -62.40234,-23.73046 -22.85156,-23.90625 -22.85156,-65.21485 0,-40.42969 23.90625,-64.86328 23.90625,-24.433594 63.63281,-24.433594 45.52734,0 69.43359,34.980474 24.08204,34.80468 24.08204,101.25 0,62.05078 -29.53125,99.14062 -29.35547,36.91406 -79.10157,36.91406 -13.35937,0 -27.07031,-2.63672 -13.71094,-2.63671 -28.47656,-7.91015 z m 70.66406,-111.26953 q 23.90625,0 37.79297,-16.34766 14.0625,-16.34766 14.0625,-44.82422 0,-28.30078 -14.0625,-44.64844 -13.88672,-16.52343 -37.79297,-16.52343 -23.90625,0 -37.96875,16.52343 -13.88672,16.34766 -13.88672,44.64844 0,28.47656 13.88672,44.82422 14.0625,16.34766 37.96875,16.34766 z" />
|
||||
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227" />
|
||||
<path d="M 35.300905,316.97546 H 93.308718 V 116.76062 L 30.203249,129.41687 V 97.07312 L 92.957155,84.41687 h 35.507815 v 232.55859 h 58.00781 v 29.88282 H 35.300905 Z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.0002 3H12.0002H13.0002V4H12.0002V13L11.0002 14H4.00024L3.00024 13V4H2.00024V3H5.00024V2C5.00024 1.73478 5.10555 1.48038 5.29309 1.29285C5.48063 1.10531 5.73503 1 6.00024 1H9.00024C9.26546 1 9.51986 1.10531 9.7074 1.29285C9.89493 1.48038 10.0002 1.73478 10.0002 2V3ZM9.00024 2H6.00024V3H9.00024V2ZM4.00024 13H11.0002V4H4.00024V13ZM6.00024 5H5.00024V12H6.00024V5ZM7.00024 5H8.00024V12H7.00024V5ZM9.00024 5H10.0002V12H9.00024V5Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 599 B |
@@ -1,58 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="30px"
|
||||
width="30px"
|
||||
fill="#000000"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
style="enable-background:new 0 0 100 100;"
|
||||
xml:space="preserve"
|
||||
id="svg3895"
|
||||
sodipodi:docname="red-x.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
|
||||
id="metadata3901"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3899" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1179"
|
||||
inkscape:window-height="701"
|
||||
id="namedview3897"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.8666667"
|
||||
inkscape:cx="-22.881356"
|
||||
inkscape:cy="15"
|
||||
inkscape:window-x="33"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg3895" /><rect
|
||||
style="opacity:1;vector-effect:none;fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:2.24023867;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect29"
|
||||
width="15.499873"
|
||||
height="88.381233"
|
||||
x="-8.3491764"
|
||||
y="27.119303"
|
||||
transform="matrix(-0.70710678,0.70710678,0.70710678,0.70710678,0,0)" /><rect
|
||||
style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:2.24023867;stroke-opacity:1"
|
||||
id="rect29-6"
|
||||
width="15.499873"
|
||||
height="88.381233"
|
||||
x="63.559982"
|
||||
y="-44.789856"
|
||||
transform="rotate(45)" /></svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
4886
extensions/ql-vscode/package-lock.json
generated
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.7.7",
|
||||
"version": "1.8.1",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -37,19 +37,13 @@
|
||||
"onLanguage:ql",
|
||||
"onLanguage:ql-summary",
|
||||
"onView:codeQLDatabases",
|
||||
"onView:codeQLDatabasesExperimental",
|
||||
"onView:codeQLVariantAnalysisRepositories",
|
||||
"onView:codeQLQueryHistory",
|
||||
"onView:codeQLAstViewer",
|
||||
"onView:codeQLEvalLogViewer",
|
||||
"onView:test-explorer",
|
||||
"onCommand:codeQL.checkForUpdatesToCLI",
|
||||
"onCommand:codeQL.authenticateToGitHub",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseFolder",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseArchive",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseInternet",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseGithub",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseLgtm",
|
||||
"onCommand:codeQL.setCurrentDatabase",
|
||||
"onCommand:codeQL.viewAst",
|
||||
"onCommand:codeQL.viewCfg",
|
||||
"onCommand:codeQL.openReferencedFile",
|
||||
@@ -58,16 +52,11 @@
|
||||
"onCommand:codeQL.chooseDatabaseArchive",
|
||||
"onCommand:codeQL.chooseDatabaseInternet",
|
||||
"onCommand:codeQL.chooseDatabaseGithub",
|
||||
"onCommand:codeQL.chooseDatabaseLgtm",
|
||||
"onCommand:codeQLDatabases.chooseDatabase",
|
||||
"onCommand:codeQLDatabases.setCurrentDatabase",
|
||||
"onCommand:codeQLDatabasesExperimental.openConfigFile",
|
||||
"onCommand:codeQLDatabasesExperimental.addNewList",
|
||||
"onCommand:codeQLDatabasesExperimental.setSelectedItem",
|
||||
"onCommand:codeQL.quickQuery",
|
||||
"onCommand:codeQL.restartQueryServer",
|
||||
"onWebviewPanel:resultsView",
|
||||
"onWebviewPanel:codeQL.variantAnalysis",
|
||||
"onWebviewPanel:codeQL.dataFlowPaths",
|
||||
"onFileSystem:codeql-zip-archive"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
@@ -89,8 +78,8 @@
|
||||
},
|
||||
"jsonValidation": [
|
||||
{
|
||||
"fileMatch": "workspace-databases.json",
|
||||
"url": "./workspace-databases-schema.json"
|
||||
"fileMatch": "GitHub.vscode-codeql/databases.json",
|
||||
"url": "./databases-schema.json"
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
@@ -236,6 +225,19 @@
|
||||
"default": true,
|
||||
"description": "Enable the 'Quick Evaluation' CodeLens."
|
||||
},
|
||||
"codeQL.runningQueries.useExtensionPacks": {
|
||||
"type": "string",
|
||||
"default": "none",
|
||||
"enum": [
|
||||
"none",
|
||||
"all"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Do not use extension packs.",
|
||||
"Use all extension packs found in the workspace."
|
||||
],
|
||||
"description": "Choose whether or not to run queries using extension packs. Requires CodeQL CLI v2.12.3 or later."
|
||||
},
|
||||
"codeQL.resultsDisplay.pageSize": {
|
||||
"type": "integer",
|
||||
"default": 200,
|
||||
@@ -278,22 +280,6 @@
|
||||
"scope": "application",
|
||||
"description": "Specifies whether or not to write telemetry events to the extension log."
|
||||
},
|
||||
"codeQL.variantAnalysis.repositoryLists": {
|
||||
"type": [
|
||||
"object",
|
||||
null
|
||||
],
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": null,
|
||||
"markdownDescription": "[For internal use only] Lists of GitHub repositories that you want to run variant analysis against. This should be a JSON object where each key is a user-specified name for this repository list, and the value is an array of GitHub repositories (of the form `<owner>/<repo>`)."
|
||||
},
|
||||
"codeQL.variantAnalysis.controllerRepo": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
@@ -317,16 +303,28 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQuery",
|
||||
"title": "CodeQL: Run Query"
|
||||
"title": "CodeQL: Run Query on Selected Database"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"title": "CodeQL: Run Query on Selected Database"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"title": "CodeQL: Run Query on Multiple Databases"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabasesContextEditor",
|
||||
"title": "CodeQL: Run Query on Multiple Databases"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysis",
|
||||
"title": "CodeQL: Run Variant Analysis"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysisContextEditor",
|
||||
"title": "CodeQL: Run Variant Analysis"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.exportSelectedVariantAnalysisResults",
|
||||
"title": "CodeQL: Export Variant Analysis Results"
|
||||
@@ -339,10 +337,22 @@
|
||||
"command": "codeQL.quickEval",
|
||||
"title": "CodeQL: Quick Evaluation"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.quickEvalContextEditor",
|
||||
"title": "CodeQL: Quick Evaluation"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFile",
|
||||
"title": "CodeQL: Open Referenced File"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFileContextEditor",
|
||||
"title": "CodeQL: Open Referenced File"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFileContextExplorer",
|
||||
"title": "CodeQL: Open Referenced File"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.previewQueryHelp",
|
||||
"title": "CodeQL: Preview Query Help"
|
||||
@@ -360,19 +370,39 @@
|
||||
"title": "CodeQL: Copy Version Information"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.openConfigFile",
|
||||
"title": "Open Database Configuration File",
|
||||
"icon": "$(edit)"
|
||||
"command": "codeQLVariantAnalysisRepositories.openConfigFile",
|
||||
"title": "Open database configuration file",
|
||||
"icon": "$(json)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.addNewList",
|
||||
"command": "codeQLVariantAnalysisRepositories.addNewDatabase",
|
||||
"title": "Add new database",
|
||||
"icon": "$(add)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.addNewList",
|
||||
"title": "Add new list",
|
||||
"icon": "$(new-folder)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.setSelectedItem",
|
||||
"title": "Select Item",
|
||||
"icon": "$(circle-small-filled)"
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItem",
|
||||
"title": "Select"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu",
|
||||
"title": "Select"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.renameItemContextMenu",
|
||||
"title": "Rename"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu",
|
||||
"title": "Open on GitHub"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.removeItemContextMenu",
|
||||
"title": "Delete"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.chooseDatabaseFolder",
|
||||
@@ -410,14 +440,6 @@
|
||||
"dark": "media/dark/github.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.chooseDatabaseLgtm",
|
||||
"title": "Download from LGTM",
|
||||
"icon": {
|
||||
"light": "media/light/lgtm-plus.svg",
|
||||
"dark": "media/dark/lgtm-plus.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "codeQL.setCurrentDatabase",
|
||||
"title": "CodeQL: Set Current Database"
|
||||
@@ -426,10 +448,26 @@
|
||||
"command": "codeQL.viewAst",
|
||||
"title": "CodeQL: View AST"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAstContextExplorer",
|
||||
"title": "CodeQL: View AST"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAstContextEditor",
|
||||
"title": "CodeQL: View AST"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewCfg",
|
||||
"title": "CodeQL: View CFG"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewCfgContextExplorer",
|
||||
"title": "CodeQL: View CFG"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewCfgContextEditor",
|
||||
"title": "CodeQL: View CFG"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.upgradeCurrentDatabase",
|
||||
"title": "CodeQL: Upgrade Current Database"
|
||||
@@ -448,7 +486,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.setCurrentDatabase",
|
||||
"title": "Set Current Database"
|
||||
"title": "Select"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.removeDatabase",
|
||||
@@ -486,10 +524,6 @@
|
||||
"command": "codeQL.chooseDatabaseGithub",
|
||||
"title": "CodeQL: Download Database from GitHub"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.chooseDatabaseLgtm",
|
||||
"title": "CodeQL: Download Database from LGTM"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.sortByName",
|
||||
"title": "Sort by Name",
|
||||
@@ -511,60 +545,54 @@
|
||||
"title": "CodeQL: Check for CLI Updates"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQuery",
|
||||
"title": "Open the Query that Produced these Results",
|
||||
"icon": {
|
||||
"light": "media/light/edit.svg",
|
||||
"dark": "media/dark/edit.svg"
|
||||
}
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"title": "View Query",
|
||||
"icon": "$(edit)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryContextMenu",
|
||||
"title": "View Query",
|
||||
"icon": "$(edit)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.itemClicked",
|
||||
"title": "Open Query Results",
|
||||
"icon": {
|
||||
"light": "media/light/preview.svg",
|
||||
"dark": "media/dark/preview.svg"
|
||||
}
|
||||
"icon": "$(preview)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItem",
|
||||
"title": "Remove History Item(s)",
|
||||
"icon": {
|
||||
"light": "media/light/trash.svg",
|
||||
"dark": "media/dark/trash.svg"
|
||||
}
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"title": "Delete",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
|
||||
"title": "Delete",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextInline",
|
||||
"title": "Delete",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByName",
|
||||
"title": "Sort by Name",
|
||||
"icon": {
|
||||
"light": "media/light/sort-alpha.svg",
|
||||
"dark": "media/dark/sort-alpha.svg"
|
||||
}
|
||||
"title": "Sort by Name"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByDate",
|
||||
"title": "Sort by Query Date",
|
||||
"icon": {
|
||||
"light": "media/light/sort-date.svg",
|
||||
"dark": "media/dark/sort-date.svg"
|
||||
}
|
||||
"title": "Sort by Date"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByCount",
|
||||
"title": "Sort by Results Count",
|
||||
"icon": {
|
||||
"light": "media/light/sort-num.svg",
|
||||
"dark": "media/dark/sort-num.svg"
|
||||
}
|
||||
"title": "Sort by Results Count"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryLog",
|
||||
"title": "Show Query Log"
|
||||
"title": "View Query Log"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryDirectory",
|
||||
"title": "Open Query Directory"
|
||||
"title": "Open Results Directory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLog",
|
||||
@@ -584,7 +612,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryText",
|
||||
"title": "Show Query Text"
|
||||
"title": "View Query Text"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.exportResults",
|
||||
@@ -607,8 +635,8 @@
|
||||
"title": "View DIL"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"title": "Set Label"
|
||||
"command": "codeQLQueryHistory.renameItem",
|
||||
"title": "Rename"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.compareWith",
|
||||
@@ -616,7 +644,7 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openOnGithub",
|
||||
"title": "Open Variant Analysis on GitHub"
|
||||
"title": "View Logs"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.copyRepoList",
|
||||
@@ -729,12 +757,7 @@
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.chooseDatabaseLgtm",
|
||||
"when": "config.codeQL.canary && view == codeQLDatabases",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQuery",
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
@@ -744,24 +767,24 @@
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItem",
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByName",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
"group": "1_queryHistory@0"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByDate",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
"group": "1_queryHistory@1"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.sortByCount",
|
||||
"when": "view == codeQLQueryHistory",
|
||||
"group": "navigation"
|
||||
"group": "1_queryHistory@2"
|
||||
},
|
||||
{
|
||||
"command": "codeQLAstViewer.clear",
|
||||
@@ -774,17 +797,42 @@
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.openConfigFile",
|
||||
"when": "view == codeQLDatabasesExperimental",
|
||||
"command": "codeQLVariantAnalysisRepositories.openConfigFile",
|
||||
"when": "view == codeQLVariantAnalysisRepositories",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.addNewList",
|
||||
"when": "view == codeQLDatabasesExperimental",
|
||||
"command": "codeQLVariantAnalysisRepositories.addNewDatabase",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && codeQLVariantAnalysisRepositories.configError == false",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.addNewList",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && codeQLVariantAnalysisRepositories.configError == false",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.removeItemContextMenu",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeRemoved/",
|
||||
"group": "2_qlContextMenu@3"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeSelected/",
|
||||
"group": "1_qlContextMenu@1"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.renameItemContextMenu",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeRenamed/",
|
||||
"group": "2_qlContextMenu@2"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeOpenedOnGitHub/",
|
||||
"group": "2_qlContextMenu@1"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.setCurrentDatabase",
|
||||
"group": "inline",
|
||||
@@ -811,63 +859,68 @@
|
||||
"when": "view == codeQLDatabases"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.setSelectedItem",
|
||||
"when": "view == codeQLDatabasesExperimental && viewItem == selectableDbItem",
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItem",
|
||||
"when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeSelected/",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQuery",
|
||||
"group": "9_qlCommands",
|
||||
"command": "codeQLQueryHistory.openQueryContextMenu",
|
||||
"group": "2_queryHistory@0",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItem",
|
||||
"group": "9_qlCommands",
|
||||
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
|
||||
"group": "7_queryHistory@0",
|
||||
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledRemoteResultsItemWithoutLogs || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"group": "9_qlCommands",
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextInline",
|
||||
"group": "inline",
|
||||
"when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledRemoteResultsItemWithoutLogs || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.renameItem",
|
||||
"group": "6_queryHistory@0",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.compareWith",
|
||||
"group": "9_qlCommands",
|
||||
"group": "3_queryHistory@0",
|
||||
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryLog",
|
||||
"group": "9_qlCommands",
|
||||
"group": "4_queryHistory@4",
|
||||
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQueryDirectory",
|
||||
"group": "9_qlCommands",
|
||||
"group": "2_queryHistory@4",
|
||||
"when": "view == codeQLQueryHistory && !hasRemoteServer"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLog",
|
||||
"group": "9_qlCommands",
|
||||
"group": "4_queryHistory@1",
|
||||
"when": "codeql.supportsEvalLog && viewItem == rawResultsItem || codeql.supportsEvalLog && viewItem == interpretedResultsItem || codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLogSummary",
|
||||
"group": "9_qlCommands",
|
||||
"group": "4_queryHistory@2",
|
||||
"when": "codeql.supportsEvalLog && viewItem == rawResultsItem || codeql.supportsEvalLog && viewItem == interpretedResultsItem || codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showEvalLogViewer",
|
||||
"group": "9_qlCommands",
|
||||
"group": "4_queryHistory@3",
|
||||
"when": "config.codeQL.canary && codeql.supportsEvalLog && viewItem == rawResultsItem || config.codeQL.canary && codeql.supportsEvalLog && viewItem == interpretedResultsItem || config.codeQL.canary && codeql.supportsEvalLog && viewItem == cancelledResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.showQueryText",
|
||||
"group": "9_qlCommands",
|
||||
"group": "2_queryHistory@2",
|
||||
"when": "view == codeQLQueryHistory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.exportResults",
|
||||
"group": "9_qlCommands",
|
||||
"group": "1_queryHistory@0",
|
||||
"when": "view == codeQLQueryHistory && viewItem == remoteResultsItem"
|
||||
},
|
||||
{
|
||||
@@ -877,17 +930,17 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewCsvAlerts",
|
||||
"group": "9_qlCommands",
|
||||
"group": "5_queryHistory@0",
|
||||
"when": "viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifAlerts",
|
||||
"group": "9_qlCommands",
|
||||
"group": "5_queryHistory@1",
|
||||
"when": "viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewDil",
|
||||
"group": "9_qlCommands",
|
||||
"group": "5_queryHistory@2",
|
||||
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
@@ -897,12 +950,12 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openOnGithub",
|
||||
"group": "9_qlCommands",
|
||||
"group": "2_queryHistory@3",
|
||||
"when": "viewItem == remoteResultsItem || viewItem == inProgressRemoteResultsItem || viewItem == cancelledRemoteResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.copyRepoList",
|
||||
"group": "9_qlCommands",
|
||||
"group": "1_queryHistory@1",
|
||||
"when": "viewItem == remoteResultsItem"
|
||||
},
|
||||
{
|
||||
@@ -923,12 +976,12 @@
|
||||
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zip"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAst",
|
||||
"command": "codeQL.viewAstContextExplorer",
|
||||
"group": "9_qlCommands",
|
||||
"when": "resourceScheme == codeql-zip-archive && !explorerResourceIsFolder && !listMultiSelection"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewCfg",
|
||||
"command": "codeQL.viewCfgContextExplorer",
|
||||
"group": "9_qlCommands",
|
||||
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
|
||||
},
|
||||
@@ -938,7 +991,7 @@
|
||||
"when": "resourceScheme != codeql-zip-archive"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFile",
|
||||
"command": "codeQL.openReferencedFileContextExplorer",
|
||||
"group": "9_qlCommands",
|
||||
"when": "resourceExtname == .qlref"
|
||||
},
|
||||
@@ -957,17 +1010,25 @@
|
||||
"command": "codeQL.runQuery",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysis",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
|
||||
"command": "codeQL.runQueryOnMultipleDatabasesContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.exportSelectedVariantAnalysisResults",
|
||||
"when": "config.codeQL.canary"
|
||||
"command": "codeQL.runVariantAnalysis",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysisContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueries",
|
||||
@@ -977,10 +1038,22 @@
|
||||
"command": "codeQL.quickEval",
|
||||
"when": "editorLangId == ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.quickEvalContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFile",
|
||||
"when": "resourceExtname == .qlref"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFileContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFileContextExplorer",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.previewQueryHelp",
|
||||
"when": "resourceExtname == .qhelp && isWorkspaceTrusted"
|
||||
@@ -993,24 +1066,56 @@
|
||||
"command": "codeQL.viewAst",
|
||||
"when": "resourceScheme == codeql-zip-archive"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAstContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAstContextExplorer",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewCfg",
|
||||
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.chooseDatabaseLgtm",
|
||||
"when": "config.codeQL.canary"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.openConfigFile",
|
||||
"command": "codeQL.viewCfgContextExplorer",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.addNewList",
|
||||
"command": "codeQL.viewCfgContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabasesExperimental.setSelectedItem",
|
||||
"command": "codeQLVariantAnalysisRepositories.openConfigFile",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.addNewDatabase",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.addNewList",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItem",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.renameItemContextMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLVariantAnalysisRepositories.removeItemContextMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
@@ -1061,20 +1166,28 @@
|
||||
"command": "codeQLDatabases.chooseDatabaseGithub",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.chooseDatabaseLgtm",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.upgradeDatabase",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQuery",
|
||||
"command": "codeQLQueryHistory.openQueryTitleMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItem",
|
||||
"command": "codeQLQueryHistory.openQueryContextMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemTitleMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextMenu",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.removeHistoryItemContextInline",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
@@ -1138,7 +1251,7 @@
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.setLabel",
|
||||
"command": "codeQLQueryHistory.renameItem",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
@@ -1200,31 +1313,31 @@
|
||||
],
|
||||
"editor/context": [
|
||||
{
|
||||
"command": "codeQL.runQuery",
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"command": "codeQL.runQueryOnMultipleDatabasesContextEditor",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysis",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
|
||||
"command": "codeQL.runVariantAnalysisContextEditor",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAst",
|
||||
"command": "codeQL.viewAstContextEditor",
|
||||
"when": "resourceScheme == codeql-zip-archive"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewCfg",
|
||||
"command": "codeQL.viewCfgContextEditor",
|
||||
"when": "resourceScheme == codeql-zip-archive && config.codeQL.canary"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.quickEval",
|
||||
"command": "codeQL.quickEvalContextEditor",
|
||||
"when": "editorLangId == ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFile",
|
||||
"command": "codeQL.openReferencedFileContextEditor",
|
||||
"when": "resourceExtname == .qlref"
|
||||
},
|
||||
{
|
||||
@@ -1253,9 +1366,8 @@
|
||||
"name": "Databases"
|
||||
},
|
||||
{
|
||||
"id": "codeQLDatabasesExperimental",
|
||||
"name": "Databases",
|
||||
"when": "config.codeQL.canary && config.codeQL.newQueryRunExperience"
|
||||
"id": "codeQLVariantAnalysisRepositories",
|
||||
"name": "Variant Analysis Repositories"
|
||||
},
|
||||
{
|
||||
"id": "codeQLQueryHistory",
|
||||
@@ -1279,7 +1391,7 @@
|
||||
},
|
||||
{
|
||||
"view": "codeQLQueryHistory",
|
||||
"contents": "Run the 'CodeQL: Run Query' command on a QL query.\n[Run Query](command:codeQL.runQuery)"
|
||||
"contents": "You have no query history items at the moment.\n\nSelect a database to run a CodeQL query and get your first results."
|
||||
},
|
||||
{
|
||||
"view": "codeQLDatabases",
|
||||
@@ -1288,37 +1400,39 @@
|
||||
{
|
||||
"view": "codeQLEvalLogViewer",
|
||||
"contents": "Run the 'Show Evaluator Log (UI)' command on a CodeQL query run in the Query History view."
|
||||
},
|
||||
{
|
||||
"view": "codeQLVariantAnalysisRepositories",
|
||||
"contents": "Set up a controller repository to start using variant analysis. [Learn more](https://codeql.github.com/docs/codeql-for-visual-studio-code/running-codeql-queries-at-scale-with-mrva#controller-repository) about controller repositories. \n[Set up controller repository](command:codeQLVariantAnalysisRepositories.setupControllerRepository)",
|
||||
"when": "!config.codeQL.variantAnalysis.controllerRepo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
"watch": "npm-run-all -p watch:*",
|
||||
"watch:extension": "tsc --watch",
|
||||
"watch:webpack": "gulp watchView",
|
||||
"watch:files": "gulp watchTestData",
|
||||
"test": "npm-run-all -p test:*",
|
||||
"test:unit": "cross-env TZ=UTC LANG=en-US jest --projects test",
|
||||
"watch": "gulp watch",
|
||||
"test": "npm-run-all test:*",
|
||||
"test:unit": "cross-env TZ=UTC LANG=en-US jest --projects test/unit-tests",
|
||||
"test:view": "jest --projects src/view",
|
||||
"integration": "npm-run-all integration:*",
|
||||
"integration:no-workspace": "jest --projects src/vscode-tests/no-workspace",
|
||||
"integration:minimal-workspace": "jest --projects src/vscode-tests/minimal-workspace",
|
||||
"cli-integration": "jest --projects src/vscode-tests/cli-integration",
|
||||
"test:vscode-integration": "npm-run-all test:vscode-integration:*",
|
||||
"test:vscode-integration:activated-extension": "jest --projects test/vscode-tests/activated-extension",
|
||||
"test:vscode-integration:no-workspace": "jest --projects test/vscode-tests/no-workspace",
|
||||
"test:vscode-integration:minimal-workspace": "jest --projects test/vscode-tests/minimal-workspace",
|
||||
"test:cli-integration": "jest --projects test/vscode-tests/cli-integration",
|
||||
"update-vscode": "node ./node_modules/vscode/bin/install",
|
||||
"format": "prettier --write **/*.{ts,tsx} && eslint . --ext .ts,.tsx --fix",
|
||||
"lint": "eslint . --ext .ts,.tsx --max-warnings=0",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx --max-warnings=0",
|
||||
"format-staged": "lint-staged",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
"lint:scenarios": "ts-node scripts/lint-scenarios.ts",
|
||||
"check-types": "find . -type f -name \"tsconfig.json\" -not -path \"./node_modules/*\" | sed -r 's|/[^/]+$||' | sort | uniq | xargs -I {} sh -c \"echo Checking types in {} && cd {} && npx tsc --noEmit\"",
|
||||
"postinstall": "patch-package"
|
||||
"postinstall": "patch-package",
|
||||
"prepare": "cd ../.. && husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@octokit/plugin-retry": "^3.0.9",
|
||||
"@octokit/rest": "^19.0.4",
|
||||
"@primer/octicons-react": "^17.6.0",
|
||||
"@primer/react": "^35.0.0",
|
||||
"@vscode/codicons": "^0.0.31",
|
||||
"@vscode/webview-ui-toolkit": "^1.0.1",
|
||||
"ajv": "^8.11.0",
|
||||
@@ -1326,13 +1440,13 @@
|
||||
"chokidar": "^3.5.3",
|
||||
"classnames": "~2.2.6",
|
||||
"d3": "^7.6.1",
|
||||
"d3-graphviz": "^2.6.1",
|
||||
"d3-graphviz": "^5.0.2",
|
||||
"fs-extra": "^10.0.1",
|
||||
"glob-promise": "^4.2.2",
|
||||
"glob-promise": "^6.0.2",
|
||||
"immutable": "^4.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimist": "~1.2.6",
|
||||
"msw": "^0.47.4",
|
||||
"msw": "^0.49.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"node-fetch": "~2.6.7",
|
||||
"p-queue": "^6.0.0",
|
||||
@@ -1396,6 +1510,7 @@
|
||||
"@types/semver": "~7.2.0",
|
||||
"@types/stream-chain": "~2.0.1",
|
||||
"@types/stream-json": "~1.7.1",
|
||||
"@types/styled-components": "^5.1.11",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/through2": "^2.0.36",
|
||||
"@types/tmp": "^0.1.0",
|
||||
@@ -1410,12 +1525,13 @@
|
||||
"@vscode/vsce": "^2.15.0",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"applicationinsights": "^2.3.5",
|
||||
"babel-loader": "^8.2.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "~3.1.0",
|
||||
"del": "^6.0.0",
|
||||
"esbuild": "^0.15.15",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-etc": "^2.0.2",
|
||||
"eslint-plugin-github": "^4.4.1",
|
||||
"eslint-plugin-jest-dom": "^4.0.2",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
@@ -1425,19 +1541,20 @@
|
||||
"file-loader": "^6.2.0",
|
||||
"glob": "^7.1.4",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-esbuild": "^0.10.5",
|
||||
"gulp-replace": "^1.1.3",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"husky": "~4.3.8",
|
||||
"husky": "^8.0.0",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"jest-runner-vscode": "^3.0.1",
|
||||
"lint-staged": "~10.2.2",
|
||||
"lint-staged": "~13.2.0",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^6.5.0",
|
||||
"prettier": "^2.7.1",
|
||||
"tar-stream": "^2.2.0",
|
||||
"tar-stream": "^3.0.0",
|
||||
"through2": "^4.0.2",
|
||||
"ts-jest": "^29.0.1",
|
||||
"ts-json-schema-generator": "^1.1.2",
|
||||
@@ -1445,14 +1562,8 @@
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-protoc-gen": "^0.9.0",
|
||||
"typescript": "^4.5.5",
|
||||
"webpack": "^5.62.2",
|
||||
"webpack-cli": "^4.6.0"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm run format-staged",
|
||||
"pre-push": "npm run lint && scripts/forbid-test-only"
|
||||
}
|
||||
"webpack": "^5.76.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/*.{json,css,scss}": [
|
||||
|
||||
@@ -20,8 +20,8 @@ import { throttling } from "@octokit/plugin-throttling";
|
||||
import { getFiles } from "./util/files";
|
||||
import type { GitHubApiRequest } from "../src/mocks/gh-api-request";
|
||||
import { isGetVariantAnalysisRequest } from "../src/mocks/gh-api-request";
|
||||
import { VariantAnalysis } from "../src/remote-queries/gh-api/variant-analysis";
|
||||
import { RepositoryWithMetadata } from "../src/remote-queries/gh-api/repository";
|
||||
import { VariantAnalysis } from "../src/variant-analysis/gh-api/variant-analysis";
|
||||
import { RepositoryWithMetadata } from "../src/variant-analysis/gh-api/repository";
|
||||
|
||||
const extensionDirectory = resolve(__dirname, "..");
|
||||
const scenariosDirectory = resolve(extensionDirectory, "src/mocks/scenarios");
|
||||
@@ -136,7 +136,7 @@ async function addFieldsToScenarios() {
|
||||
}
|
||||
}
|
||||
|
||||
addFieldsToScenarios().catch((e) => {
|
||||
addFieldsToScenarios().catch((e: unknown) => {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ async function fixScenarioFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
fixScenarioFiles().catch((e) => {
|
||||
fixScenarioFiles().catch((e: unknown) => {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ async function lintScenarios() {
|
||||
throw new Error(`Invalid schema: ${ajv.errorsText()}`);
|
||||
}
|
||||
|
||||
const validate = await ajv.compile(schema);
|
||||
const validate = ajv.compile(schema);
|
||||
|
||||
let invalidFiles = 0;
|
||||
|
||||
@@ -66,7 +66,7 @@ async function lintScenarios() {
|
||||
}
|
||||
}
|
||||
|
||||
lintScenarios().catch((e) => {
|
||||
lintScenarios().catch((e: unknown) => {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
241
extensions/ql-vscode/scripts/source-map.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* This scripts helps finding the original source file and line number for a
|
||||
* given file and line number in the compiled extension. It currently only
|
||||
* works with released extensions.
|
||||
*
|
||||
* Usage: npx ts-node scripts/source-map.ts <version-number> <filename>:<line>:<column>
|
||||
* For example: npx ts-node scripts/source-map.ts v1.7.8 "/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131164:13"
|
||||
*
|
||||
* Alternative usage: npx ts-node scripts/source-map.ts <version-number> <multi-line-stacktrace>
|
||||
* For example: npx ts-node scripts/source-map.ts v1.7.8 'Error: Failed to find CodeQL distribution.
|
||||
* at CodeQLCliServer.getCodeQlPath (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131164:13)
|
||||
* at CodeQLCliServer.launchProcess (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131169:24)
|
||||
* at CodeQLCliServer.runCodeQlCliInternal (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131194:24)
|
||||
* at CodeQLCliServer.runJsonCodeQlCliCommand (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131330:20)
|
||||
* at CodeQLCliServer.resolveRam (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131455:12)
|
||||
* at QueryServerClient2.startQueryServerImpl (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:138618:21)'
|
||||
*/
|
||||
|
||||
import { spawnSync } from "child_process";
|
||||
import { basename, resolve } from "path";
|
||||
import { pathExists, readJSON } from "fs-extra";
|
||||
import { RawSourceMap, SourceMapConsumer } from "source-map";
|
||||
import { Open } from "unzipper";
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.error(
|
||||
"Expected 2 arguments - the version number and the filename:line number",
|
||||
);
|
||||
}
|
||||
|
||||
const stackLineRegex =
|
||||
/at (?<name>.*)? \((?<file>.*):(?<line>\d+):(?<column>\d+)\)/gm;
|
||||
|
||||
const versionNumber = process.argv[2].startsWith("v")
|
||||
? process.argv[2]
|
||||
: `v${process.argv[2]}`;
|
||||
const stacktrace = process.argv[3];
|
||||
|
||||
async function extractSourceMap() {
|
||||
const releaseAssetsDirectory = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"release-assets",
|
||||
versionNumber,
|
||||
);
|
||||
const sourceMapsDirectory = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"artifacts",
|
||||
"source-maps",
|
||||
versionNumber,
|
||||
);
|
||||
|
||||
if (!(await pathExists(sourceMapsDirectory))) {
|
||||
console.log("Downloading source maps...");
|
||||
|
||||
const release = runGhJSON<Release>([
|
||||
"release",
|
||||
"view",
|
||||
versionNumber,
|
||||
"--json",
|
||||
"id,name,assets",
|
||||
]);
|
||||
|
||||
const sourcemapAsset = release.assets.find(
|
||||
(asset) => asset.name === `vscode-codeql-sourcemaps-${versionNumber}.zip`,
|
||||
);
|
||||
|
||||
if (sourcemapAsset) {
|
||||
// This downloads a ZIP file of the source maps
|
||||
runGh([
|
||||
"release",
|
||||
"download",
|
||||
versionNumber,
|
||||
"--pattern",
|
||||
sourcemapAsset.name,
|
||||
"--dir",
|
||||
releaseAssetsDirectory,
|
||||
]);
|
||||
|
||||
const file = await Open.file(
|
||||
resolve(releaseAssetsDirectory, sourcemapAsset.name),
|
||||
);
|
||||
await file.extract({ path: sourceMapsDirectory });
|
||||
} else {
|
||||
const workflowRuns = runGhJSON<WorkflowRunListItem[]>([
|
||||
"run",
|
||||
"list",
|
||||
"--workflow",
|
||||
"release.yml",
|
||||
"--branch",
|
||||
versionNumber,
|
||||
"--json",
|
||||
"databaseId,number",
|
||||
]);
|
||||
|
||||
if (workflowRuns.length !== 1) {
|
||||
throw new Error(
|
||||
`Expected exactly one workflow run for ${versionNumber}, got ${workflowRuns.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
const workflowRun = workflowRuns[0];
|
||||
|
||||
runGh([
|
||||
"run",
|
||||
"download",
|
||||
workflowRun.databaseId.toString(),
|
||||
"--name",
|
||||
"vscode-codeql-sourcemaps",
|
||||
"--dir",
|
||||
sourceMapsDirectory,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (stacktrace.includes("at")) {
|
||||
const rawSourceMaps = new Map<string, RawSourceMap>();
|
||||
|
||||
const mappedStacktrace = await replaceAsync(
|
||||
stacktrace,
|
||||
stackLineRegex,
|
||||
async (match, name, file, line, column) => {
|
||||
if (!rawSourceMaps.has(file)) {
|
||||
const rawSourceMap: RawSourceMap = await readJSON(
|
||||
resolve(sourceMapsDirectory, `${basename(file)}.map`),
|
||||
);
|
||||
rawSourceMaps.set(file, rawSourceMap);
|
||||
}
|
||||
|
||||
const originalPosition = await SourceMapConsumer.with(
|
||||
rawSourceMaps.get(file) as RawSourceMap,
|
||||
null,
|
||||
async function (consumer) {
|
||||
return consumer.originalPositionFor({
|
||||
line: parseInt(line, 10),
|
||||
column: parseInt(column, 10),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (!originalPosition.source) {
|
||||
return match;
|
||||
}
|
||||
|
||||
const originalFilename = resolve(file, "..", originalPosition.source);
|
||||
|
||||
return `at ${originalPosition.name ?? name} (${originalFilename}:${
|
||||
originalPosition.line
|
||||
}:${originalPosition.column})`;
|
||||
},
|
||||
);
|
||||
|
||||
console.log(mappedStacktrace);
|
||||
} else {
|
||||
// This means it's just a filename:line:column
|
||||
const [filename, line, column] = stacktrace.split(":", 3);
|
||||
|
||||
const fileBasename = basename(filename);
|
||||
|
||||
const sourcemapName = `${fileBasename}.map`;
|
||||
const sourcemapPath = resolve(sourceMapsDirectory, sourcemapName);
|
||||
|
||||
if (!(await pathExists(sourcemapPath))) {
|
||||
throw new Error(`No source map found for ${fileBasename}`);
|
||||
}
|
||||
|
||||
const rawSourceMap: RawSourceMap = await readJSON(sourcemapPath);
|
||||
|
||||
const originalPosition = await SourceMapConsumer.with(
|
||||
rawSourceMap,
|
||||
null,
|
||||
async function (consumer) {
|
||||
return consumer.originalPositionFor({
|
||||
line: parseInt(line, 10),
|
||||
column: parseInt(column, 10),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (!originalPosition.source) {
|
||||
throw new Error(`No source found for ${stacktrace}`);
|
||||
}
|
||||
|
||||
const originalFilename = resolve(filename, "..", originalPosition.source);
|
||||
|
||||
console.log(
|
||||
`${originalFilename}:${originalPosition.line}:${originalPosition.column}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extractSourceMap().catch((e: unknown) => {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
function runGh(args: readonly string[]): string {
|
||||
const gh = spawnSync("gh", args);
|
||||
if (gh.status !== 0) {
|
||||
throw new Error(
|
||||
`Failed to get the source map for ${versionNumber}: ${gh.stderr}`,
|
||||
);
|
||||
}
|
||||
return gh.stdout.toString("utf-8");
|
||||
}
|
||||
|
||||
function runGhJSON<T>(args: readonly string[]): T {
|
||||
return JSON.parse(runGh(args));
|
||||
}
|
||||
|
||||
type ReleaseAsset = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Release = {
|
||||
id: string;
|
||||
name: string;
|
||||
assets: ReleaseAsset[];
|
||||
};
|
||||
|
||||
type WorkflowRunListItem = {
|
||||
databaseId: number;
|
||||
number: number;
|
||||
};
|
||||
|
||||
async function replaceAsync(
|
||||
str: string,
|
||||
regex: RegExp,
|
||||
replacer: (substring: string, ...args: any[]) => Promise<string>,
|
||||
) {
|
||||
const promises: Array<Promise<string>> = [];
|
||||
str.replace(regex, (match, ...args) => {
|
||||
const promise = replacer(match, ...args);
|
||||
promises.push(promise);
|
||||
return match;
|
||||
});
|
||||
const data = await Promise.all(promises);
|
||||
return str.replace(regex, () => data.shift() as string);
|
||||
}
|
||||
@@ -24,6 +24,7 @@ export type WebviewPanelConfig = {
|
||||
view: WebviewView;
|
||||
preserveFocus?: boolean;
|
||||
additionalOptions?: WebviewPanelOptions & WebviewOptions;
|
||||
allowWasmEval?: boolean;
|
||||
};
|
||||
|
||||
export abstract class AbstractWebview<
|
||||
@@ -51,7 +52,7 @@ export abstract class AbstractWebview<
|
||||
}
|
||||
|
||||
protected async getPanel(): Promise<WebviewPanel> {
|
||||
if (this.panel == undefined) {
|
||||
if (this.panel === undefined) {
|
||||
const { ctx } = this;
|
||||
|
||||
// This is an async method, so in theory this method can be called concurrently. To ensure that we don't
|
||||
@@ -116,6 +117,7 @@ export abstract class AbstractWebview<
|
||||
config.view,
|
||||
{
|
||||
allowInlineStyles: true,
|
||||
allowWasmEval: !!config.allowWasmEval,
|
||||
},
|
||||
);
|
||||
this.push(
|
||||
|
||||
90
extensions/ql-vscode/src/ast-cfg-commands.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Uri, window } from "vscode";
|
||||
import { withProgress } from "./progress";
|
||||
import { AstViewer } from "./astViewer";
|
||||
import {
|
||||
TemplatePrintAstProvider,
|
||||
TemplatePrintCfgProvider,
|
||||
} from "./contextual/templateProvider";
|
||||
import { compileAndRunQuery } from "./local-queries";
|
||||
import { QueryRunner } from "./queryRunner";
|
||||
import { QueryHistoryManager } from "./query-history/query-history-manager";
|
||||
import { DatabaseUI } from "./local-databases-ui";
|
||||
import { ResultsView } from "./interface";
|
||||
import { AstCfgCommands } from "./common/commands";
|
||||
|
||||
type AstCfgOptions = {
|
||||
queryRunner: QueryRunner;
|
||||
queryHistoryManager: QueryHistoryManager;
|
||||
databaseUI: DatabaseUI;
|
||||
localQueryResultsView: ResultsView;
|
||||
queryStorageDir: string;
|
||||
|
||||
astViewer: AstViewer;
|
||||
astTemplateProvider: TemplatePrintAstProvider;
|
||||
cfgTemplateProvider: TemplatePrintCfgProvider;
|
||||
};
|
||||
|
||||
export function getAstCfgCommands({
|
||||
queryRunner,
|
||||
queryHistoryManager,
|
||||
databaseUI,
|
||||
localQueryResultsView,
|
||||
queryStorageDir,
|
||||
astViewer,
|
||||
astTemplateProvider,
|
||||
cfgTemplateProvider,
|
||||
}: AstCfgOptions): AstCfgCommands {
|
||||
const viewAst = async (selectedFile: Uri) =>
|
||||
withProgress(
|
||||
async (progress, token) => {
|
||||
const ast = await astTemplateProvider.provideAst(
|
||||
progress,
|
||||
token,
|
||||
selectedFile ?? window.activeTextEditor?.document.uri,
|
||||
);
|
||||
if (ast) {
|
||||
astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName);
|
||||
}
|
||||
},
|
||||
{
|
||||
cancellable: true,
|
||||
title: "Calculate AST",
|
||||
},
|
||||
);
|
||||
|
||||
const viewCfg = async () =>
|
||||
withProgress(
|
||||
async (progress, token) => {
|
||||
const res = await cfgTemplateProvider.provideCfgUri(
|
||||
window.activeTextEditor?.document,
|
||||
);
|
||||
if (res) {
|
||||
await compileAndRunQuery(
|
||||
queryRunner,
|
||||
queryHistoryManager,
|
||||
databaseUI,
|
||||
localQueryResultsView,
|
||||
queryStorageDir,
|
||||
false,
|
||||
res[0],
|
||||
progress,
|
||||
token,
|
||||
undefined,
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Calculating Control Flow Graph",
|
||||
cancellable: true,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
"codeQL.viewAst": viewAst,
|
||||
"codeQL.viewAstContextExplorer": viewAst,
|
||||
"codeQL.viewAstContextEditor": viewAst,
|
||||
"codeQL.viewCfg": viewCfg,
|
||||
"codeQL.viewCfgContextExplorer": viewCfg,
|
||||
"codeQL.viewCfgContextEditor": viewCfg,
|
||||
};
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "vscode";
|
||||
import { basename } from "path";
|
||||
|
||||
import { DatabaseItem } from "./databases";
|
||||
import { DatabaseItem } from "./local-databases";
|
||||
import { UrlValue, BqrsId } from "./pure/bqrs-cli-types";
|
||||
import { showLocation } from "./interface-utils";
|
||||
import {
|
||||
@@ -23,9 +23,11 @@ import {
|
||||
isWholeFileLoc,
|
||||
isLineColumnLoc,
|
||||
} from "./pure/bqrs-utils";
|
||||
import { commandRunner } from "./commandRunner";
|
||||
import { DisposableObject } from "./pure/disposable-object";
|
||||
import { showAndLogErrorMessage } from "./helpers";
|
||||
import { showAndLogExceptionWithTelemetry } from "./helpers";
|
||||
import { asError, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { AstViewerCommands } from "./common/commands";
|
||||
|
||||
export interface AstItem {
|
||||
id: BqrsId;
|
||||
@@ -53,15 +55,6 @@ class AstViewerDataProvider
|
||||
readonly onDidChangeTreeData: Event<AstItem | undefined> =
|
||||
this._onDidChangeTreeData.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.push(
|
||||
commandRunner("codeQLAstViewer.gotoCode", async (item: AstItem) => {
|
||||
await showLocation(item.fileLocation);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this._onDidChangeTreeData.fire(undefined);
|
||||
}
|
||||
@@ -124,16 +117,20 @@ export class AstViewer extends DisposableObject {
|
||||
|
||||
this.push(this.treeView);
|
||||
this.push(this.treeDataProvider);
|
||||
this.push(
|
||||
commandRunner("codeQLAstViewer.clear", async () => {
|
||||
this.clear();
|
||||
}),
|
||||
);
|
||||
this.push(
|
||||
window.onDidChangeTextEditorSelection(this.updateTreeSelection, this),
|
||||
);
|
||||
}
|
||||
|
||||
getCommands(): AstViewerCommands {
|
||||
return {
|
||||
"codeQLAstViewer.clear": async () => this.clear(),
|
||||
"codeQLAstViewer.gotoCode": async (item: AstItem) => {
|
||||
await showLocation(item.fileLocation);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
updateRoots(roots: AstItem[], db: DatabaseItem, fileUri: Uri) {
|
||||
this.treeDataProvider.roots = roots;
|
||||
this.treeDataProvider.db = db;
|
||||
@@ -146,7 +143,12 @@ export class AstViewer extends DisposableObject {
|
||||
() => {
|
||||
/**/
|
||||
},
|
||||
(err) => showAndLogErrorMessage(err),
|
||||
(error: unknown) =>
|
||||
showAndLogExceptionWithTelemetry(
|
||||
redactableError(
|
||||
asError(error),
|
||||
)`Failed to reveal AST: ${getErrorMessage(error)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -204,7 +206,12 @@ export class AstViewer extends DisposableObject {
|
||||
() => {
|
||||
/**/
|
||||
},
|
||||
(err) => showAndLogErrorMessage(err),
|
||||
(error: unknown) =>
|
||||
showAndLogExceptionWithTelemetry(
|
||||
redactableError(
|
||||
asError(error),
|
||||
)`Failed to reveal AST: ${getErrorMessage(error)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +1,60 @@
|
||||
import * as vscode from "vscode";
|
||||
import * as Octokit from "@octokit/rest";
|
||||
import { retry } from "@octokit/plugin-retry";
|
||||
import { Credentials } from "./common/authentication";
|
||||
|
||||
const GITHUB_AUTH_PROVIDER_ID = "github";
|
||||
|
||||
// We need 'repo' scope for triggering workflows and 'gist' scope for exporting results to Gist.
|
||||
// We need 'repo' scope for triggering workflows, 'gist' scope for exporting results to Gist,
|
||||
// and 'read:packages' for reading private CodeQL packages.
|
||||
// For a comprehensive list of scopes, see:
|
||||
// https://docs.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps
|
||||
const SCOPES = ["repo", "gist"];
|
||||
const SCOPES = ["repo", "gist", "read:packages"];
|
||||
|
||||
/**
|
||||
* Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication).
|
||||
*/
|
||||
export class Credentials {
|
||||
export class VSCodeCredentials implements Credentials {
|
||||
/**
|
||||
* A specific octokit to return, otherwise a new authenticated octokit will be created when needed.
|
||||
*/
|
||||
private octokit: Octokit.Octokit | undefined;
|
||||
|
||||
// Explicitly make the constructor private, so that we can't accidentally call the constructor from outside the class
|
||||
// without also initializing the class.
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* Initializes an instance of credentials with an octokit instance.
|
||||
*
|
||||
* Do not call this method until you know you actually need an instance of credentials.
|
||||
* since calling this method will require the user to log in.
|
||||
*
|
||||
* @param context The extension context.
|
||||
* @returns An instance of credentials.
|
||||
*/
|
||||
static async initialize(
|
||||
context: vscode.ExtensionContext,
|
||||
): Promise<Credentials> {
|
||||
const c = new Credentials();
|
||||
c.registerListeners(context);
|
||||
c.octokit = await c.createOctokit(false);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an instance of credentials with an octokit instance using
|
||||
* a token from the user's GitHub account. This method is meant to be
|
||||
* used non-interactive environments such as tests.
|
||||
*
|
||||
* @param overrideToken The GitHub token to use for authentication.
|
||||
* @returns An instance of credentials.
|
||||
*/
|
||||
static async initializeWithToken(overrideToken: string) {
|
||||
const c = new Credentials();
|
||||
c.octokit = await c.createOctokit(false, overrideToken);
|
||||
return c;
|
||||
}
|
||||
|
||||
private async createOctokit(
|
||||
createIfNone: boolean,
|
||||
overrideToken?: string,
|
||||
): Promise<Octokit.Octokit | undefined> {
|
||||
if (overrideToken) {
|
||||
return new Octokit.Octokit({ auth: overrideToken, retry });
|
||||
}
|
||||
|
||||
const session = await vscode.authentication.getSession(
|
||||
GITHUB_AUTH_PROVIDER_ID,
|
||||
SCOPES,
|
||||
{ createIfNone },
|
||||
);
|
||||
|
||||
if (session) {
|
||||
return new Octokit.Octokit({
|
||||
auth: session.accessToken,
|
||||
retry,
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
registerListeners(context: vscode.ExtensionContext): void {
|
||||
// Sessions are changed when a user logs in or logs out.
|
||||
context.subscriptions.push(
|
||||
vscode.authentication.onDidChangeSessions(async (e) => {
|
||||
if (e.provider.id === GITHUB_AUTH_PROVIDER_ID) {
|
||||
this.octokit = await this.createOctokit(false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or returns an instance of Octokit.
|
||||
*
|
||||
* @param requireAuthentication Whether the Octokit instance needs to be authenticated as user.
|
||||
* @returns An instance of Octokit.
|
||||
*/
|
||||
async getOctokit(requireAuthentication = true): Promise<Octokit.Octokit> {
|
||||
async getOctokit(): Promise<Octokit.Octokit> {
|
||||
if (this.octokit) {
|
||||
return this.octokit;
|
||||
}
|
||||
|
||||
this.octokit = await this.createOctokit(requireAuthentication);
|
||||
const accessToken = await this.getAccessToken();
|
||||
|
||||
if (!this.octokit) {
|
||||
if (requireAuthentication) {
|
||||
throw new Error("Did not initialize Octokit.");
|
||||
}
|
||||
return new Octokit.Octokit({
|
||||
auth: accessToken,
|
||||
retry,
|
||||
});
|
||||
}
|
||||
|
||||
// We don't want to set this in this.octokit because that would prevent
|
||||
// authenticating when requireCredentials is true.
|
||||
return new Octokit.Octokit({ retry });
|
||||
}
|
||||
return this.octokit;
|
||||
async getAccessToken(): Promise<string> {
|
||||
const session = await vscode.authentication.getSession(
|
||||
GITHUB_AUTH_PROVIDER_ID,
|
||||
SCOPES,
|
||||
{ createIfNone: true },
|
||||
);
|
||||
|
||||
return session.accessToken;
|
||||
}
|
||||
|
||||
async getExistingAccessToken(): Promise<string | undefined> {
|
||||
const session = await vscode.authentication.getSession(
|
||||
GITHUB_AUTH_PROVIDER_ID,
|
||||
SCOPES,
|
||||
{ createIfNone: false },
|
||||
);
|
||||
|
||||
return session?.accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EOL } from "os";
|
||||
import { spawn } from "child-process-promise";
|
||||
import * as child_process from "child_process";
|
||||
import { readFile } from "fs-extra";
|
||||
@@ -6,7 +7,7 @@ import * as sarif from "sarif";
|
||||
import { SemVer } from "semver";
|
||||
import { Readable } from "stream";
|
||||
import { StringDecoder } from "string_decoder";
|
||||
import * as tk from "tree-kill";
|
||||
import tk from "tree-kill";
|
||||
import { promisify } from "util";
|
||||
import { CancellationToken, commands, Disposable, Uri } from "vscode";
|
||||
|
||||
@@ -25,7 +26,9 @@ import { QueryMetadata, SortDirection } from "./pure/interface-types";
|
||||
import { Logger, ProgressReporter } from "./common";
|
||||
import { CompilationMessage } from "./pure/legacy-messages";
|
||||
import { sarifParser } from "./sarif-parser";
|
||||
import { dbSchemeToLanguage, walkDirectory } from "./helpers";
|
||||
import { walkDirectory } from "./helpers";
|
||||
import { App } from "./common/app";
|
||||
import { QueryLanguage } from "./common/query-language";
|
||||
|
||||
/**
|
||||
* The version of the SARIF format that we are using.
|
||||
@@ -121,14 +124,6 @@ export interface SourceInfo {
|
||||
*/
|
||||
export type ResolvedTests = string[];
|
||||
|
||||
/**
|
||||
* Options for `codeql test run`.
|
||||
*/
|
||||
export interface TestRunOptions {
|
||||
cancellationToken?: CancellationToken;
|
||||
logger?: Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired by `codeql test run`.
|
||||
*/
|
||||
@@ -156,6 +151,10 @@ interface BqrsDecodeOptions {
|
||||
entities?: string[];
|
||||
}
|
||||
|
||||
export type OnLineCallback = (
|
||||
line: string,
|
||||
) => Promise<string | undefined> | string | undefined;
|
||||
|
||||
/**
|
||||
* This class manages a cli server started by `codeql execute cli-server` to
|
||||
* run commands without the overhead of starting a new java
|
||||
@@ -191,6 +190,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
public quiet = false;
|
||||
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private distributionProvider: DistributionProvider,
|
||||
private cliConfig: CliConfig,
|
||||
private logger: Logger,
|
||||
@@ -288,7 +288,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
return await spawnServer(
|
||||
return spawnServer(
|
||||
codeQlPath,
|
||||
"CodeQL CLI Server",
|
||||
["execute", "cli-server"],
|
||||
@@ -304,6 +304,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
command: string[],
|
||||
commandArgs: string[],
|
||||
description: string,
|
||||
onLine?: OnLineCallback,
|
||||
): Promise<string> {
|
||||
const stderrBuffers: Buffer[] = [];
|
||||
if (this.commandInProcess) {
|
||||
@@ -328,6 +329,22 @@ export class CodeQLCliServer implements Disposable {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// Start listening to stdout
|
||||
process.stdout.addListener("data", (newData: Buffer) => {
|
||||
if (onLine) {
|
||||
void (async () => {
|
||||
const response = await onLine(newData.toString("utf-8"));
|
||||
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdin.write(`${response}${EOL}`);
|
||||
|
||||
// Remove newData from stdoutBuffers because the data has been consumed
|
||||
// by the onLine callback.
|
||||
stdoutBuffers.splice(stdoutBuffers.indexOf(newData), 1);
|
||||
})();
|
||||
}
|
||||
|
||||
stdoutBuffers.push(newData);
|
||||
// If the buffer ends in '0' then exit.
|
||||
// We don't have to check the middle as no output will be written after the null until
|
||||
@@ -360,7 +377,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
this.killProcessIfRunning();
|
||||
// Report the error (if there is a stderr then use that otherwise just report the error cod or nodejs error)
|
||||
const newError =
|
||||
stderrBuffers.length == 0
|
||||
stderrBuffers.length === 0
|
||||
? new Error(`${description} failed: ${err}`)
|
||||
: new Error(
|
||||
`${description} failed: ${Buffer.concat(stderrBuffers).toString(
|
||||
@@ -431,7 +448,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
void logStream(child.stderr!, logger);
|
||||
}
|
||||
|
||||
for await (const event of await splitStreamAtSeparators(child.stdout!, [
|
||||
for await (const event of splitStreamAtSeparators(child.stdout!, [
|
||||
"\0",
|
||||
])) {
|
||||
yield event;
|
||||
@@ -460,10 +477,15 @@ export class CodeQLCliServer implements Disposable {
|
||||
command: string[],
|
||||
commandArgs: string[],
|
||||
description: string,
|
||||
cancellationToken?: CancellationToken,
|
||||
logger?: Logger,
|
||||
{
|
||||
cancellationToken,
|
||||
logger,
|
||||
}: {
|
||||
cancellationToken?: CancellationToken;
|
||||
logger?: Logger;
|
||||
} = {},
|
||||
): AsyncGenerator<EventType, void, unknown> {
|
||||
for await (const event of await this.runAsyncCodeQlCliCommandInternal(
|
||||
for await (const event of this.runAsyncCodeQlCliCommandInternal(
|
||||
command,
|
||||
commandArgs,
|
||||
cancellationToken,
|
||||
@@ -487,13 +509,20 @@ export class CodeQLCliServer implements Disposable {
|
||||
* @param commandArgs The arguments to pass to the `codeql` command.
|
||||
* @param description Description of the action being run, to be shown in log and error messages.
|
||||
* @param progressReporter Used to output progress messages, e.g. to the status bar.
|
||||
* @param onLine Used for responding to interactive output on stdout/stdin.
|
||||
* @returns The contents of the command's stdout, if the command succeeded.
|
||||
*/
|
||||
runCodeQlCliCommand(
|
||||
command: string[],
|
||||
commandArgs: string[],
|
||||
description: string,
|
||||
progressReporter?: ProgressReporter,
|
||||
{
|
||||
progressReporter,
|
||||
onLine,
|
||||
}: {
|
||||
progressReporter?: ProgressReporter;
|
||||
onLine?: OnLineCallback;
|
||||
} = {},
|
||||
): Promise<string> {
|
||||
if (progressReporter) {
|
||||
progressReporter.report({ message: description });
|
||||
@@ -503,10 +532,12 @@ export class CodeQLCliServer implements Disposable {
|
||||
// Construct the command that actually does the work
|
||||
const callback = (): void => {
|
||||
try {
|
||||
this.runCodeQlCliInternal(command, commandArgs, description).then(
|
||||
resolve,
|
||||
reject,
|
||||
);
|
||||
this.runCodeQlCliInternal(
|
||||
command,
|
||||
commandArgs,
|
||||
description,
|
||||
onLine,
|
||||
).then(resolve, reject);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
@@ -522,32 +553,38 @@ export class CodeQLCliServer implements Disposable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a CodeQL CLI command, returning the output as JSON.
|
||||
* Runs a CodeQL CLI command, parsing the output as JSON.
|
||||
* @param command The `codeql` command to be run, provided as an array of command/subcommand names.
|
||||
* @param commandArgs The arguments to pass to the `codeql` command.
|
||||
* @param description Description of the action being run, to be shown in log and error messages.
|
||||
* @param addFormat Whether or not to add commandline arguments to specify the format as JSON.
|
||||
* @param progressReporter Used to output progress messages, e.g. to the status bar.
|
||||
* @param onLine Used for responding to interactive output on stdout/stdin.
|
||||
* @returns The contents of the command's stdout, if the command succeeded.
|
||||
*/
|
||||
async runJsonCodeQlCliCommand<OutputType>(
|
||||
command: string[],
|
||||
commandArgs: string[],
|
||||
description: string,
|
||||
addFormat = true,
|
||||
progressReporter?: ProgressReporter,
|
||||
{
|
||||
addFormat = true,
|
||||
progressReporter,
|
||||
onLine,
|
||||
}: {
|
||||
addFormat?: boolean;
|
||||
progressReporter?: ProgressReporter;
|
||||
onLine?: OnLineCallback;
|
||||
} = {},
|
||||
): Promise<OutputType> {
|
||||
let args: string[] = [];
|
||||
if (addFormat)
|
||||
// Add format argument first, in case commandArgs contains positional parameters.
|
||||
args = args.concat(["--format", "json"]);
|
||||
args = args.concat(commandArgs);
|
||||
const result = await this.runCodeQlCliCommand(
|
||||
command,
|
||||
args,
|
||||
description,
|
||||
const result = await this.runCodeQlCliCommand(command, args, description, {
|
||||
progressReporter,
|
||||
);
|
||||
onLine,
|
||||
});
|
||||
try {
|
||||
return JSON.parse(result) as OutputType;
|
||||
} catch (err) {
|
||||
@@ -559,6 +596,72 @@ export class CodeQLCliServer implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a CodeQL CLI command with authentication, parsing the output as JSON.
|
||||
*
|
||||
* This method is intended for use with commands that accept a `--github-auth-stdin` argument. This
|
||||
* will be added to the command line arguments automatically if an access token is available.
|
||||
*
|
||||
* When the argument is given to the command, the CLI server will prompt for the access token on
|
||||
* stdin. This method will automatically respond to the prompt with the access token.
|
||||
*
|
||||
* There are a few race conditions that can potentially happen:
|
||||
* 1. The user logs in after the command has started. In this case, no access token will be given.
|
||||
* 2. The user logs out after the command has started. In this case, the user will be prompted
|
||||
* to login again. If they cancel the login, the old access token that was present before the
|
||||
* command was started will be used.
|
||||
*
|
||||
* @param command The `codeql` command to be run, provided as an array of command/subcommand names.
|
||||
* @param commandArgs The arguments to pass to the `codeql` command.
|
||||
* @param description Description of the action being run, to be shown in log and error messages.
|
||||
* @param addFormat Whether or not to add commandline arguments to specify the format as JSON.
|
||||
* @param progressReporter Used to output progress messages, e.g. to the status bar.
|
||||
* @returns The contents of the command's stdout, if the command succeeded.
|
||||
*/
|
||||
async runJsonCodeQlCliCommandWithAuthentication<OutputType>(
|
||||
command: string[],
|
||||
commandArgs: string[],
|
||||
description: string,
|
||||
{
|
||||
addFormat,
|
||||
progressReporter,
|
||||
}: {
|
||||
addFormat?: boolean;
|
||||
progressReporter?: ProgressReporter;
|
||||
} = {},
|
||||
): Promise<OutputType> {
|
||||
const accessToken = await this.app.credentials.getExistingAccessToken();
|
||||
|
||||
const extraArgs = accessToken ? ["--github-auth-stdin"] : [];
|
||||
|
||||
return this.runJsonCodeQlCliCommand(
|
||||
command,
|
||||
[...extraArgs, ...commandArgs],
|
||||
description,
|
||||
{
|
||||
addFormat,
|
||||
progressReporter,
|
||||
onLine: async (line) => {
|
||||
if (line.startsWith("Enter value for --github-auth-stdin")) {
|
||||
try {
|
||||
return await this.app.credentials.getAccessToken();
|
||||
} catch (e) {
|
||||
// If the user cancels the authentication prompt, we still need to give a value to the CLI.
|
||||
// By giving a potentially invalid value, the user will just get a 401/403 when they try to access a
|
||||
// private package and the access token is invalid.
|
||||
// This code path is very rare to hit. It would only be hit if the user is logged in when
|
||||
// starting the command, then logging out before the getAccessToken() is called again and
|
||||
// then cancelling the authentication prompt.
|
||||
return accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the library path and dbscheme for a query.
|
||||
* @param workspaces The current open workspaces
|
||||
@@ -623,7 +726,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
["resolve", "qlref"],
|
||||
subcommandArgs,
|
||||
"Resolving qlref",
|
||||
false,
|
||||
{
|
||||
addFormat: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -651,7 +756,13 @@ export class CodeQLCliServer implements Disposable {
|
||||
public async *runTests(
|
||||
testPaths: string[],
|
||||
workspaces: string[],
|
||||
options: TestRunOptions,
|
||||
{
|
||||
cancellationToken,
|
||||
logger,
|
||||
}: {
|
||||
cancellationToken?: CancellationToken;
|
||||
logger?: Logger;
|
||||
},
|
||||
): AsyncGenerator<TestCompleted, void, unknown> {
|
||||
const subcommandArgs = this.cliConfig.additionalTestArguments.concat([
|
||||
...this.getAdditionalPacksArg(workspaces),
|
||||
@@ -660,12 +771,14 @@ export class CodeQLCliServer implements Disposable {
|
||||
...testPaths,
|
||||
]);
|
||||
|
||||
for await (const event of await this.runAsyncCodeQlCliCommand<TestCompleted>(
|
||||
for await (const event of this.runAsyncCodeQlCliCommand<TestCompleted>(
|
||||
["test", "run"],
|
||||
subcommandArgs,
|
||||
"Run CodeQL Tests",
|
||||
options.cancellationToken,
|
||||
options.logger,
|
||||
{
|
||||
cancellationToken,
|
||||
logger,
|
||||
},
|
||||
)) {
|
||||
yield event;
|
||||
}
|
||||
@@ -696,7 +809,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
["resolve", "ml-models"],
|
||||
args,
|
||||
"Resolving ML models",
|
||||
false,
|
||||
{
|
||||
addFormat: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -720,8 +835,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
["resolve", "ram"],
|
||||
args,
|
||||
"Resolving RAM settings",
|
||||
true,
|
||||
progressReporter,
|
||||
{
|
||||
progressReporter,
|
||||
},
|
||||
);
|
||||
}
|
||||
/**
|
||||
@@ -1034,10 +1150,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
];
|
||||
if (targetDbScheme) {
|
||||
args.push("--target-dbscheme", targetDbScheme);
|
||||
if (
|
||||
allowDowngradesIfPossible &&
|
||||
(await this.cliConstraints.supportsDowngrades())
|
||||
) {
|
||||
if (allowDowngradesIfPossible) {
|
||||
args.push("--allow-downgrades");
|
||||
}
|
||||
}
|
||||
@@ -1050,24 +1163,32 @@ export class CodeQLCliServer implements Disposable {
|
||||
|
||||
/**
|
||||
* Gets information about available qlpacks
|
||||
* @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.
|
||||
* @param additionalPacks A list of directories to search for qlpacks.
|
||||
* @param extensionPacksOnly Whether to only search for extension packs. If true, only extension packs will
|
||||
* be returned. If false, all packs will be returned.
|
||||
* @returns A dictionary mapping qlpack name to the directory it comes from
|
||||
*/
|
||||
resolveQlpacks(
|
||||
async resolveQlpacks(
|
||||
additionalPacks: string[],
|
||||
searchPath?: string[],
|
||||
extensionPacksOnly = false,
|
||||
): Promise<QlpacksInfo> {
|
||||
const args = this.getAdditionalPacksArg(additionalPacks);
|
||||
if (searchPath?.length) {
|
||||
args.push("--search-path", join(...searchPath));
|
||||
if (extensionPacksOnly) {
|
||||
if (!(await this.cliConstraints.supportsQlpacksKind())) {
|
||||
void this.logger.log(
|
||||
"Warning: Running with extension packs is only supported by CodeQL CLI v2.12.3 or later.",
|
||||
);
|
||||
return {};
|
||||
}
|
||||
args.push("--kind", "extension", "--no-recursive");
|
||||
}
|
||||
|
||||
return this.runJsonCodeQlCliCommand<QlpacksInfo>(
|
||||
["resolve", "qlpacks"],
|
||||
args,
|
||||
"Resolving qlpack information",
|
||||
`Resolving qlpack information${
|
||||
extensionPacksOnly ? " (extension packs only)" : ""
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1091,9 +1212,11 @@ export class CodeQLCliServer implements Disposable {
|
||||
*/
|
||||
public async getSupportedLanguages(): Promise<string[]> {
|
||||
if (!this._supportedLanguages) {
|
||||
// Get the intersection of resolveLanguages with the list of hardcoded languages in dbSchemeToLanguage.
|
||||
// Get the intersection of resolveLanguages with the list of languages in QueryLanguage.
|
||||
const resolvedLanguages = Object.keys(await this.resolveLanguages());
|
||||
const hardcodedLanguages = Object.values(dbSchemeToLanguage);
|
||||
const hardcodedLanguages = Object.values(QueryLanguage).map((s) =>
|
||||
s.toString(),
|
||||
);
|
||||
|
||||
this._supportedLanguages = resolvedLanguages.filter((lang) =>
|
||||
hardcodedLanguages.includes(lang),
|
||||
@@ -1119,10 +1242,8 @@ export class CodeQLCliServer implements Disposable {
|
||||
if (searchPath !== undefined) {
|
||||
args.push("--search-path", join(...searchPath));
|
||||
}
|
||||
if (await this.cliConstraints.supportsAllowLibraryPacksInResolveQueries()) {
|
||||
// All of our usage of `codeql resolve queries` needs to handle library packs.
|
||||
args.push("--allow-library-packs");
|
||||
}
|
||||
// All of our usage of `codeql resolve queries` needs to handle library packs.
|
||||
args.push("--allow-library-packs");
|
||||
args.push(suite);
|
||||
return this.runJsonCodeQlCliCommand<string[]>(
|
||||
["resolve", "queries"],
|
||||
@@ -1131,24 +1252,58 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a core language QL library pack for the given query language as a dependency
|
||||
* of the current package, and then installs them. This command modifies the qlpack.yml
|
||||
* file of the current package. Formatting and comments will be removed.
|
||||
* @param dir The directory where QL pack exists.
|
||||
* @param language The language of the QL pack.
|
||||
*/
|
||||
async packAdd(dir: string, queryLanguage: QueryLanguage) {
|
||||
const args = ["--dir", dir];
|
||||
args.push(`codeql/${queryLanguage}-all`);
|
||||
return this.runJsonCodeQlCliCommandWithAuthentication(
|
||||
["pack", "add"],
|
||||
args,
|
||||
`Adding and installing ${queryLanguage} pack dependency.`,
|
||||
{
|
||||
addFormat: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a specified pack.
|
||||
* @param packs The `<package-scope/name[@version]>` of the packs to download.
|
||||
*/
|
||||
async packDownload(packs: string[]) {
|
||||
return this.runJsonCodeQlCliCommand(
|
||||
return this.runJsonCodeQlCliCommandWithAuthentication(
|
||||
["pack", "download"],
|
||||
packs,
|
||||
"Downloading packs",
|
||||
);
|
||||
}
|
||||
|
||||
async packInstall(dir: string, forceUpdate = false) {
|
||||
async packInstall(
|
||||
dir: string,
|
||||
{ forceUpdate = false, workspaceFolders = [] as string[] } = {},
|
||||
) {
|
||||
const args = [dir];
|
||||
if (forceUpdate) {
|
||||
args.push("--mode", "update");
|
||||
}
|
||||
return this.runJsonCodeQlCliCommand(
|
||||
if (workspaceFolders?.length > 0) {
|
||||
if (await this.cliConstraints.supportsAdditionalPacksInstall()) {
|
||||
args.push(
|
||||
// Allow prerelease packs from the ql submodule.
|
||||
"--allow-prerelease",
|
||||
// Allow the use of --additional-packs argument without issueing a warning
|
||||
"--no-strict-mode",
|
||||
...this.getAdditionalPacksArg(workspaceFolders),
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.runJsonCodeQlCliCommandWithAuthentication(
|
||||
["pack", "install"],
|
||||
args,
|
||||
"Installing pack dependencies",
|
||||
@@ -1169,7 +1324,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
...this.getAdditionalPacksArg(workspaceFolders),
|
||||
];
|
||||
|
||||
return this.runJsonCodeQlCliCommand(
|
||||
return this.runJsonCodeQlCliCommandWithAuthentication(
|
||||
["pack", "bundle"],
|
||||
args,
|
||||
"Bundling pack",
|
||||
@@ -1200,7 +1355,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
): Promise<{ [pack: string]: string }> {
|
||||
// Uses the default `--mode use-lock`, which creates the lock file if it doesn't exist.
|
||||
const results: { [pack: string]: string } =
|
||||
await this.runJsonCodeQlCliCommand(
|
||||
await this.runJsonCodeQlCliCommandWithAuthentication(
|
||||
["pack", "resolve-dependencies"],
|
||||
[dir],
|
||||
"Resolving pack dependencies",
|
||||
@@ -1209,12 +1364,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
}
|
||||
|
||||
async generateDil(qloFile: string, outFile: string): Promise<void> {
|
||||
const extraArgs = (await this.cliConstraints.supportsDecompileDil())
|
||||
? ["--kind", "dil", "-o", outFile, qloFile]
|
||||
: ["-o", outFile, qloFile];
|
||||
await this.runCodeQlCliCommand(
|
||||
["query", "decompile"],
|
||||
extraArgs,
|
||||
["--kind", "dil", "-o", outFile, qloFile],
|
||||
"Generating DIL",
|
||||
);
|
||||
}
|
||||
@@ -1250,6 +1402,17 @@ export class CodeQLCliServer implements Disposable {
|
||||
private getAdditionalPacksArg(paths: string[]): string[] {
|
||||
return paths.length ? ["--additional-packs", paths.join(delimiter)] : [];
|
||||
}
|
||||
|
||||
public async useExtensionPacks(): Promise<boolean> {
|
||||
return (
|
||||
this.cliConfig.useExtensionPacks &&
|
||||
(await this.cliConstraints.supportsQlpacksKind())
|
||||
);
|
||||
}
|
||||
|
||||
public async setUseExtensionPacks(useExtensionPacks: boolean) {
|
||||
await this.cliConfig.setUseExtensionPacks(useExtensionPacks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1461,7 +1624,7 @@ const lineEndings = ["\r\n", "\r", "\n"];
|
||||
* @param logger The logger that will consume the stream output.
|
||||
*/
|
||||
async function logStream(stream: Readable, logger: Logger): Promise<void> {
|
||||
for await (const line of await splitStreamAtSeparators(stream, lineEndings)) {
|
||||
for await (const line of splitStreamAtSeparators(stream, lineEndings)) {
|
||||
// Await the result of log here in order to ensure the logs are written in the correct order.
|
||||
await logger.log(line);
|
||||
}
|
||||
@@ -1492,56 +1655,6 @@ export function shouldDebugCliServer() {
|
||||
}
|
||||
|
||||
export class CliVersionConstraint {
|
||||
/**
|
||||
* CLI version where --kind=DIL was introduced
|
||||
*/
|
||||
public static CLI_VERSION_WITH_DECOMPILE_KIND_DIL = new SemVer("2.3.0");
|
||||
|
||||
/**
|
||||
* CLI version where languages are exposed during a `codeql resolve database` command.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_LANGUAGE = new SemVer("2.4.1");
|
||||
|
||||
public static CLI_VERSION_WITH_NONDESTURCTIVE_UPGRADES = new SemVer("2.4.2");
|
||||
|
||||
/**
|
||||
* CLI version where `codeql resolve upgrades` supports
|
||||
* the `--allow-downgrades` flag
|
||||
*/
|
||||
public static CLI_VERSION_WITH_DOWNGRADES = new SemVer("2.4.4");
|
||||
|
||||
/**
|
||||
* CLI version where the `codeql resolve qlref` command is available.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_RESOLVE_QLREF = new SemVer("2.5.1");
|
||||
|
||||
/**
|
||||
* CLI version where database registration was introduced
|
||||
*/
|
||||
public static CLI_VERSION_WITH_DB_REGISTRATION = new SemVer("2.4.1");
|
||||
|
||||
/**
|
||||
* CLI version where the `--allow-library-packs` option to `codeql resolve queries` was
|
||||
* introduced.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES =
|
||||
new SemVer("2.6.1");
|
||||
|
||||
/**
|
||||
* CLI version where the `database unbundle` subcommand was introduced.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_DATABASE_UNBUNDLE = new SemVer("2.6.0");
|
||||
|
||||
/**
|
||||
* CLI version where the `--no-precompile` option for pack creation was introduced.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_NO_PRECOMPILE = new SemVer("2.7.1");
|
||||
|
||||
/**
|
||||
* CLI version where remote queries (variant analysis) are supported.
|
||||
*/
|
||||
public static CLI_VERSION_REMOTE_QUERIES = new SemVer("2.6.3");
|
||||
|
||||
/**
|
||||
* CLI version where building QLX packs for remote queries is supported.
|
||||
* (The options were _accepted_ by a few earlier versions, but only from
|
||||
@@ -1549,11 +1662,6 @@ export class CliVersionConstraint {
|
||||
*/
|
||||
public static CLI_VERSION_QLX_REMOTE = new SemVer("2.11.3");
|
||||
|
||||
/**
|
||||
* CLI version where the `resolve ml-models` subcommand was introduced.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_RESOLVE_ML_MODELS = new SemVer("2.7.3");
|
||||
|
||||
/**
|
||||
* CLI version where the `resolve ml-models` subcommand was enhanced to work with packaging.
|
||||
*/
|
||||
@@ -1561,16 +1669,6 @@ export class CliVersionConstraint {
|
||||
"2.10.0",
|
||||
);
|
||||
|
||||
/**
|
||||
* CLI version where the `--old-eval-stats` option to the query server was introduced.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_OLD_EVAL_STATS = new SemVer("2.7.4");
|
||||
|
||||
/**
|
||||
* CLI version where packaging was introduced.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_PACKAGING = new SemVer("2.6.0");
|
||||
|
||||
/**
|
||||
* CLI version where the `--evaluator-log` and related options to the query server were introduced,
|
||||
* on a per-query server basis.
|
||||
@@ -1603,6 +1701,18 @@ export class CliVersionConstraint {
|
||||
*/
|
||||
public static CLI_VERSION_WITH_WORKSPACE_RFERENCES = new SemVer("2.11.3");
|
||||
|
||||
/**
|
||||
* CLI version that supports the `--kind` option for the `resolve qlpacks` command.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_QLPACKS_KIND = new SemVer("2.12.3");
|
||||
|
||||
/**
|
||||
* CLI version that supports the `--additional-packs` option for the `pack install` command.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_ADDITIONAL_PACKS_INSTALL = new SemVer(
|
||||
"2.12.4",
|
||||
);
|
||||
|
||||
constructor(private readonly cli: CodeQLCliServer) {
|
||||
/**/
|
||||
}
|
||||
@@ -1611,94 +1721,16 @@ export class CliVersionConstraint {
|
||||
return (await this.cli.getVersion()).compare(v) >= 0;
|
||||
}
|
||||
|
||||
public async supportsDecompileDil() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_DECOMPILE_KIND_DIL,
|
||||
);
|
||||
}
|
||||
|
||||
public async supportsLanguageName() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_LANGUAGE,
|
||||
);
|
||||
}
|
||||
|
||||
public async supportsNonDestructiveUpgrades() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_NONDESTURCTIVE_UPGRADES,
|
||||
);
|
||||
}
|
||||
|
||||
public async supportsDowngrades() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_DOWNGRADES,
|
||||
);
|
||||
}
|
||||
|
||||
public async supportsResolveQlref() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_QLREF,
|
||||
);
|
||||
}
|
||||
|
||||
public async supportsAllowLibraryPacksInResolveQueries() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsDatabaseRegistration() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsDatabaseUnbundle() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_DATABASE_UNBUNDLE,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsNoPrecompile() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_NO_PRECOMPILE,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsRemoteQueries() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsQlxRemote() {
|
||||
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_QLX_REMOTE);
|
||||
}
|
||||
|
||||
async supportsResolveMlModels() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_ML_MODELS,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsPreciseResolveMlModels() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_PRECISE_RESOLVE_ML_MODELS,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsOldEvalStats() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_OLD_EVAL_STATS,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsPackaging() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_PACKAGING,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsStructuredEvalLog() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_STRUCTURED_EVAL_LOG,
|
||||
@@ -1738,4 +1770,16 @@ export class CliVersionConstraint {
|
||||
CliVersionConstraint.CLI_VERSION_WITH_WORKSPACE_RFERENCES,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsQlpacksKind() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsAdditionalPacksInstall() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_ADDITIONAL_PACKS_INSTALL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,13 @@
|
||||
import { commands, Disposable } from "vscode";
|
||||
import {
|
||||
CancellationToken,
|
||||
ProgressOptions,
|
||||
window as Window,
|
||||
commands,
|
||||
Disposable,
|
||||
ProgressLocation,
|
||||
} from "vscode";
|
||||
import { showAndLogErrorMessage, showAndLogWarningMessage } from "./helpers";
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogWarningMessage,
|
||||
} from "./helpers";
|
||||
import { extLogger } from "./common";
|
||||
import { getErrorMessage, getErrorStack } from "./pure/helpers-pure";
|
||||
import { asError, getErrorMessage, getErrorStack } from "./pure/helpers-pure";
|
||||
import { telemetryListener } from "./telemetry";
|
||||
|
||||
export class UserCancellationException extends Error {
|
||||
/**
|
||||
* @param message The error message
|
||||
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
|
||||
*/
|
||||
constructor(message?: string, public readonly silent = false) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProgressUpdate {
|
||||
/**
|
||||
* The current step
|
||||
*/
|
||||
step: number;
|
||||
/**
|
||||
* The maximum step. This *should* be constant for a single job.
|
||||
*/
|
||||
maxStep: number;
|
||||
/**
|
||||
* The current progress message
|
||||
*/
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type ProgressCallback = (p: ProgressUpdate) => void;
|
||||
|
||||
/**
|
||||
* A task that handles command invocations from `commandRunner`
|
||||
* and includes a progress monitor.
|
||||
*
|
||||
*
|
||||
* Arguments passed to the command handler are passed along,
|
||||
* untouched to this `ProgressTask` instance.
|
||||
*
|
||||
* @param progress a progress handler function. Call this
|
||||
* function with a `ProgressUpdate` instance in order to
|
||||
* denote some progress being achieved on this task.
|
||||
* @param token a cencellation token
|
||||
* @param args arguments passed to this task passed on from
|
||||
* `commands.registerCommand`.
|
||||
*/
|
||||
export type ProgressTask<R> = (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
...args: any[]
|
||||
) => Thenable<R>;
|
||||
import { redactableError } from "./pure/errors";
|
||||
import { UserCancellationException } from "./progress";
|
||||
|
||||
/**
|
||||
* A task that handles command invocations from `commandRunner`.
|
||||
@@ -67,43 +17,7 @@ export type ProgressTask<R> = (
|
||||
* @param args arguments passed to this task passed on from
|
||||
* `commands.registerCommand`.
|
||||
*/
|
||||
type NoProgressTask = (...args: any[]) => Promise<any>;
|
||||
|
||||
/**
|
||||
* This mediates between the kind of progress callbacks we want to
|
||||
* write (where we *set* current progress position and give
|
||||
* `maxSteps`) and the kind vscode progress api expects us to write
|
||||
* (which increment progress by a certain amount out of 100%).
|
||||
*
|
||||
* Where possible, the `commandRunner` function below should be used
|
||||
* instead of this function. The commandRunner is meant for wrapping
|
||||
* top-level commands and provides error handling and other support
|
||||
* automatically.
|
||||
*
|
||||
* Only use this function if you need a progress monitor and the
|
||||
* control flow does not always come from a command (eg- during
|
||||
* extension activation, or from an internal language server
|
||||
* request).
|
||||
*/
|
||||
export function withProgress<R>(
|
||||
options: ProgressOptions,
|
||||
task: ProgressTask<R>,
|
||||
...args: any[]
|
||||
): Thenable<R> {
|
||||
let progressAchieved = 0;
|
||||
return Window.withProgress(options, (progress, token) => {
|
||||
return task(
|
||||
(p) => {
|
||||
const { message, step, maxStep } = p;
|
||||
const increment = (100 * (step - progressAchieved)) / maxStep;
|
||||
progressAchieved = step;
|
||||
progress.report({ message, increment });
|
||||
},
|
||||
token,
|
||||
...args,
|
||||
);
|
||||
});
|
||||
}
|
||||
export type NoProgressTask = (...args: any[]) => Promise<any>;
|
||||
|
||||
/**
|
||||
* A generic wrapper for command registration. This wrapper adds uniform error handling for commands.
|
||||
@@ -117,6 +31,7 @@ export function withProgress<R>(
|
||||
export function commandRunner(
|
||||
commandId: string,
|
||||
task: NoProgressTask,
|
||||
outputLogger = extLogger,
|
||||
): Disposable {
|
||||
return commands.registerCommand(commandId, async (...args: any[]) => {
|
||||
const startTime = Date.now();
|
||||
@@ -125,128 +40,37 @@ export function commandRunner(
|
||||
try {
|
||||
return await task(...args);
|
||||
} catch (e) {
|
||||
const errorMessage = `${getErrorMessage(e) || e} (${commandId})`;
|
||||
error = e instanceof Error ? e : new Error(errorMessage);
|
||||
error = asError(e);
|
||||
const errorMessage = redactableError(error)`${
|
||||
getErrorMessage(e) || e
|
||||
} (${commandId})`;
|
||||
const errorStack = getErrorStack(e);
|
||||
if (e instanceof UserCancellationException) {
|
||||
// User has cancelled this action manually
|
||||
if (e.silent) {
|
||||
void extLogger.log(errorMessage);
|
||||
void outputLogger.log(errorMessage.fullMessage);
|
||||
} else {
|
||||
void showAndLogWarningMessage(errorMessage);
|
||||
void showAndLogWarningMessage(errorMessage.fullMessage, {
|
||||
outputLogger,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Include the full stack in the error log only.
|
||||
const fullMessage = errorStack
|
||||
? `${errorMessage}\n${errorStack}`
|
||||
: errorMessage;
|
||||
void showAndLogErrorMessage(errorMessage, {
|
||||
fullMessage,
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
} finally {
|
||||
const executionTime = Date.now() - startTime;
|
||||
telemetryListener.sendCommandUsage(commandId, executionTime, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic wrapper for command registration. This wrapper adds uniform error handling,
|
||||
* progress monitoring, and cancellation for commands.
|
||||
*
|
||||
* @param commandId The ID of the command to register.
|
||||
* @param task The task to run. It is passed directly to `commands.registerCommand`. Any
|
||||
* arguments to the command handler are passed on to the task after the progress callback
|
||||
* and cancellation token.
|
||||
* @param progressOptions Progress options to be sent to the progress monitor.
|
||||
*/
|
||||
export function commandRunnerWithProgress<R>(
|
||||
commandId: string,
|
||||
task: ProgressTask<R>,
|
||||
progressOptions: Partial<ProgressOptions>,
|
||||
outputLogger = extLogger,
|
||||
): Disposable {
|
||||
return commands.registerCommand(commandId, async (...args: any[]) => {
|
||||
const startTime = Date.now();
|
||||
let error: Error | undefined;
|
||||
const progressOptionsWithDefaults = {
|
||||
location: ProgressLocation.Notification,
|
||||
...progressOptions,
|
||||
};
|
||||
try {
|
||||
return await withProgress(progressOptionsWithDefaults, task, ...args);
|
||||
} catch (e) {
|
||||
const errorMessage = `${getErrorMessage(e) || e} (${commandId})`;
|
||||
error = e instanceof Error ? e : new Error(errorMessage);
|
||||
const errorStack = getErrorStack(e);
|
||||
if (e instanceof UserCancellationException) {
|
||||
// User has cancelled this action manually
|
||||
if (e.silent) {
|
||||
void outputLogger.log(errorMessage);
|
||||
} else {
|
||||
void showAndLogWarningMessage(errorMessage, { outputLogger });
|
||||
}
|
||||
} else {
|
||||
// Include the full stack in the error log only.
|
||||
const fullMessage = errorStack
|
||||
? `${errorMessage}\n${errorStack}`
|
||||
: errorMessage;
|
||||
void showAndLogErrorMessage(errorMessage, {
|
||||
? `${errorMessage.fullMessage}\n${errorStack}`
|
||||
: errorMessage.fullMessage;
|
||||
void showAndLogExceptionWithTelemetry(errorMessage, {
|
||||
outputLogger,
|
||||
fullMessage,
|
||||
extraTelemetryProperties: {
|
||||
command: commandId,
|
||||
},
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
} finally {
|
||||
const executionTime = Date.now() - startTime;
|
||||
telemetryListener.sendCommandUsage(commandId, executionTime, error);
|
||||
telemetryListener?.sendCommandUsage(commandId, executionTime, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a progress monitor that indicates how much progess has been made
|
||||
* reading from a stream.
|
||||
*
|
||||
* @param readable The stream to read progress from
|
||||
* @param messagePrefix A prefix for displaying the message
|
||||
* @param totalNumBytes Total number of bytes in this stream
|
||||
* @param progress The progress callback used to set messages
|
||||
*/
|
||||
export function reportStreamProgress(
|
||||
readable: NodeJS.ReadableStream,
|
||||
messagePrefix: string,
|
||||
totalNumBytes?: number,
|
||||
progress?: ProgressCallback,
|
||||
) {
|
||||
if (progress && totalNumBytes) {
|
||||
let numBytesDownloaded = 0;
|
||||
const bytesToDisplayMB = (numBytes: number): string =>
|
||||
`${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
const updateProgress = () => {
|
||||
progress({
|
||||
step: numBytesDownloaded,
|
||||
maxStep: totalNumBytes,
|
||||
message: `${messagePrefix} [${bytesToDisplayMB(
|
||||
numBytesDownloaded,
|
||||
)} of ${bytesToDisplayMB(totalNumBytes)}]`,
|
||||
});
|
||||
};
|
||||
|
||||
// Display the progress straight away rather than waiting for the first chunk.
|
||||
updateProgress();
|
||||
|
||||
readable.on("data", (data) => {
|
||||
numBytesDownloaded += data.length;
|
||||
updateProgress();
|
||||
});
|
||||
} else if (progress) {
|
||||
progress({
|
||||
step: 1,
|
||||
maxStep: 2,
|
||||
message: `${messagePrefix} (Size unknown)`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import { Credentials } from "./authentication";
|
||||
import { Disposable } from "../pure/disposable-object";
|
||||
import { AppEventEmitter } from "./events";
|
||||
import { Logger } from "./logging";
|
||||
import { Memento } from "./memento";
|
||||
import { AppCommandManager } from "./commands";
|
||||
|
||||
export interface App {
|
||||
createEventEmitter<T>(): AppEventEmitter<T>;
|
||||
executeCommand(command: string, ...args: any): Thenable<void>;
|
||||
mode: AppMode;
|
||||
subscriptions: Disposable[];
|
||||
extensionPath: string;
|
||||
globalStoragePath: string;
|
||||
workspaceStoragePath?: string;
|
||||
readonly mode: AppMode;
|
||||
readonly logger: Logger;
|
||||
readonly subscriptions: Disposable[];
|
||||
readonly extensionPath: string;
|
||||
readonly globalStoragePath: string;
|
||||
readonly workspaceStoragePath?: string;
|
||||
readonly workspaceState: Memento;
|
||||
readonly credentials: Credentials;
|
||||
readonly commands: AppCommandManager;
|
||||
}
|
||||
|
||||
export enum AppMode {
|
||||
|
||||
34
extensions/ql-vscode/src/common/authentication.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as Octokit from "@octokit/rest";
|
||||
|
||||
/**
|
||||
* An interface providing methods for obtaining access tokens
|
||||
* or an octokit instance for making HTTP requests.
|
||||
*/
|
||||
export interface Credentials {
|
||||
/**
|
||||
* Returns an authenticated instance of Octokit.
|
||||
* May prompt the user to log in and grant permission to use their
|
||||
* token, if they have not already done so.
|
||||
*
|
||||
* @returns An instance of Octokit.
|
||||
*/
|
||||
getOctokit(): Promise<Octokit.Octokit>;
|
||||
|
||||
/**
|
||||
* Returns an OAuth access token.
|
||||
* May prompt the user to log in and grant permission to use their
|
||||
* token, if they have not already done so.
|
||||
*
|
||||
* @returns An OAuth access token.
|
||||
*/
|
||||
getAccessToken(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Returns an OAuth access token if one is available.
|
||||
* If a token is not available this will return undefined and
|
||||
* will not prompt the user to log in.
|
||||
*
|
||||
* @returns An OAuth access token, or undefined.
|
||||
*/
|
||||
getExistingAccessToken(): Promise<string | undefined>;
|
||||
}
|
||||
278
extensions/ql-vscode/src/common/commands.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import type { CommandManager } from "../packages/commands";
|
||||
import type { Uri, Range } from "vscode";
|
||||
import type { AstItem } from "../astViewer";
|
||||
import type { DbTreeViewItem } from "../databases/ui/db-tree-view-item";
|
||||
import type { DatabaseItem } from "../local-databases";
|
||||
import type { QueryHistoryInfo } from "../query-history/query-history-info";
|
||||
import type { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort";
|
||||
import type { TestTreeNode } from "../test-tree-node";
|
||||
import type {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisScannedRepository,
|
||||
VariantAnalysisScannedRepositoryResult,
|
||||
} from "../variant-analysis/shared/variant-analysis";
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
// a command on a selection is invoked.
|
||||
export type SelectionCommandFunction<Item> = (
|
||||
singleItem: Item,
|
||||
multiSelect: Item[],
|
||||
) => Promise<void>;
|
||||
|
||||
// A command function matching the signature that VS Code calls when
|
||||
// a command on a selection is invoked when canSelectMany is false.
|
||||
export type SingleSelectionCommandFunction<Item> = (
|
||||
singleItem: Item,
|
||||
) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Contains type definitions for all commands used by the extension.
|
||||
*
|
||||
* To add a new command first define its type here, then provide
|
||||
* the implementation in the corresponding `getCommands` function.
|
||||
*/
|
||||
|
||||
// Builtin commands where the implementation is provided by VS Code and not by this extension.
|
||||
// See https://code.visualstudio.com/api/references/commands
|
||||
export type BuiltInVsCodeCommands = {
|
||||
"markdown.showPreviewToSide": (uri: Uri) => Promise<void>;
|
||||
setContext: (
|
||||
key: `${"codeql" | "codeQL"}${string}`,
|
||||
value: unknown,
|
||||
) => Promise<void>;
|
||||
"workbench.action.reloadWindow": () => Promise<void>;
|
||||
};
|
||||
|
||||
// Commands that are available before the extension is fully activated.
|
||||
// These commands are *not* registered using the command manager, but can
|
||||
// be invoked using the command manager.
|
||||
export type PreActivationCommands = {
|
||||
"codeQL.checkForUpdatesToCLI": () => Promise<void>;
|
||||
};
|
||||
|
||||
// Base commands not tied directly to a module like e.g. variant analysis.
|
||||
export type BaseCommands = {
|
||||
"codeQL.openDocumentation": () => Promise<void>;
|
||||
"codeQL.showLogs": () => Promise<void>;
|
||||
"codeQL.authenticateToGitHub": () => Promise<void>;
|
||||
|
||||
"codeQL.copyVersion": () => Promise<void>;
|
||||
"codeQL.restartQueryServer": () => Promise<void>;
|
||||
};
|
||||
|
||||
// Commands used when working with queries in the editor
|
||||
export type QueryEditorCommands = {
|
||||
"codeQL.openReferencedFile": (selectedQuery: Uri) => Promise<void>;
|
||||
"codeQL.openReferencedFileContextEditor": (
|
||||
selectedQuery: Uri,
|
||||
) => Promise<void>;
|
||||
"codeQL.openReferencedFileContextExplorer": (
|
||||
selectedQuery: Uri,
|
||||
) => Promise<void>;
|
||||
"codeQL.previewQueryHelp": (selectedQuery: Uri) => Promise<void>;
|
||||
};
|
||||
|
||||
// Commands used for running local queries
|
||||
export type LocalQueryCommands = {
|
||||
"codeQL.runQuery": (uri?: Uri) => Promise<void>;
|
||||
"codeQL.runQueryContextEditor": (uri?: Uri) => Promise<void>;
|
||||
"codeQL.runQueryOnMultipleDatabases": (uri?: Uri) => Promise<void>;
|
||||
"codeQL.runQueryOnMultipleDatabasesContextEditor": (
|
||||
uri?: Uri,
|
||||
) => Promise<void>;
|
||||
"codeQL.runQueries": SelectionCommandFunction<Uri>;
|
||||
"codeQL.quickEval": (uri: Uri) => Promise<void>;
|
||||
"codeQL.quickEvalContextEditor": (uri: Uri) => Promise<void>;
|
||||
"codeQL.codeLensQuickEval": (uri: Uri, range: Range) => Promise<void>;
|
||||
"codeQL.quickQuery": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type ResultsViewCommands = {
|
||||
"codeQLQueryResults.up": () => Promise<void>;
|
||||
"codeQLQueryResults.down": () => Promise<void>;
|
||||
"codeQLQueryResults.left": () => Promise<void>;
|
||||
"codeQLQueryResults.right": () => Promise<void>;
|
||||
"codeQLQueryResults.nextPathStep": () => Promise<void>;
|
||||
"codeQLQueryResults.previousPathStep": () => Promise<void>;
|
||||
};
|
||||
|
||||
// Commands used for the query history panel
|
||||
export type QueryHistoryCommands = {
|
||||
// Commands in the "navigation" group
|
||||
"codeQLQueryHistory.sortByName": () => Promise<void>;
|
||||
"codeQLQueryHistory.sortByDate": () => Promise<void>;
|
||||
"codeQLQueryHistory.sortByCount": () => Promise<void>;
|
||||
|
||||
// Commands in the context menu or in the hover menu
|
||||
"codeQLQueryHistory.openQueryTitleMenu": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.openQueryContextMenu": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemTitleMenu": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemContextMenu": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.removeHistoryItemContextInline": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.renameItem": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.compareWith": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.showEvalLog": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.showEvalLogSummary": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.showEvalLogViewer": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.showQueryLog": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.showQueryText": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.openQueryDirectory": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.cancel": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.exportResults": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.viewCsvResults": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.viewCsvAlerts": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.viewSarifAlerts": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.viewDil": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.itemClicked": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.openOnGithub": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
"codeQLQueryHistory.copyRepoList": SelectionCommandFunction<QueryHistoryInfo>;
|
||||
|
||||
// Commands in the command palette
|
||||
"codeQL.exportSelectedVariantAnalysisResults": () => Promise<void>;
|
||||
};
|
||||
|
||||
// Commands used for the local databases panel
|
||||
export type LocalDatabasesCommands = {
|
||||
// Command palette commands
|
||||
"codeQL.chooseDatabaseFolder": () => Promise<void>;
|
||||
"codeQL.chooseDatabaseArchive": () => Promise<void>;
|
||||
"codeQL.chooseDatabaseInternet": () => Promise<void>;
|
||||
"codeQL.chooseDatabaseGithub": () => Promise<void>;
|
||||
"codeQL.upgradeCurrentDatabase": () => Promise<void>;
|
||||
"codeQL.clearCache": () => Promise<void>;
|
||||
|
||||
// Explorer context menu
|
||||
"codeQL.setCurrentDatabase": (uri: Uri) => Promise<void>;
|
||||
|
||||
// Database panel view title commands
|
||||
"codeQLDatabases.chooseDatabaseFolder": () => Promise<void>;
|
||||
"codeQLDatabases.chooseDatabaseArchive": () => Promise<void>;
|
||||
"codeQLDatabases.chooseDatabaseInternet": () => Promise<void>;
|
||||
"codeQLDatabases.chooseDatabaseGithub": () => Promise<void>;
|
||||
"codeQLDatabases.sortByName": () => Promise<void>;
|
||||
"codeQLDatabases.sortByDateAdded": () => Promise<void>;
|
||||
|
||||
// Database panel context menu
|
||||
"codeQLDatabases.setCurrentDatabase": (
|
||||
databaseItem: DatabaseItem,
|
||||
) => Promise<void>;
|
||||
|
||||
// Database panel selection commands
|
||||
"codeQLDatabases.removeDatabase": SelectionCommandFunction<DatabaseItem>;
|
||||
"codeQLDatabases.upgradeDatabase": SelectionCommandFunction<DatabaseItem>;
|
||||
"codeQLDatabases.renameDatabase": SelectionCommandFunction<DatabaseItem>;
|
||||
"codeQLDatabases.openDatabaseFolder": SelectionCommandFunction<DatabaseItem>;
|
||||
"codeQLDatabases.addDatabaseSource": SelectionCommandFunction<DatabaseItem>;
|
||||
|
||||
// Codespace template commands
|
||||
"codeQL.setDefaultTourDatabase": () => Promise<void>;
|
||||
|
||||
// Internal commands
|
||||
"codeQLDatabases.removeOrphanedDatabases": () => Promise<void>;
|
||||
};
|
||||
|
||||
// Commands tied to variant analysis
|
||||
export type VariantAnalysisCommands = {
|
||||
"codeQL.autoDownloadVariantAnalysisResult": (
|
||||
scannedRepo: VariantAnalysisScannedRepository,
|
||||
variantAnalysisSummary: VariantAnalysis,
|
||||
) => Promise<void>;
|
||||
"codeQL.copyVariantAnalysisRepoList": (
|
||||
variantAnalysisId: number,
|
||||
filterSort?: RepositoriesFilterSortStateWithIds,
|
||||
) => Promise<void>;
|
||||
"codeQL.loadVariantAnalysisRepoResults": (
|
||||
variantAnalysisId: number,
|
||||
repositoryFullName: string,
|
||||
) => Promise<VariantAnalysisScannedRepositoryResult>;
|
||||
"codeQL.monitorVariantAnalysis": (
|
||||
variantAnalysis: VariantAnalysis,
|
||||
) => Promise<void>;
|
||||
"codeQL.openVariantAnalysisLogs": (
|
||||
variantAnalysisId: number,
|
||||
) => Promise<void>;
|
||||
"codeQL.openVariantAnalysisView": (
|
||||
variantAnalysisId: number,
|
||||
) => Promise<void>;
|
||||
"codeQL.runVariantAnalysis": (uri?: Uri) => Promise<void>;
|
||||
"codeQL.runVariantAnalysisContextEditor": (uri?: Uri) => Promise<void>;
|
||||
};
|
||||
|
||||
export type DatabasePanelCommands = {
|
||||
"codeQLVariantAnalysisRepositories.openConfigFile": () => Promise<void>;
|
||||
"codeQLVariantAnalysisRepositories.addNewDatabase": () => Promise<void>;
|
||||
"codeQLVariantAnalysisRepositories.addNewList": () => Promise<void>;
|
||||
"codeQLVariantAnalysisRepositories.setupControllerRepository": () => Promise<void>;
|
||||
|
||||
"codeQLVariantAnalysisRepositories.setSelectedItem": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
"codeQLVariantAnalysisRepositories.setSelectedItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
"codeQLVariantAnalysisRepositories.openOnGitHubContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
"codeQLVariantAnalysisRepositories.renameItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
"codeQLVariantAnalysisRepositories.removeItemContextMenu": SingleSelectionCommandFunction<DbTreeViewItem>;
|
||||
};
|
||||
|
||||
export type AstCfgCommands = {
|
||||
"codeQL.viewAst": (selectedFile: Uri) => Promise<void>;
|
||||
"codeQL.viewAstContextExplorer": (selectedFile: Uri) => Promise<void>;
|
||||
"codeQL.viewAstContextEditor": (selectedFile: Uri) => Promise<void>;
|
||||
"codeQL.viewCfg": () => Promise<void>;
|
||||
"codeQL.viewCfgContextExplorer": () => Promise<void>;
|
||||
"codeQL.viewCfgContextEditor": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type AstViewerCommands = {
|
||||
"codeQLAstViewer.clear": () => Promise<void>;
|
||||
"codeQLAstViewer.gotoCode": (item: AstItem) => Promise<void>;
|
||||
};
|
||||
|
||||
export type PackagingCommands = {
|
||||
"codeQL.installPackDependencies": () => Promise<void>;
|
||||
"codeQL.downloadPacks": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type EvalLogViewerCommands = {
|
||||
"codeQLEvalLogViewer.clear": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type SummaryLanguageSupportCommands = {
|
||||
"codeQL.gotoQL": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type TestUICommands = {
|
||||
"codeQLTests.showOutputDifferences": (node: TestTreeNode) => Promise<void>;
|
||||
"codeQLTests.acceptOutput": (node: TestTreeNode) => Promise<void>;
|
||||
};
|
||||
|
||||
export type MockGitHubApiServerCommands = {
|
||||
"codeQL.mockGitHubApiServer.startRecording": () => Promise<void>;
|
||||
"codeQL.mockGitHubApiServer.saveScenario": () => Promise<void>;
|
||||
"codeQL.mockGitHubApiServer.cancelRecording": () => Promise<void>;
|
||||
"codeQL.mockGitHubApiServer.loadScenario": () => Promise<void>;
|
||||
"codeQL.mockGitHubApiServer.unloadScenario": () => Promise<void>;
|
||||
};
|
||||
|
||||
// All commands where the implementation is provided by this activated extension.
|
||||
export type AllExtensionCommands = BaseCommands &
|
||||
QueryEditorCommands &
|
||||
ResultsViewCommands &
|
||||
QueryHistoryCommands &
|
||||
LocalDatabasesCommands &
|
||||
VariantAnalysisCommands &
|
||||
DatabasePanelCommands &
|
||||
AstCfgCommands &
|
||||
AstViewerCommands &
|
||||
PackagingCommands &
|
||||
EvalLogViewerCommands &
|
||||
SummaryLanguageSupportCommands &
|
||||
Partial<TestUICommands> &
|
||||
MockGitHubApiServerCommands;
|
||||
|
||||
export type AllCommands = AllExtensionCommands &
|
||||
PreActivationCommands &
|
||||
BuiltInVsCodeCommands;
|
||||
|
||||
export type AppCommandManager = CommandManager<AllCommands>;
|
||||
|
||||
// Separate command manager because it uses a different logger
|
||||
export type QueryServerCommands = LocalQueryCommands;
|
||||
export type QueryServerCommandManager = CommandManager<QueryServerCommands>;
|
||||
@@ -0,0 +1,77 @@
|
||||
import { OWNER_REGEX, REPO_REGEX } from "../pure/helpers-pure";
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid GitHub NWO.
|
||||
* @param identifier The GitHub NWO
|
||||
* @returns
|
||||
*/
|
||||
export function isValidGitHubNwo(identifier: string): boolean {
|
||||
return validGitHubNwoOrOwner(identifier, "nwo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid GitHub owner.
|
||||
* @param identifier The GitHub owner
|
||||
* @returns
|
||||
*/
|
||||
export function isValidGitHubOwner(identifier: string): boolean {
|
||||
return validGitHubNwoOrOwner(identifier, "owner");
|
||||
}
|
||||
|
||||
function validGitHubNwoOrOwner(
|
||||
identifier: string,
|
||||
kind: "owner" | "nwo",
|
||||
): boolean {
|
||||
return kind === "owner"
|
||||
? OWNER_REGEX.test(identifier)
|
||||
: REPO_REGEX.test(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an NWO from a GitHub URL.
|
||||
* @param githubUrl The GitHub repository URL
|
||||
* @return The corresponding NWO, or undefined if the URL is not valid
|
||||
*/
|
||||
export function getNwoFromGitHubUrl(githubUrl: string): string | undefined {
|
||||
return getNwoOrOwnerFromGitHubUrl(githubUrl, "nwo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an owner from a GitHub URL.
|
||||
* @param githubUrl The GitHub repository URL
|
||||
* @return The corresponding Owner, or undefined if the URL is not valid
|
||||
*/
|
||||
export function getOwnerFromGitHubUrl(githubUrl: string): string | undefined {
|
||||
return getNwoOrOwnerFromGitHubUrl(githubUrl, "owner");
|
||||
}
|
||||
|
||||
function getNwoOrOwnerFromGitHubUrl(
|
||||
githubUrl: string,
|
||||
kind: "owner" | "nwo",
|
||||
): string | undefined {
|
||||
try {
|
||||
let paths: string[];
|
||||
const urlElements = githubUrl.split("/");
|
||||
if (
|
||||
urlElements[0] === "github.com" ||
|
||||
urlElements[0] === "www.github.com"
|
||||
) {
|
||||
paths = githubUrl.split("/").slice(1);
|
||||
} else {
|
||||
const uri = new URL(githubUrl);
|
||||
if (uri.hostname !== "github.com" && uri.hostname !== "www.github.com") {
|
||||
return;
|
||||
}
|
||||
paths = uri.pathname.split("/").filter((segment: string) => segment);
|
||||
}
|
||||
const owner = `${paths[0]}`;
|
||||
if (kind === "owner") {
|
||||
return owner ? owner : undefined;
|
||||
}
|
||||
const nwo = `${paths[0]}/${paths[1]}`;
|
||||
return paths[1] ? nwo : undefined;
|
||||
} catch (e) {
|
||||
// Ignore the error here, since we catch failures at a higher level.
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./logger";
|
||||
export * from "./tee-logger";
|
||||
export * from "./vscode/loggers";
|
||||
export * from "./vscode/output-channel-logger";
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
export interface LogOptions {
|
||||
// If false, don't output a trailing newline for the log entry. Default true.
|
||||
trailingNewline?: boolean;
|
||||
|
||||
// If specified, add this log entry to the log file at the specified location.
|
||||
additionalLogLocation?: string;
|
||||
}
|
||||
|
||||
export interface Logger {
|
||||
@@ -25,11 +22,4 @@ export interface Logger {
|
||||
* @param preserveFocus When `true` the channel will not take focus.
|
||||
*/
|
||||
show(preserveFocus?: boolean): void;
|
||||
|
||||
/**
|
||||
* Remove the log at the specified location.
|
||||
*
|
||||
* @param location log to remove
|
||||
*/
|
||||
removeAdditionalLogLocation(location: string | undefined): void;
|
||||
}
|
||||
|
||||
68
extensions/ql-vscode/src/common/logging/tee-logger.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { appendFile, ensureFile } from "fs-extra";
|
||||
import { isAbsolute } from "path";
|
||||
import { getErrorMessage } from "../../pure/helpers-pure";
|
||||
import { Logger, LogOptions } from "./logger";
|
||||
|
||||
/**
|
||||
* An implementation of {@link Logger} that sends the output both to another {@link Logger}
|
||||
* and to a file.
|
||||
*
|
||||
* The first time a message is written, an additional banner is written to the underlying logger
|
||||
* pointing the user to the "side log" file.
|
||||
*/
|
||||
export class TeeLogger implements Logger {
|
||||
private emittedRedirectMessage = false;
|
||||
private error = false;
|
||||
|
||||
public constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly location: string,
|
||||
) {
|
||||
if (!isAbsolute(location)) {
|
||||
throw new Error(
|
||||
`Additional Log Location must be an absolute path: ${location}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async log(message: string, options = {} as LogOptions): Promise<void> {
|
||||
if (!this.emittedRedirectMessage) {
|
||||
this.emittedRedirectMessage = true;
|
||||
const msg = `| Log being saved to ${this.location} |`;
|
||||
const separator = new Array(msg.length).fill("-").join("");
|
||||
await this.logger.log(separator);
|
||||
await this.logger.log(msg);
|
||||
await this.logger.log(separator);
|
||||
}
|
||||
|
||||
if (!this.error) {
|
||||
try {
|
||||
const trailingNewline = options.trailingNewline ?? true;
|
||||
await ensureFile(this.location);
|
||||
|
||||
await appendFile(
|
||||
this.location,
|
||||
message + (trailingNewline ? "\n" : ""),
|
||||
{
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
// Write an error message to the primary log, and stop trying to write to the side log.
|
||||
this.error = true;
|
||||
const errorMessage = getErrorMessage(e);
|
||||
await this.logger.log(
|
||||
`Error writing to additional log file: ${errorMessage}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.error) {
|
||||
await this.logger.log(message, options);
|
||||
}
|
||||
}
|
||||
|
||||
show(preserveFocus?: boolean): void {
|
||||
this.logger.show(preserveFocus);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import { window as Window, OutputChannel, Progress } from "vscode";
|
||||
import { ensureFile, appendFile } from "fs-extra";
|
||||
import { isAbsolute } from "path";
|
||||
import { Logger, LogOptions } from "../logger";
|
||||
import { DisposableObject } from "../../../pure/disposable-object";
|
||||
|
||||
@@ -9,10 +7,6 @@ import { DisposableObject } from "../../../pure/disposable-object";
|
||||
*/
|
||||
export class OutputChannelLogger extends DisposableObject implements Logger {
|
||||
public readonly outputChannel: OutputChannel;
|
||||
private readonly additionalLocations = new Map<
|
||||
string,
|
||||
AdditionalLogLocation
|
||||
>();
|
||||
isCustomLogDirectory: boolean;
|
||||
|
||||
constructor(title: string) {
|
||||
@@ -32,27 +26,6 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
|
||||
} else {
|
||||
this.outputChannel.append(message);
|
||||
}
|
||||
|
||||
if (options.additionalLogLocation) {
|
||||
if (!isAbsolute(options.additionalLogLocation)) {
|
||||
throw new Error(
|
||||
`Additional Log Location must be an absolute path: ${options.additionalLogLocation}`,
|
||||
);
|
||||
}
|
||||
const logPath = options.additionalLogLocation;
|
||||
let additional = this.additionalLocations.get(logPath);
|
||||
if (!additional) {
|
||||
const msg = `| Log being saved to ${logPath} |`;
|
||||
const separator = new Array(msg.length).fill("-").join("");
|
||||
this.outputChannel.appendLine(separator);
|
||||
this.outputChannel.appendLine(msg);
|
||||
this.outputChannel.appendLine(separator);
|
||||
additional = new AdditionalLogLocation(logPath);
|
||||
this.additionalLocations.set(logPath, additional);
|
||||
}
|
||||
|
||||
await additional.log(message, options);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message === "Channel has been closed") {
|
||||
// Output channel is closed logging to console instead
|
||||
@@ -69,31 +42,6 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
|
||||
show(preserveFocus?: boolean): void {
|
||||
this.outputChannel.show(preserveFocus);
|
||||
}
|
||||
|
||||
removeAdditionalLogLocation(location: string | undefined): void {
|
||||
if (location) {
|
||||
this.additionalLocations.delete(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AdditionalLogLocation {
|
||||
constructor(private location: string) {}
|
||||
|
||||
async log(message: string, options = {} as LogOptions): Promise<void> {
|
||||
if (options.trailingNewline === undefined) {
|
||||
options.trailingNewline = true;
|
||||
}
|
||||
await ensureFile(this.location);
|
||||
|
||||
await appendFile(
|
||||
this.location,
|
||||
message + (options.trailingNewline ? "\n" : ""),
|
||||
{
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type ProgressReporter = Progress<{ message: string }>;
|
||||
|
||||
44
extensions/ql-vscode/src/common/memento.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* A memento represents a storage utility. It can store and retrieve
|
||||
* values.
|
||||
*
|
||||
* It is an interface used by the VS Code API. We replicate it here
|
||||
* to avoid the dependency to the VS Code API.
|
||||
*/
|
||||
export interface Memento {
|
||||
/**
|
||||
* Returns the stored keys.
|
||||
*
|
||||
* @return The stored keys.
|
||||
*/
|
||||
keys(): readonly string[];
|
||||
|
||||
/**
|
||||
* Return a value.
|
||||
*
|
||||
* @param key A string.
|
||||
* @return The stored value or `undefined`.
|
||||
*/
|
||||
get<T>(key: string): T | undefined;
|
||||
|
||||
/**
|
||||
* Return a value.
|
||||
*
|
||||
* @param key A string.
|
||||
* @param defaultValue A value that should be returned when there is no
|
||||
* value (`undefined`) with the given key.
|
||||
* @return The stored value or the defaultValue.
|
||||
*/
|
||||
get<T>(key: string, defaultValue: T): T;
|
||||
|
||||
/**
|
||||
* Store a value. The value must be JSON-stringifyable.
|
||||
*
|
||||
* *Note* that using `undefined` as value removes the key from the underlying
|
||||
* storage.
|
||||
*
|
||||
* @param key A string.
|
||||
* @param value A value. MUST not contain cyclic references.
|
||||
*/
|
||||
update(key: string, value: any): Thenable<void>;
|
||||
}
|
||||
37
extensions/ql-vscode/src/common/query-language.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export enum QueryLanguage {
|
||||
CSharp = "csharp",
|
||||
Cpp = "cpp",
|
||||
Go = "go",
|
||||
Java = "java",
|
||||
Javascript = "javascript",
|
||||
Python = "python",
|
||||
Ruby = "ruby",
|
||||
Swift = "swift",
|
||||
}
|
||||
|
||||
export const PACKS_BY_QUERY_LANGUAGE = {
|
||||
[QueryLanguage.Cpp]: ["codeql/cpp-queries"],
|
||||
[QueryLanguage.CSharp]: [
|
||||
"codeql/csharp-queries",
|
||||
"codeql/csharp-solorigate-queries",
|
||||
],
|
||||
[QueryLanguage.Go]: ["codeql/go-queries"],
|
||||
[QueryLanguage.Java]: ["codeql/java-queries"],
|
||||
[QueryLanguage.Javascript]: [
|
||||
"codeql/javascript-queries",
|
||||
"codeql/javascript-experimental-atm-queries",
|
||||
],
|
||||
[QueryLanguage.Python]: ["codeql/python-queries"],
|
||||
[QueryLanguage.Ruby]: ["codeql/ruby-queries"],
|
||||
};
|
||||
|
||||
export const dbSchemeToLanguage = {
|
||||
"semmlecode.javascript.dbscheme": "javascript",
|
||||
"semmlecode.cpp.dbscheme": "cpp",
|
||||
"semmlecode.dbscheme": "java",
|
||||
"semmlecode.python.dbscheme": "python",
|
||||
"semmlecode.csharp.dbscheme": "csharp",
|
||||
"go.dbscheme": "go",
|
||||
"ruby.dbscheme": "ruby",
|
||||
"swift.dbscheme": "swift",
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Represents a result that can be either a value or some errors.
|
||||
*/
|
||||
export class ValueResult<TValue> {
|
||||
export class ValueResult<TValue, TError> {
|
||||
private constructor(
|
||||
private readonly errorMsgs: string[],
|
||||
private readonly errs: TError[],
|
||||
private readonly val?: TValue,
|
||||
) {}
|
||||
|
||||
public static ok<TValue>(value: TValue): ValueResult<TValue> {
|
||||
public static ok<TValue, TError>(value: TValue): ValueResult<TValue, TError> {
|
||||
if (value === undefined) {
|
||||
throw new Error("Value must be set for successful result");
|
||||
}
|
||||
@@ -15,30 +15,30 @@ export class ValueResult<TValue> {
|
||||
return new ValueResult([], value);
|
||||
}
|
||||
|
||||
public static fail<TValue>(errorMsgs: string[]): ValueResult<TValue> {
|
||||
if (errorMsgs.length === 0) {
|
||||
throw new Error(
|
||||
"At least one error message must be set for a failed result",
|
||||
);
|
||||
public static fail<TValue, TError>(
|
||||
errors: TError[],
|
||||
): ValueResult<TValue, TError> {
|
||||
if (errors.length === 0) {
|
||||
throw new Error("At least one error must be set for a failed result");
|
||||
}
|
||||
|
||||
return new ValueResult<TValue>(errorMsgs, undefined);
|
||||
return new ValueResult<TValue, TError>(errors, undefined);
|
||||
}
|
||||
|
||||
public get isOk(): boolean {
|
||||
return this.errorMsgs.length === 0;
|
||||
return this.errs.length === 0;
|
||||
}
|
||||
|
||||
public get isFailure(): boolean {
|
||||
return this.errorMsgs.length > 0;
|
||||
return this.errs.length > 0;
|
||||
}
|
||||
|
||||
public get errors(): string[] {
|
||||
if (!this.errorMsgs) {
|
||||
public get errors(): TError[] {
|
||||
if (!this.errs) {
|
||||
throw new Error("Cannot get error for successful result");
|
||||
}
|
||||
|
||||
return this.errorMsgs;
|
||||
return this.errs;
|
||||
}
|
||||
|
||||
public get value(): TValue {
|
||||
|
||||
35
extensions/ql-vscode/src/common/vscode/commands.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { commands } from "vscode";
|
||||
import { commandRunner, NoProgressTask } from "../../commandRunner";
|
||||
import { CommandFunction, CommandManager } from "../../packages/commands";
|
||||
import { OutputChannelLogger } from "../logging";
|
||||
|
||||
/**
|
||||
* Create a command manager for VSCode, wrapping the commandRunner
|
||||
* and vscode.executeCommand.
|
||||
*/
|
||||
export function createVSCodeCommandManager<
|
||||
Commands extends Record<string, CommandFunction>,
|
||||
>(outputLogger?: OutputChannelLogger): CommandManager<Commands> {
|
||||
return new CommandManager((commandId, task: NoProgressTask) => {
|
||||
return commandRunner(commandId, task, outputLogger);
|
||||
}, wrapExecuteCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapExecuteCommand wraps commands.executeCommand to satisfy that the
|
||||
* type is a Promise. Type script does not seem to be smart enough
|
||||
* to figure out that `ReturnType<Commands[CommandName]>` is actually
|
||||
* a Promise, so we need to add a second layer of wrapping and unwrapping
|
||||
* (The `Promise<Awaited<` part) to get the right types.
|
||||
*/
|
||||
async function wrapExecuteCommand<
|
||||
Commands extends Record<string, CommandFunction>,
|
||||
CommandName extends keyof Commands & string = keyof Commands & string,
|
||||
>(
|
||||
commandName: CommandName,
|
||||
...args: Parameters<Commands[CommandName]>
|
||||
): Promise<Awaited<ReturnType<Commands[CommandName]>>> {
|
||||
return await commands.executeCommand<
|
||||
Awaited<ReturnType<Commands[CommandName]>>
|
||||
>(commandName, ...args);
|
||||
}
|
||||
@@ -1,13 +1,27 @@
|
||||
import * as vscode from "vscode";
|
||||
import { VSCodeCredentials } from "../../authentication";
|
||||
import { Disposable } from "../../pure/disposable-object";
|
||||
import { App, AppMode } from "../app";
|
||||
import { AppEventEmitter } from "../events";
|
||||
import { extLogger, Logger, queryServerLogger } from "../logging";
|
||||
import { Memento } from "../memento";
|
||||
import { VSCodeAppEventEmitter } from "./events";
|
||||
import { AppCommandManager, QueryServerCommandManager } from "../commands";
|
||||
import { createVSCodeCommandManager } from "./commands";
|
||||
|
||||
export class ExtensionApp implements App {
|
||||
public readonly credentials: VSCodeCredentials;
|
||||
public readonly commands: AppCommandManager;
|
||||
public readonly queryServerCommands: QueryServerCommandManager;
|
||||
|
||||
public constructor(
|
||||
public readonly extensionContext: vscode.ExtensionContext,
|
||||
) {}
|
||||
) {
|
||||
this.credentials = new VSCodeCredentials();
|
||||
this.commands = createVSCodeCommandManager();
|
||||
this.queryServerCommands = createVSCodeCommandManager(queryServerLogger);
|
||||
extensionContext.subscriptions.push(this.commands);
|
||||
}
|
||||
|
||||
public get extensionPath(): string {
|
||||
return this.extensionContext.extensionPath;
|
||||
@@ -21,6 +35,10 @@ export class ExtensionApp implements App {
|
||||
return this.extensionContext.storageUri?.fsPath;
|
||||
}
|
||||
|
||||
public get workspaceState(): Memento {
|
||||
return this.extensionContext.workspaceState;
|
||||
}
|
||||
|
||||
public get subscriptions(): Disposable[] {
|
||||
return this.extensionContext.subscriptions;
|
||||
}
|
||||
@@ -36,11 +54,11 @@ export class ExtensionApp implements App {
|
||||
}
|
||||
}
|
||||
|
||||
public get logger(): Logger {
|
||||
return extLogger;
|
||||
}
|
||||
|
||||
public createEventEmitter<T>(): AppEventEmitter<T> {
|
||||
return new VSCodeAppEventEmitter<T>();
|
||||
}
|
||||
|
||||
public executeCommand(command: string, ...args: any): Thenable<void> {
|
||||
return vscode.commands.executeCommand(command, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../pure/interface-types";
|
||||
import { Logger } from "../common";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseManager } from "../databases";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { jumpToLocation } from "../interface-utils";
|
||||
import {
|
||||
transformBqrsResultSet,
|
||||
@@ -16,9 +16,12 @@ import {
|
||||
} from "../pure/bqrs-cli-types";
|
||||
import resultsDiff from "./resultsDiff";
|
||||
import { CompletedLocalQueryInfo } from "../query-results";
|
||||
import { getErrorMessage } from "../pure/helpers-pure";
|
||||
import { HistoryItemLabelProvider } from "../history-item-label-provider";
|
||||
import { assertNever, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { HistoryItemLabelProvider } from "../query-history/history-item-label-provider";
|
||||
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
|
||||
import { telemetryListener } from "../telemetry";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { showAndLogExceptionWithTelemetry } from "../helpers";
|
||||
|
||||
interface ComparePair {
|
||||
from: CompletedLocalQueryInfo;
|
||||
@@ -118,6 +121,9 @@ export class CompareView extends AbstractWebview<
|
||||
|
||||
case "changeCompare":
|
||||
await this.changeTable(msg.newResultSetName);
|
||||
telemetryListener?.sendUIInteraction(
|
||||
"compare-view-change-table-to-compare",
|
||||
);
|
||||
break;
|
||||
|
||||
case "viewSourceFile":
|
||||
@@ -126,7 +132,25 @@ export class CompareView extends AbstractWebview<
|
||||
|
||||
case "openQuery":
|
||||
await this.openQuery(msg.kind);
|
||||
telemetryListener?.sendUIInteraction(
|
||||
`compare-view-open-${msg.kind}-query`,
|
||||
);
|
||||
break;
|
||||
|
||||
case "telemetry":
|
||||
telemetryListener?.sendUIInteraction(msg.action);
|
||||
break;
|
||||
|
||||
case "unhandledError":
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
redactableError(
|
||||
msg.error,
|
||||
)`Unhandled error in result comparison view: ${msg.error.message}`,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertNever(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,15 +56,6 @@ export class Setting {
|
||||
.getConfiguration(this.parent.qualifiedName)
|
||||
.update(this.name, value, target);
|
||||
}
|
||||
|
||||
inspect<T>(): InspectionResult<T> | undefined {
|
||||
if (this.parent === undefined) {
|
||||
throw new Error("Cannot update the value of a root setting.");
|
||||
}
|
||||
return workspace
|
||||
.getConfiguration(this.parent.qualifiedName)
|
||||
.inspect(this.name);
|
||||
}
|
||||
}
|
||||
|
||||
export interface InspectionResult<T> {
|
||||
@@ -146,6 +137,10 @@ const DEBUG_SETTING = new Setting("debug", RUNNING_QUERIES_SETTING);
|
||||
const MAX_PATHS = new Setting("maxPaths", RUNNING_QUERIES_SETTING);
|
||||
const RUNNING_TESTS_SETTING = new Setting("runningTests", ROOT_SETTING);
|
||||
const RESULTS_DISPLAY_SETTING = new Setting("resultsDisplay", ROOT_SETTING);
|
||||
const USE_EXTENSION_PACKS = new Setting(
|
||||
"useExtensionPacks",
|
||||
RUNNING_QUERIES_SETTING,
|
||||
);
|
||||
|
||||
export const ADDITIONAL_TEST_ARGUMENTS_SETTING = new Setting(
|
||||
"additionalTestArguments",
|
||||
@@ -205,6 +200,7 @@ const CLI_SETTINGS = [
|
||||
NUMBER_OF_TEST_THREADS_SETTING,
|
||||
NUMBER_OF_THREADS_SETTING,
|
||||
MAX_PATHS,
|
||||
USE_EXTENSION_PACKS,
|
||||
];
|
||||
|
||||
export interface CliConfig {
|
||||
@@ -212,7 +208,9 @@ export interface CliConfig {
|
||||
numberTestThreads: number;
|
||||
numberThreads: number;
|
||||
maxPaths: number;
|
||||
useExtensionPacks: boolean;
|
||||
onDidChangeConfiguration?: Event<void>;
|
||||
setUseExtensionPacks: (useExtensionPacks: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class ConfigListener extends DisposableObject {
|
||||
@@ -348,7 +346,7 @@ export class QueryServerConfigListener
|
||||
if (memory === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (memory == 0 || typeof memory !== "number") {
|
||||
if (memory === 0 || typeof memory !== "number") {
|
||||
void extLogger.log(
|
||||
`Ignoring value '${memory}' for setting ${MEMORY_SETTING.qualifiedName}`,
|
||||
);
|
||||
@@ -409,6 +407,19 @@ export class CliConfigListener extends ConfigListener implements CliConfig {
|
||||
return MAX_PATHS.getValue<number>();
|
||||
}
|
||||
|
||||
public get useExtensionPacks(): boolean {
|
||||
// currently, we are restricting the values of this setting to 'all' or 'none'.
|
||||
return USE_EXTENSION_PACKS.getValue() === "all";
|
||||
}
|
||||
|
||||
// Exposed for testing only
|
||||
public async setUseExtensionPacks(newUseExtensionPacks: boolean) {
|
||||
await USE_EXTENSION_PACKS.updateValue(
|
||||
newUseExtensionPacks ? "all" : "none",
|
||||
ConfigurationTarget.Global,
|
||||
);
|
||||
}
|
||||
|
||||
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||
this.handleDidChangeConfigurationForRelevantSettings(CLI_SETTINGS, e);
|
||||
}
|
||||
@@ -476,49 +487,7 @@ export const NO_CACHE_AST_VIEWER = new Setting(
|
||||
);
|
||||
|
||||
// Settings for variant analysis
|
||||
const REMOTE_QUERIES_SETTING = new Setting("variantAnalysis", ROOT_SETTING);
|
||||
|
||||
/**
|
||||
* Lists of GitHub repositories that you want to query remotely via the "Run Variant Analysis" command.
|
||||
* Note: This command is only available for internal users.
|
||||
*
|
||||
* This setting should be a JSON object where each key is a user-specified name (string),
|
||||
* and the value is an array of GitHub repositories (of the form `<owner>/<repo>`).
|
||||
*/
|
||||
const REMOTE_REPO_LISTS = new Setting(
|
||||
"repositoryLists",
|
||||
REMOTE_QUERIES_SETTING,
|
||||
);
|
||||
|
||||
export function getRemoteRepositoryLists():
|
||||
| Record<string, string[]>
|
||||
| undefined {
|
||||
return REMOTE_REPO_LISTS.getValue<Record<string, string[]>>() || undefined;
|
||||
}
|
||||
|
||||
export async function setRemoteRepositoryLists(
|
||||
lists: Record<string, string[]> | undefined,
|
||||
) {
|
||||
await REMOTE_REPO_LISTS.updateValue(lists, ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to a file that contains lists of GitHub repositories that you want to query remotely via
|
||||
* the "Run Variant Analysis" command.
|
||||
* Note: This command is only available for internal users.
|
||||
*
|
||||
* This setting should be a path to a JSON file that contains a JSON object where each key is a
|
||||
* user-specified name (string), and the value is an array of GitHub repositories
|
||||
* (of the form `<owner>/<repo>`).
|
||||
*/
|
||||
const REPO_LISTS_PATH = new Setting(
|
||||
"repositoryListsPath",
|
||||
REMOTE_QUERIES_SETTING,
|
||||
);
|
||||
|
||||
export function getRemoteRepositoryListsPath(): string | undefined {
|
||||
return REPO_LISTS_PATH.getValue<string>() || undefined;
|
||||
}
|
||||
const VARIANT_ANALYSIS_SETTING = new Setting("variantAnalysis", ROOT_SETTING);
|
||||
|
||||
/**
|
||||
* The name of the "controller" repository that you want to use with the "Run Variant Analysis" command.
|
||||
@@ -528,7 +497,7 @@ export function getRemoteRepositoryListsPath(): string | undefined {
|
||||
*/
|
||||
const REMOTE_CONTROLLER_REPO = new Setting(
|
||||
"controllerRepo",
|
||||
REMOTE_QUERIES_SETTING,
|
||||
VARIANT_ANALYSIS_SETTING,
|
||||
);
|
||||
|
||||
export function getRemoteControllerRepo(): string | undefined {
|
||||
@@ -539,12 +508,33 @@ export async function setRemoteControllerRepo(repo: string | undefined) {
|
||||
await REMOTE_CONTROLLER_REPO.updateValue(repo, ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
export interface VariantAnalysisConfig {
|
||||
controllerRepo: string | undefined;
|
||||
onDidChangeConfiguration?: Event<void>;
|
||||
}
|
||||
|
||||
export class VariantAnalysisConfigListener
|
||||
extends ConfigListener
|
||||
implements VariantAnalysisConfig
|
||||
{
|
||||
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||
this.handleDidChangeConfigurationForRelevantSettings(
|
||||
[VARIANT_ANALYSIS_SETTING],
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
public get controllerRepo(): string | undefined {
|
||||
return getRemoteControllerRepo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The branch of "github/codeql-variant-analysis-action" to use with the "Run Variant Analysis" command.
|
||||
* Default value is "main".
|
||||
* Note: This command is only available for internal users.
|
||||
*/
|
||||
const ACTION_BRANCH = new Setting("actionBranch", REMOTE_QUERIES_SETTING);
|
||||
const ACTION_BRANCH = new Setting("actionBranch", VARIANT_ANALYSIS_SETTING);
|
||||
|
||||
export function getActionBranch(): string {
|
||||
return ACTION_BRANCH.getValue<string>() || "main";
|
||||
@@ -558,19 +548,6 @@ export function isVariantAnalysisLiveResultsEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A flag indicating whether to use the new query run experience which involves
|
||||
* using a new database panel.
|
||||
*/
|
||||
const NEW_QUERY_RUN_EXPERIENCE = new Setting(
|
||||
"newQueryRunExperience",
|
||||
ROOT_SETTING,
|
||||
);
|
||||
|
||||
export function isNewQueryRunExperienceEnabled(): boolean {
|
||||
return !!NEW_QUERY_RUN_EXPERIENCE.getValue<boolean>();
|
||||
}
|
||||
|
||||
// Settings for mocking the GitHub API.
|
||||
const MOCK_GH_API_SERVER = new Setting("mockGitHubApiServer", ROOT_SETTING);
|
||||
|
||||
@@ -618,3 +595,16 @@ export class MockGitHubApiConfigListener
|
||||
export function getMockGitHubApiServerScenariosPath(): string | undefined {
|
||||
return MOCK_GH_API_SERVER_SCENARIOS_PATH.getValue<string>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables features that are specific to the codespaces-codeql template workspace from
|
||||
* https://github.com/github/codespaces-codeql.
|
||||
*/
|
||||
export const CODESPACES_TEMPLATE = new Setting(
|
||||
"codespacesTemplate",
|
||||
ROOT_SETTING,
|
||||
);
|
||||
|
||||
export function isCodespacesTemplate() {
|
||||
return !!CODESPACES_TEMPLATE.getValue<boolean>();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DecodedBqrsChunk, BqrsId, EntityValue } from "../pure/bqrs-cli-types";
|
||||
import { DatabaseItem } from "../databases";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { ChildAstItem, AstItem } from "../astViewer";
|
||||
import fileRangeFromURI from "./fileRangeFromURI";
|
||||
import { Uri } from "vscode";
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as vscode from "vscode";
|
||||
|
||||
import { UrlValue, LineColumnLocation } from "../pure/bqrs-cli-types";
|
||||
import { isEmptyPath } from "../pure/bqrs-utils";
|
||||
import { DatabaseItem } from "../databases";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
|
||||
export default function fileRangeFromURI(
|
||||
uri: UrlValue | undefined,
|
||||
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
ResultSetSchema,
|
||||
} from "../pure/bqrs-cli-types";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseManager, DatabaseItem } from "../databases";
|
||||
import { DatabaseManager, DatabaseItem } from "../local-databases";
|
||||
import fileRangeFromURI from "./fileRangeFromURI";
|
||||
import { ProgressCallback } from "../commandRunner";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { KeyType } from "./keyType";
|
||||
import {
|
||||
qlpackOfDatabase,
|
||||
@@ -128,9 +128,9 @@ function createTemplates(path: string): Record<string, string> {
|
||||
function isValidSelect(selectInfo: ResultSetSchema | undefined) {
|
||||
return (
|
||||
selectInfo &&
|
||||
selectInfo.columns.length == 3 &&
|
||||
selectInfo.columns[0].kind == ColumnKindCode.ENTITY &&
|
||||
selectInfo.columns[1].kind == ColumnKindCode.ENTITY &&
|
||||
selectInfo.columns[2].kind == ColumnKindCode.STRING
|
||||
selectInfo.columns.length === 3 &&
|
||||
selectInfo.columns[0].kind === ColumnKindCode.ENTITY &&
|
||||
selectInfo.columns[1].kind === ColumnKindCode.ENTITY &&
|
||||
selectInfo.columns[2].kind === ColumnKindCode.STRING
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,17 +7,19 @@ import {
|
||||
getPrimaryDbscheme,
|
||||
getQlPackForDbscheme,
|
||||
getOnDiskWorkspaceFolders,
|
||||
showAndLogErrorMessage,
|
||||
QlPacksForLanguage,
|
||||
showAndLogExceptionWithTelemetry,
|
||||
} from "../helpers";
|
||||
import { KeyType, kindOfKeyType, nameOfKeyType, tagOfKeyType } from "./keyType";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseItem } from "../databases";
|
||||
import { DatabaseItem } from "../local-databases";
|
||||
import { extLogger } from "../common";
|
||||
import { createInitialQueryInfo } from "../run-queries-shared";
|
||||
import { CancellationToken, Uri } from "vscode";
|
||||
import { ProgressCallback } from "../commandRunner";
|
||||
import { ProgressCallback } from "../progress";
|
||||
import { QueryRunner } from "../queryRunner";
|
||||
import { redactableError } from "../pure/errors";
|
||||
import { QLPACK_FILENAMES } from "../pure/ql";
|
||||
|
||||
export async function qlpackOfDatabase(
|
||||
cli: CodeQLCliServer,
|
||||
@@ -74,39 +76,12 @@ export async function resolveQueries(
|
||||
qlpacks: QlPacksForLanguage,
|
||||
keyType: KeyType,
|
||||
): Promise<string[]> {
|
||||
const cliCanHandleLibraryPack =
|
||||
await cli.cliConstraints.supportsAllowLibraryPacksInResolveQueries();
|
||||
const packsToSearch: string[] = [];
|
||||
let blameCli: boolean;
|
||||
|
||||
if (cliCanHandleLibraryPack) {
|
||||
// The CLI can handle both library packs and query packs, so search both packs in order.
|
||||
packsToSearch.push(qlpacks.dbschemePack);
|
||||
if (qlpacks.queryPack !== undefined) {
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
// If we don't find the query, it's because it's not there, not because the CLI was unable to
|
||||
// search the pack.
|
||||
blameCli = false;
|
||||
} else {
|
||||
// Older CLIs can't handle `codeql resolve queries` with a suite that references a library pack.
|
||||
if (qlpacks.dbschemePackIsLibraryPack) {
|
||||
if (qlpacks.queryPack !== undefined) {
|
||||
// Just search the query pack, because some older library/query releases still had the
|
||||
// contextual queries in the query pack.
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
// If we don't find it, it's because the CLI was unable to search the library pack that
|
||||
// actually contains the query. Blame any failure on the CLI, not the packs.
|
||||
blameCli = true;
|
||||
} else {
|
||||
// We have an old CLI, but the dbscheme pack is old enough that it's still a unified pack with
|
||||
// both libraries and queries. Just search that pack.
|
||||
packsToSearch.push(qlpacks.dbschemePack);
|
||||
// Any CLI should be able to search the single query pack, so if we don't find it, it's
|
||||
// because the language doesn't support it.
|
||||
blameCli = false;
|
||||
}
|
||||
// The CLI can handle both library packs and query packs, so search both packs in order.
|
||||
packsToSearch.push(qlpacks.dbschemePack);
|
||||
if (qlpacks.queryPack !== undefined) {
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
|
||||
const queries = await resolveQueriesFromPacks(cli, packsToSearch, keyType);
|
||||
@@ -115,26 +90,16 @@ export async function resolveQueries(
|
||||
}
|
||||
|
||||
// No queries found. Determine the correct error message for the various scenarios.
|
||||
const errorMessage = blameCli
|
||||
? `Your current version of the CodeQL CLI, '${
|
||||
(await cli.getVersion()).version
|
||||
}', \
|
||||
is unable to use contextual queries from recent versions of the standard CodeQL libraries. \
|
||||
Please upgrade to the latest version of the CodeQL CLI.`
|
||||
: `No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(
|
||||
keyType,
|
||||
)}") could be found in the current library path. \
|
||||
Try upgrading the CodeQL libraries. If that doesn't work, then ${nameOfKeyType(
|
||||
keyType,
|
||||
)} queries are not yet available \
|
||||
for this language.`;
|
||||
const keyTypeName = nameOfKeyType(keyType);
|
||||
const keyTypeTag = tagOfKeyType(keyType);
|
||||
const joinedPacksToSearch = packsToSearch.join(", ");
|
||||
const error = redactableError`No ${keyTypeName} queries (tagged "${keyTypeTag}") could be found in the \
|
||||
current library path (tried searching the following packs: ${joinedPacksToSearch}). \
|
||||
Try upgrading the CodeQL libraries. If that doesn't work, then ${keyTypeName} queries are not yet available \
|
||||
for this language.`;
|
||||
|
||||
void showAndLogErrorMessage(errorMessage);
|
||||
throw new Error(
|
||||
`Couldn't find any queries tagged ${tagOfKeyType(
|
||||
keyType,
|
||||
)} in any of the following packs: ${packsToSearch.join(", ")}.`,
|
||||
);
|
||||
void showAndLogExceptionWithTelemetry(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
async function resolveContextualQuery(
|
||||
@@ -149,7 +114,7 @@ async function resolveContextualQuery(
|
||||
// Work out the enclosing pack.
|
||||
const packContents = await cli.packPacklist(query, false);
|
||||
const packFilePath = packContents.find((p) =>
|
||||
["codeql-pack.yml", "qlpack.yml"].includes(basename(p)),
|
||||
QLPACK_FILENAMES.includes(basename(p)),
|
||||
);
|
||||
if (packFilePath === undefined) {
|
||||
// Should not happen; we already resolved this query.
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Location,
|
||||
LocationLink,
|
||||
Position,
|
||||
ProgressLocation,
|
||||
ReferenceContext,
|
||||
ReferenceProvider,
|
||||
TextDocument,
|
||||
@@ -17,9 +16,9 @@ import {
|
||||
zipArchiveScheme,
|
||||
} from "../archive-filesystem-provider";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { DatabaseManager } from "../databases";
|
||||
import { DatabaseManager } from "../local-databases";
|
||||
import { CachedOperation } from "../helpers";
|
||||
import { ProgressCallback, withProgress } from "../commandRunner";
|
||||
import { ProgressCallback, withProgress } from "../progress";
|
||||
import AstBuilder from "./astBuilder";
|
||||
import { KeyType } from "./keyType";
|
||||
import {
|
||||
@@ -73,11 +72,6 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
|
||||
|
||||
private async getDefinitions(uriString: string): Promise<LocationLink[]> {
|
||||
return withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
cancellable: true,
|
||||
title: "Finding definitions",
|
||||
},
|
||||
async (progress, token) => {
|
||||
return getLocationsForUriString(
|
||||
this.cli,
|
||||
@@ -91,6 +85,10 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
|
||||
(src, _dest) => src === uriString,
|
||||
);
|
||||
},
|
||||
{
|
||||
cancellable: true,
|
||||
title: "Finding definitions",
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -136,11 +134,6 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
|
||||
|
||||
private async getReferences(uriString: string): Promise<FullLocationLink[]> {
|
||||
return withProgress(
|
||||
{
|
||||
location: ProgressLocation.Notification,
|
||||
cancellable: true,
|
||||
title: "Finding references",
|
||||
},
|
||||
async (progress, token) => {
|
||||
return getLocationsForUriString(
|
||||
this.cli,
|
||||
@@ -154,6 +147,10 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
|
||||
(src, _dest) => src === uriString,
|
||||
);
|
||||
},
|
||||
{
|
||||
cancellable: true,
|
||||
title: "Finding references",
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,16 @@ import { basename, join } from "path";
|
||||
import * as Octokit from "@octokit/rest";
|
||||
import { retry } from "@octokit/plugin-retry";
|
||||
|
||||
import { DatabaseManager, DatabaseItem } from "./databases";
|
||||
import { DatabaseManager, DatabaseItem } from "./local-databases";
|
||||
import { showAndLogInformationMessage, tmpDir } from "./helpers";
|
||||
import { reportStreamProgress, ProgressCallback } from "./commandRunner";
|
||||
import { reportStreamProgress, ProgressCallback } from "./progress";
|
||||
import { extLogger } from "./common";
|
||||
import { Credentials } from "./authentication";
|
||||
import { REPO_REGEX, getErrorMessage } from "./pure/helpers-pure";
|
||||
import { getErrorMessage } from "./pure/helpers-pure";
|
||||
import {
|
||||
getNwoFromGitHubUrl,
|
||||
isValidGitHubNwo,
|
||||
} from "./common/github-url-identifier-helper";
|
||||
import { Credentials } from "./common/authentication";
|
||||
|
||||
/**
|
||||
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
|
||||
@@ -96,19 +100,16 @@ export async function promptImportGithubDatabase(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!looksLikeGithubRepo(githubRepo)) {
|
||||
const nwo = getNwoFromGitHubUrl(githubRepo) || githubRepo;
|
||||
if (!isValidGitHubNwo(nwo)) {
|
||||
throw new Error(`Invalid GitHub repository: ${githubRepo}`);
|
||||
}
|
||||
|
||||
const octokit = credentials
|
||||
? await credentials.getOctokit(true)
|
||||
? await credentials.getOctokit()
|
||||
: new Octokit.Octokit({ retry });
|
||||
|
||||
const result = await convertGithubNwoToDatabaseUrl(
|
||||
githubRepo,
|
||||
octokit,
|
||||
progress,
|
||||
);
|
||||
const result = await convertGithubNwoToDatabaseUrl(nwo, octokit, progress);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
@@ -149,74 +150,6 @@ export async function promptImportGithubDatabase(
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts a user to fetch a database from lgtm.
|
||||
* User enters a project url and then the user is asked which language
|
||||
* to download (if there is more than one)
|
||||
*
|
||||
* @param databaseManager the DatabaseManager
|
||||
* @param storagePath where to store the unzipped database.
|
||||
*/
|
||||
export async function promptImportLgtmDatabase(
|
||||
databaseManager: DatabaseManager,
|
||||
storagePath: string,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
cli?: CodeQLCliServer,
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
progress({
|
||||
message: "Choose project",
|
||||
step: 1,
|
||||
maxStep: 2,
|
||||
});
|
||||
const lgtmUrl = await window.showInputBox({
|
||||
prompt:
|
||||
"Enter the project slug or URL on LGTM (e.g., g/github/codeql or https://lgtm.com/projects/g/github/codeql)",
|
||||
});
|
||||
if (!lgtmUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (looksLikeLgtmUrl(lgtmUrl)) {
|
||||
const databaseUrl = await convertLgtmUrlToDatabaseUrl(lgtmUrl, progress);
|
||||
if (databaseUrl) {
|
||||
const item = await databaseArchiveFetcher(
|
||||
databaseUrl,
|
||||
{},
|
||||
databaseManager,
|
||||
storagePath,
|
||||
undefined,
|
||||
progress,
|
||||
token,
|
||||
cli,
|
||||
);
|
||||
if (item) {
|
||||
await commands.executeCommand("codeQLDatabases.focus");
|
||||
void showAndLogInformationMessage(
|
||||
"Database downloaded and imported successfully.",
|
||||
);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Invalid LGTM URL: ${lgtmUrl}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
export async function retrieveCanonicalRepoName(lgtmUrl: string) {
|
||||
const givenRepoName = extractProjectSlug(lgtmUrl);
|
||||
const response = await checkForFailingResponse(
|
||||
await fetch(`https://api.github.com/repos/${givenRepoName}`),
|
||||
"Failed to locate the repository on github",
|
||||
);
|
||||
const repo = await response.json();
|
||||
if (!repo || !repo.full_name) {
|
||||
return;
|
||||
}
|
||||
return repo.full_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a database from a local archive.
|
||||
*
|
||||
@@ -388,7 +321,7 @@ async function readAndUnzip(
|
||||
step: 9,
|
||||
message: `Unzipping into ${basename(unzipPath)}`,
|
||||
});
|
||||
if (cli && (await cli.cliConstraints.supportsDatabaseUnbundle())) {
|
||||
if (cli) {
|
||||
// Use the `database unbundle` command if the installed cli version supports it
|
||||
await cli.databaseUnbundle(zipFile, unzipPath);
|
||||
} else {
|
||||
@@ -509,57 +442,8 @@ export async function findDirWithFile(
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL pattern is https://github.com/{owner}/{name}/{subpages}.
|
||||
*
|
||||
* This function accepts any URL that matches the pattern above. It also accepts just the
|
||||
* name with owner (NWO): `<owner>/<repo>`.
|
||||
*
|
||||
* @param githubRepo The GitHub repository URL or NWO
|
||||
*
|
||||
* @return true if this looks like a valid GitHub repository URL or NWO
|
||||
*/
|
||||
export function looksLikeGithubRepo(
|
||||
githubRepo: string | undefined,
|
||||
): githubRepo is string {
|
||||
if (!githubRepo) {
|
||||
return false;
|
||||
}
|
||||
if (REPO_REGEX.test(githubRepo) || convertGitHubUrlToNwo(githubRepo)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a GitHub repository URL to the corresponding NWO.
|
||||
* @param githubUrl The GitHub repository URL
|
||||
* @return The corresponding NWO, or undefined if the URL is not valid
|
||||
*/
|
||||
function convertGitHubUrlToNwo(githubUrl: string): string | undefined {
|
||||
try {
|
||||
const uri = Uri.parse(githubUrl, true);
|
||||
if (uri.scheme !== "https") {
|
||||
return;
|
||||
}
|
||||
if (uri.authority !== "github.com" && uri.authority !== "www.github.com") {
|
||||
return;
|
||||
}
|
||||
const paths = uri.path.split("/").filter((segment: string) => segment);
|
||||
const nwo = `${paths[0]}/${paths[1]}`;
|
||||
if (REPO_REGEX.test(nwo)) {
|
||||
return nwo;
|
||||
}
|
||||
return;
|
||||
} catch (e) {
|
||||
// Ignore the error here, since we catch failures at a higher level.
|
||||
// In particular: returning undefined leads to an error in 'promptImportGithubDatabase'.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export async function convertGithubNwoToDatabaseUrl(
|
||||
githubRepo: string,
|
||||
nwo: string,
|
||||
octokit: Octokit.Octokit,
|
||||
progress: ProgressCallback,
|
||||
): Promise<
|
||||
@@ -571,7 +455,6 @@ export async function convertGithubNwoToDatabaseUrl(
|
||||
| undefined
|
||||
> {
|
||||
try {
|
||||
const nwo = convertGitHubUrlToNwo(githubRepo) || githubRepo;
|
||||
const [owner, repo] = nwo.split("/");
|
||||
|
||||
const response = await octokit.request(
|
||||
@@ -593,131 +476,10 @@ export async function convertGithubNwoToDatabaseUrl(
|
||||
};
|
||||
} catch (e) {
|
||||
void extLogger.log(`Error: ${getErrorMessage(e)}`);
|
||||
throw new Error(`Unable to get database for '${githubRepo}'`);
|
||||
throw new Error(`Unable to get database for '${nwo}'`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL pattern is https://lgtm.com/projects/{provider}/{org}/{name}/{irrelevant-subpages}.
|
||||
* There are several possibilities for the provider: in addition to GitHub.com (g),
|
||||
* LGTM currently hosts projects from Bitbucket (b), GitLab (gl) and plain git (git).
|
||||
*
|
||||
* This function accepts any url that matches the pattern above. It also accepts the
|
||||
* raw project slug, e.g., `g/myorg/myproject`
|
||||
*
|
||||
* After the `{provider}/{org}/{name}` path components, there may be the components
|
||||
* related to sub pages.
|
||||
*
|
||||
* @param lgtmUrl The URL to the lgtm project
|
||||
*
|
||||
* @return true if this looks like an LGTM project url
|
||||
*/
|
||||
// exported for testing
|
||||
export function looksLikeLgtmUrl(
|
||||
lgtmUrl: string | undefined,
|
||||
): lgtmUrl is string {
|
||||
if (!lgtmUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (convertRawLgtmSlug(lgtmUrl)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const uri = Uri.parse(lgtmUrl, true);
|
||||
if (uri.scheme !== "https") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (uri.authority !== "lgtm.com" && uri.authority !== "www.lgtm.com") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const paths = uri.path.split("/").filter((segment: string) => segment);
|
||||
return paths.length >= 4 && paths[0] === "projects";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function convertRawLgtmSlug(maybeSlug: string): string | undefined {
|
||||
if (!maybeSlug) {
|
||||
return;
|
||||
}
|
||||
const segments = maybeSlug.split("/");
|
||||
const providers = ["g", "gl", "b", "git"];
|
||||
if (segments.length === 3 && providers.includes(segments[0])) {
|
||||
return `https://lgtm.com/projects/${maybeSlug}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function extractProjectSlug(lgtmUrl: string): string | undefined {
|
||||
// Only matches the '/g/' provider (github)
|
||||
const re = new RegExp("https://lgtm.com/projects/g/(.*[^/])");
|
||||
const match = lgtmUrl.match(re);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
// exported for testing
|
||||
export async function convertLgtmUrlToDatabaseUrl(
|
||||
lgtmUrl: string,
|
||||
progress: ProgressCallback,
|
||||
) {
|
||||
try {
|
||||
lgtmUrl = convertRawLgtmSlug(lgtmUrl) || lgtmUrl;
|
||||
let projectJson = await downloadLgtmProjectMetadata(lgtmUrl);
|
||||
|
||||
if (projectJson.code === 404) {
|
||||
// fallback check for github repositories with same name but different case
|
||||
// will fail for other providers
|
||||
let canonicalName = await retrieveCanonicalRepoName(lgtmUrl);
|
||||
if (!canonicalName) {
|
||||
throw new Error(`Project was not found at ${lgtmUrl}.`);
|
||||
}
|
||||
canonicalName = convertRawLgtmSlug(`g/${canonicalName}`);
|
||||
projectJson = await downloadLgtmProjectMetadata(canonicalName);
|
||||
if (projectJson.code === 404) {
|
||||
throw new Error("Failed to download project from LGTM.");
|
||||
}
|
||||
}
|
||||
|
||||
const languages =
|
||||
projectJson?.languages?.map(
|
||||
(lang: { language: string }) => lang.language,
|
||||
) || [];
|
||||
|
||||
const language = await promptForLanguage(languages, progress);
|
||||
if (!language) {
|
||||
return;
|
||||
}
|
||||
return `https://lgtm.com/${[
|
||||
"api",
|
||||
"v1.0",
|
||||
"snapshots",
|
||||
projectJson.id,
|
||||
language,
|
||||
].join("/")}`;
|
||||
} catch (e) {
|
||||
void extLogger.log(`Error: ${getErrorMessage(e)}`);
|
||||
throw new Error(`Invalid LGTM URL: ${lgtmUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadLgtmProjectMetadata(lgtmUrl: string): Promise<any> {
|
||||
const uri = Uri.parse(lgtmUrl, true);
|
||||
const paths = ["api", "v1.0"]
|
||||
.concat(uri.path.split("/").filter((segment: string) => segment))
|
||||
.slice(0, 6);
|
||||
const projectUrl = `https://lgtm.com/${paths.join("/")}`;
|
||||
const projectResponse = await fetch(projectUrl);
|
||||
return projectResponse.json();
|
||||
}
|
||||
|
||||
async function promptForLanguage(
|
||||
languages: string[],
|
||||
progress: ProgressCallback,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
### Databases
|
||||
|
||||
This folder contains code for the new experimental databases panel and new query run experience.
|
||||
This folder contains tests for the variant analysis repository panel.
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
import { pathExists, writeJSON, readJSON, readJSONSync } from "fs-extra";
|
||||
import { pathExists, outputJSON, readJSON, readJSONSync } from "fs-extra";
|
||||
import { join } from "path";
|
||||
import {
|
||||
clearLocalDbConfig,
|
||||
cloneDbConfig,
|
||||
DbConfig,
|
||||
ExpandedDbItem,
|
||||
initializeLocalDbConfig,
|
||||
removeLocalDb,
|
||||
removeLocalList,
|
||||
removeRemoteList,
|
||||
removeRemoteOwner,
|
||||
removeRemoteRepo,
|
||||
renameLocalDb,
|
||||
renameLocalList,
|
||||
renameRemoteList,
|
||||
SelectedDbItem,
|
||||
DB_CONFIG_VERSION,
|
||||
SelectedDbItemKind,
|
||||
} from "./db-config";
|
||||
import * as chokidar from "chokidar";
|
||||
import { DisposableObject, DisposeHandler } from "../../pure/disposable-object";
|
||||
import { DbConfigValidator } from "./db-config-validator";
|
||||
import { ValueResult } from "../../common/value-result";
|
||||
import { App } from "../../common/app";
|
||||
import { AppEvent, AppEventEmitter } from "../../common/events";
|
||||
import {
|
||||
DbConfigValidationError,
|
||||
DbConfigValidationErrorKind,
|
||||
} from "../db-validation-errors";
|
||||
import { ValueResult } from "../../common/value-result";
|
||||
import {
|
||||
LocalDatabaseDbItem,
|
||||
LocalListDbItem,
|
||||
RemoteUserDefinedListDbItem,
|
||||
DbItem,
|
||||
DbItemKind,
|
||||
} from "../db-item";
|
||||
|
||||
export class DbConfigStore extends DisposableObject {
|
||||
public static readonly databaseConfigFileName = "databases.json";
|
||||
|
||||
public readonly onDidChangeConfig: AppEvent<void>;
|
||||
private readonly onDidChangeConfigEventEmitter: AppEventEmitter<void>;
|
||||
|
||||
@@ -21,14 +45,17 @@ export class DbConfigStore extends DisposableObject {
|
||||
private readonly configValidator: DbConfigValidator;
|
||||
|
||||
private config: DbConfig | undefined;
|
||||
private configErrors: string[];
|
||||
private configErrors: DbConfigValidationError[];
|
||||
private configWatcher: chokidar.FSWatcher | undefined;
|
||||
|
||||
public constructor(app: App) {
|
||||
public constructor(
|
||||
private readonly app: App,
|
||||
private readonly shouldWatchConfig = true,
|
||||
) {
|
||||
super();
|
||||
|
||||
const storagePath = app.workspaceStoragePath || app.globalStoragePath;
|
||||
this.configPath = join(storagePath, "workspace-databases.json");
|
||||
this.configPath = join(storagePath, DbConfigStore.databaseConfigFileName);
|
||||
|
||||
this.config = this.createEmptyConfig();
|
||||
this.configErrors = [];
|
||||
@@ -40,7 +67,9 @@ export class DbConfigStore extends DisposableObject {
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
await this.loadConfig();
|
||||
this.watchConfig();
|
||||
if (this.shouldWatchConfig) {
|
||||
this.watchConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(disposeHandler?: DisposeHandler): void {
|
||||
@@ -48,7 +77,7 @@ export class DbConfigStore extends DisposableObject {
|
||||
this.configWatcher?.unwatch(this.configPath);
|
||||
}
|
||||
|
||||
public getConfig(): ValueResult<DbConfig> {
|
||||
public getConfig(): ValueResult<DbConfig, DbConfigValidationError> {
|
||||
if (this.config) {
|
||||
// Clone the config so that it's not modified outside of this class.
|
||||
return ValueResult.ok(cloneDbConfig(this.config));
|
||||
@@ -69,7 +98,7 @@ export class DbConfigStore extends DisposableObject {
|
||||
throw Error("Cannot select database item if config is not loaded");
|
||||
}
|
||||
|
||||
const config: DbConfig = {
|
||||
const config = {
|
||||
...this.config,
|
||||
selected: dbItem,
|
||||
};
|
||||
@@ -77,46 +106,269 @@ export class DbConfigStore extends DisposableObject {
|
||||
await this.writeConfig(config);
|
||||
}
|
||||
|
||||
public async updateExpandedState(expandedItems: ExpandedDbItem[]) {
|
||||
public async removeDbItem(dbItem: DbItem): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot update expansion state if config is not loaded");
|
||||
throw Error("Cannot remove item if config is not loaded");
|
||||
}
|
||||
|
||||
const config: DbConfig = {
|
||||
...this.config,
|
||||
expanded: expandedItems,
|
||||
};
|
||||
let config: DbConfig;
|
||||
|
||||
switch (dbItem.kind) {
|
||||
case DbItemKind.LocalList:
|
||||
config = removeLocalList(this.config, dbItem.listName);
|
||||
break;
|
||||
case DbItemKind.RemoteUserDefinedList:
|
||||
config = removeRemoteList(this.config, dbItem.listName);
|
||||
break;
|
||||
case DbItemKind.LocalDatabase:
|
||||
// When we start using local databases these need to be removed from disk as well.
|
||||
config = removeLocalDb(
|
||||
this.config,
|
||||
dbItem.databaseName,
|
||||
dbItem.parentListName,
|
||||
);
|
||||
break;
|
||||
case DbItemKind.RemoteRepo:
|
||||
config = removeRemoteRepo(
|
||||
this.config,
|
||||
dbItem.repoFullName,
|
||||
dbItem.parentListName,
|
||||
);
|
||||
break;
|
||||
case DbItemKind.RemoteOwner:
|
||||
config = removeRemoteOwner(this.config, dbItem.ownerName);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Type '${dbItem.kind}' cannot be removed`);
|
||||
}
|
||||
|
||||
await this.writeConfig(config);
|
||||
}
|
||||
|
||||
public async addRemoteRepo(
|
||||
repoNwo: string,
|
||||
parentList?: string,
|
||||
): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot add variant analysis repo if config is not loaded");
|
||||
}
|
||||
|
||||
if (repoNwo === "") {
|
||||
throw Error("Repository name cannot be empty");
|
||||
}
|
||||
|
||||
if (this.doesRemoteDbExist(repoNwo, parentList)) {
|
||||
throw Error(
|
||||
`A variant analysis repository with the name '${repoNwo}' already exists`,
|
||||
);
|
||||
}
|
||||
|
||||
const config = cloneDbConfig(this.config);
|
||||
if (parentList) {
|
||||
const parent = config.databases.variantAnalysis.repositoryLists.find(
|
||||
(list) => list.name === parentList,
|
||||
);
|
||||
if (!parent) {
|
||||
throw Error(`Cannot find parent list '${parentList}'`);
|
||||
} else {
|
||||
parent.repositories.push(repoNwo);
|
||||
}
|
||||
} else {
|
||||
config.databases.variantAnalysis.repositories.push(repoNwo);
|
||||
}
|
||||
await this.writeConfig(config);
|
||||
}
|
||||
|
||||
public async addRemoteOwner(owner: string): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot add owner if config is not loaded");
|
||||
}
|
||||
|
||||
if (owner === "") {
|
||||
throw Error("Owner name cannot be empty");
|
||||
}
|
||||
|
||||
if (this.doesRemoteOwnerExist(owner)) {
|
||||
throw Error(`An owner with the name '${owner}' already exists`);
|
||||
}
|
||||
|
||||
const config = cloneDbConfig(this.config);
|
||||
config.databases.variantAnalysis.owners.push(owner);
|
||||
|
||||
await this.writeConfig(config);
|
||||
}
|
||||
|
||||
public async addLocalList(listName: string): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot add local list if config is not loaded");
|
||||
}
|
||||
|
||||
this.validateLocalListName(listName);
|
||||
|
||||
const config = cloneDbConfig(this.config);
|
||||
config.databases.local.lists.push({
|
||||
name: listName,
|
||||
databases: [],
|
||||
});
|
||||
|
||||
await this.writeConfig(config);
|
||||
}
|
||||
|
||||
public async addRemoteList(listName: string): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot add remote list if config is not loaded");
|
||||
throw Error("Cannot add variant analysis list if config is not loaded");
|
||||
}
|
||||
|
||||
const config: DbConfig = cloneDbConfig(this.config);
|
||||
config.databases.remote.repositoryLists.push({
|
||||
this.validateRemoteListName(listName);
|
||||
|
||||
const config = cloneDbConfig(this.config);
|
||||
config.databases.variantAnalysis.repositoryLists.push({
|
||||
name: listName,
|
||||
repositories: [],
|
||||
});
|
||||
|
||||
// TODO: validate that the name doesn't already exist
|
||||
await this.writeConfig(config);
|
||||
}
|
||||
|
||||
public async renameLocalList(
|
||||
currentDbItem: LocalListDbItem,
|
||||
newName: string,
|
||||
) {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot rename local list if config is not loaded");
|
||||
}
|
||||
|
||||
this.validateLocalListName(newName);
|
||||
|
||||
const updatedConfig = renameLocalList(
|
||||
this.config,
|
||||
currentDbItem.listName,
|
||||
newName,
|
||||
);
|
||||
|
||||
await this.writeConfig(updatedConfig);
|
||||
}
|
||||
|
||||
public async renameRemoteList(
|
||||
currentDbItem: RemoteUserDefinedListDbItem,
|
||||
newName: string,
|
||||
) {
|
||||
if (!this.config) {
|
||||
throw Error(
|
||||
"Cannot rename variant analysis list if config is not loaded",
|
||||
);
|
||||
}
|
||||
|
||||
this.validateRemoteListName(newName);
|
||||
|
||||
const updatedConfig = renameRemoteList(
|
||||
this.config,
|
||||
currentDbItem.listName,
|
||||
newName,
|
||||
);
|
||||
|
||||
await this.writeConfig(updatedConfig);
|
||||
}
|
||||
|
||||
public async renameLocalDb(
|
||||
currentDbItem: LocalDatabaseDbItem,
|
||||
newName: string,
|
||||
parentListName?: string,
|
||||
): Promise<void> {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot rename local db if config is not loaded");
|
||||
}
|
||||
|
||||
this.validateLocalDbName(newName);
|
||||
|
||||
const updatedConfig = renameLocalDb(
|
||||
this.config,
|
||||
currentDbItem.databaseName,
|
||||
newName,
|
||||
parentListName,
|
||||
);
|
||||
|
||||
await this.writeConfig(updatedConfig);
|
||||
}
|
||||
|
||||
public doesRemoteListExist(listName: string): boolean {
|
||||
if (!this.config) {
|
||||
throw Error(
|
||||
"Cannot check variant analysis list existence if config is not loaded",
|
||||
);
|
||||
}
|
||||
|
||||
return this.config.databases.variantAnalysis.repositoryLists.some(
|
||||
(l) => l.name === listName,
|
||||
);
|
||||
}
|
||||
|
||||
public doesLocalListExist(listName: string): boolean {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot check local list existence if config is not loaded");
|
||||
}
|
||||
|
||||
return this.config.databases.local.lists.some((l) => l.name === listName);
|
||||
}
|
||||
|
||||
public doesLocalDbExist(dbName: string, listName?: string): boolean {
|
||||
if (!this.config) {
|
||||
throw Error(
|
||||
"Cannot check variant analysis repository existence if config is not loaded",
|
||||
);
|
||||
}
|
||||
|
||||
if (listName) {
|
||||
return this.config.databases.local.lists.some(
|
||||
(l) =>
|
||||
l.name === listName && l.databases.some((d) => d.name === dbName),
|
||||
);
|
||||
}
|
||||
|
||||
return this.config.databases.local.databases.some((d) => d.name === dbName);
|
||||
}
|
||||
|
||||
public doesRemoteDbExist(dbName: string, listName?: string): boolean {
|
||||
if (!this.config) {
|
||||
throw Error(
|
||||
"Cannot check variant analysis repository existence if config is not loaded",
|
||||
);
|
||||
}
|
||||
|
||||
if (listName) {
|
||||
return this.config.databases.variantAnalysis.repositoryLists.some(
|
||||
(l) => l.name === listName && l.repositories.includes(dbName),
|
||||
);
|
||||
}
|
||||
|
||||
return this.config.databases.variantAnalysis.repositories.includes(dbName);
|
||||
}
|
||||
|
||||
public doesRemoteOwnerExist(owner: string): boolean {
|
||||
if (!this.config) {
|
||||
throw Error("Cannot check owner existence if config is not loaded");
|
||||
}
|
||||
|
||||
return this.config.databases.variantAnalysis.owners.includes(owner);
|
||||
}
|
||||
|
||||
private async writeConfig(config: DbConfig): Promise<void> {
|
||||
await writeJSON(this.configPath, config, {
|
||||
clearLocalDbConfig(config);
|
||||
await outputJSON(this.configPath, config, {
|
||||
spaces: 2,
|
||||
});
|
||||
}
|
||||
|
||||
private async loadConfig(): Promise<void> {
|
||||
if (!(await pathExists(this.configPath))) {
|
||||
void this.app.logger.log(
|
||||
`Creating new database config file at ${this.configPath}`,
|
||||
);
|
||||
await this.writeConfig(this.createEmptyConfig());
|
||||
}
|
||||
|
||||
await this.readConfig();
|
||||
void this.app.logger.log(`Database config loaded from ${this.configPath}`);
|
||||
}
|
||||
|
||||
private async readConfig(): Promise<void> {
|
||||
@@ -124,14 +376,34 @@ export class DbConfigStore extends DisposableObject {
|
||||
try {
|
||||
newConfig = await readJSON(this.configPath);
|
||||
} catch (e) {
|
||||
this.configErrors = [`Failed to read config file: ${this.configPath}`];
|
||||
this.configErrors = [
|
||||
{
|
||||
kind: DbConfigValidationErrorKind.InvalidJson,
|
||||
message: `Failed to read config file: ${this.configPath}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (newConfig) {
|
||||
initializeLocalDbConfig(newConfig);
|
||||
this.configErrors = this.configValidator.validate(newConfig);
|
||||
}
|
||||
|
||||
this.config = this.configErrors.length === 0 ? newConfig : undefined;
|
||||
if (this.configErrors.length === 0) {
|
||||
this.config = newConfig;
|
||||
await this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeQLVariantAnalysisRepositories.configError",
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
this.config = undefined;
|
||||
await this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeQLVariantAnalysisRepositories.configError",
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private readConfigSync(): void {
|
||||
@@ -139,28 +411,59 @@ export class DbConfigStore extends DisposableObject {
|
||||
try {
|
||||
newConfig = readJSONSync(this.configPath);
|
||||
} catch (e) {
|
||||
this.configErrors = [`Failed to read config file: ${this.configPath}`];
|
||||
this.configErrors = [
|
||||
{
|
||||
kind: DbConfigValidationErrorKind.InvalidJson,
|
||||
message: `Failed to read config file: ${this.configPath}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (newConfig) {
|
||||
initializeLocalDbConfig(newConfig);
|
||||
this.configErrors = this.configValidator.validate(newConfig);
|
||||
}
|
||||
|
||||
this.config = this.configErrors.length === 0 ? newConfig : undefined;
|
||||
|
||||
if (this.configErrors.length === 0) {
|
||||
this.config = newConfig;
|
||||
void this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeQLVariantAnalysisRepositories.configError",
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
this.config = undefined;
|
||||
void this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeQLVariantAnalysisRepositories.configError",
|
||||
true,
|
||||
);
|
||||
}
|
||||
this.onDidChangeConfigEventEmitter.fire();
|
||||
}
|
||||
|
||||
private watchConfig(): void {
|
||||
this.configWatcher = chokidar.watch(this.configPath).on("change", () => {
|
||||
this.readConfigSync();
|
||||
});
|
||||
this.configWatcher = chokidar
|
||||
.watch(this.configPath, {
|
||||
// In some cases, change events are emitted while the file is still
|
||||
// being written. The awaitWriteFinish option tells the watcher to
|
||||
// poll the file size, holding its add and change events until the size
|
||||
// does not change for a configurable amount of time. We set that time
|
||||
// to 1 second, but it may need to be adjusted if there are issues.
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 1000,
|
||||
},
|
||||
})
|
||||
.on("change", () => {
|
||||
this.readConfigSync();
|
||||
});
|
||||
}
|
||||
|
||||
private createEmptyConfig(): DbConfig {
|
||||
return {
|
||||
version: DB_CONFIG_VERSION,
|
||||
databases: {
|
||||
remote: {
|
||||
variantAnalysis: {
|
||||
repositoryLists: [],
|
||||
owners: [],
|
||||
repositories: [],
|
||||
@@ -170,7 +473,42 @@ export class DbConfigStore extends DisposableObject {
|
||||
databases: [],
|
||||
},
|
||||
},
|
||||
expanded: [],
|
||||
selected: {
|
||||
kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList,
|
||||
listName: "top_10",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private validateLocalListName(listName: string): void {
|
||||
if (listName === "") {
|
||||
throw Error("List name cannot be empty");
|
||||
}
|
||||
|
||||
if (this.doesLocalListExist(listName)) {
|
||||
throw Error(`A local list with the name '${listName}' already exists`);
|
||||
}
|
||||
}
|
||||
|
||||
private validateRemoteListName(listName: string): void {
|
||||
if (listName === "") {
|
||||
throw Error("List name cannot be empty");
|
||||
}
|
||||
|
||||
if (this.doesRemoteListExist(listName)) {
|
||||
throw Error(
|
||||
`A variant analysis list with the name '${listName}' already exists`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private validateLocalDbName(dbName: string): void {
|
||||
if (dbName === "") {
|
||||
throw Error("Database name cannot be empty");
|
||||
}
|
||||
|
||||
if (this.doesLocalDbExist(dbName)) {
|
||||
throw Error(`A local database with the name '${dbName}' already exists`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,145 @@
|
||||
import { readJsonSync } from "fs-extra";
|
||||
import { resolve } from "path";
|
||||
import Ajv from "ajv";
|
||||
import { DbConfig } from "./db-config";
|
||||
import Ajv, { ValidateFunction } from "ajv";
|
||||
import { clearLocalDbConfig, DbConfig } from "./db-config";
|
||||
import { findDuplicateStrings } from "../../pure/text-utils";
|
||||
import {
|
||||
DbConfigValidationError,
|
||||
DbConfigValidationErrorKind,
|
||||
} from "../db-validation-errors";
|
||||
|
||||
export class DbConfigValidator {
|
||||
private readonly schema: any;
|
||||
private readonly validateSchemaFn: ValidateFunction;
|
||||
|
||||
constructor(extensionPath: string) {
|
||||
const schemaPath = resolve(
|
||||
extensionPath,
|
||||
"workspace-databases-schema.json",
|
||||
);
|
||||
this.schema = readJsonSync(schemaPath);
|
||||
const schemaPath = resolve(extensionPath, "databases-schema.json");
|
||||
const schema = readJsonSync(schemaPath);
|
||||
const schemaValidator = new Ajv({ allErrors: true });
|
||||
this.validateSchemaFn = schemaValidator.compile(schema);
|
||||
}
|
||||
|
||||
public validate(dbConfig: DbConfig): string[] {
|
||||
const ajv = new Ajv({ allErrors: true });
|
||||
ajv.validate(this.schema, dbConfig);
|
||||
public validate(dbConfig: DbConfig): DbConfigValidationError[] {
|
||||
const localDbs = clearLocalDbConfig(dbConfig);
|
||||
|
||||
if (ajv.errors) {
|
||||
return ajv.errors.map(
|
||||
(error) => `${error.instancePath} ${error.message}`,
|
||||
);
|
||||
this.validateSchemaFn(dbConfig);
|
||||
|
||||
if (this.validateSchemaFn.errors) {
|
||||
return this.validateSchemaFn.errors.map((error) => ({
|
||||
kind: DbConfigValidationErrorKind.InvalidConfig,
|
||||
message: `${error.instancePath} ${error.message}`,
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
// Add any local db config back so that we have a config
|
||||
// object that respects its type and validation can happen
|
||||
// as normal.
|
||||
if (localDbs) {
|
||||
dbConfig.databases.local = localDbs;
|
||||
}
|
||||
|
||||
return [
|
||||
...this.validateDbListNames(dbConfig),
|
||||
...this.validateDbNames(dbConfig),
|
||||
...this.validateDbNamesInLists(dbConfig),
|
||||
...this.validateOwners(dbConfig),
|
||||
];
|
||||
}
|
||||
|
||||
private validateDbListNames(dbConfig: DbConfig): DbConfigValidationError[] {
|
||||
const errors: DbConfigValidationError[] = [];
|
||||
|
||||
const buildError = (dups: string[]) => ({
|
||||
kind: DbConfigValidationErrorKind.DuplicateNames,
|
||||
message: `There are database lists with the same name: ${dups.join(
|
||||
", ",
|
||||
)}`,
|
||||
});
|
||||
|
||||
const duplicateLocalDbLists = findDuplicateStrings(
|
||||
dbConfig.databases.local.lists.map((n) => n.name),
|
||||
);
|
||||
|
||||
if (duplicateLocalDbLists.length > 0) {
|
||||
errors.push(buildError(duplicateLocalDbLists));
|
||||
}
|
||||
|
||||
const duplicateRemoteDbLists = findDuplicateStrings(
|
||||
dbConfig.databases.variantAnalysis.repositoryLists.map((n) => n.name),
|
||||
);
|
||||
if (duplicateRemoteDbLists.length > 0) {
|
||||
errors.push(buildError(duplicateRemoteDbLists));
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private validateDbNames(dbConfig: DbConfig): DbConfigValidationError[] {
|
||||
const errors: DbConfigValidationError[] = [];
|
||||
|
||||
const buildError = (dups: string[]) => ({
|
||||
kind: DbConfigValidationErrorKind.DuplicateNames,
|
||||
message: `There are databases with the same name: ${dups.join(", ")}`,
|
||||
});
|
||||
|
||||
const duplicateLocalDbs = findDuplicateStrings(
|
||||
dbConfig.databases.local.databases.map((d) => d.name),
|
||||
);
|
||||
|
||||
if (duplicateLocalDbs.length > 0) {
|
||||
errors.push(buildError(duplicateLocalDbs));
|
||||
}
|
||||
|
||||
const duplicateRemoteDbs = findDuplicateStrings(
|
||||
dbConfig.databases.variantAnalysis.repositories,
|
||||
);
|
||||
if (duplicateRemoteDbs.length > 0) {
|
||||
errors.push(buildError(duplicateRemoteDbs));
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private validateDbNamesInLists(
|
||||
dbConfig: DbConfig,
|
||||
): DbConfigValidationError[] {
|
||||
const errors: DbConfigValidationError[] = [];
|
||||
|
||||
const buildError = (listName: string, dups: string[]) => ({
|
||||
kind: DbConfigValidationErrorKind.DuplicateNames,
|
||||
message: `There are databases with the same name in the ${listName} list: ${dups.join(
|
||||
", ",
|
||||
)}`,
|
||||
});
|
||||
|
||||
for (const list of dbConfig.databases.local.lists) {
|
||||
const dups = findDuplicateStrings(list.databases.map((d) => d.name));
|
||||
if (dups.length > 0) {
|
||||
errors.push(buildError(list.name, dups));
|
||||
}
|
||||
}
|
||||
|
||||
for (const list of dbConfig.databases.variantAnalysis.repositoryLists) {
|
||||
const dups = findDuplicateStrings(list.repositories);
|
||||
if (dups.length > 0) {
|
||||
errors.push(buildError(list.name, dups));
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private validateOwners(dbConfig: DbConfig): DbConfigValidationError[] {
|
||||
const errors: DbConfigValidationError[] = [];
|
||||
|
||||
const dups = findDuplicateStrings(
|
||||
dbConfig.databases.variantAnalysis.owners,
|
||||
);
|
||||
if (dups.length > 0) {
|
||||
errors.push({
|
||||
kind: DbConfigValidationErrorKind.DuplicateNames,
|
||||
message: `There are owners with the same name: ${dups.join(", ")}`,
|
||||
});
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// Contains models for the data we want to store in the database config
|
||||
// Contains models and consts for the data we want to store in the database config.
|
||||
// Changes to these models should be done carefully and account for backwards compatibility of data.
|
||||
|
||||
export const DB_CONFIG_VERSION = 1;
|
||||
|
||||
export interface DbConfig {
|
||||
version: number;
|
||||
databases: DbConfigDatabases;
|
||||
expanded: ExpandedDbItem[];
|
||||
selected?: SelectedDbItem;
|
||||
}
|
||||
|
||||
export interface DbConfigDatabases {
|
||||
remote: RemoteDbConfig;
|
||||
variantAnalysis: RemoteDbConfig;
|
||||
local: LocalDbConfig;
|
||||
}
|
||||
|
||||
@@ -15,17 +18,17 @@ export type SelectedDbItem =
|
||||
| SelectedLocalUserDefinedList
|
||||
| SelectedLocalDatabase
|
||||
| SelectedRemoteSystemDefinedList
|
||||
| SelectedRemoteUserDefinedList
|
||||
| SelectedVariantAnalysisUserDefinedList
|
||||
| SelectedRemoteOwner
|
||||
| SelectedRemoteRepository;
|
||||
|
||||
export enum SelectedDbItemKind {
|
||||
LocalUserDefinedList = "localUserDefinedList",
|
||||
LocalDatabase = "localDatabase",
|
||||
RemoteSystemDefinedList = "remoteSystemDefinedList",
|
||||
RemoteUserDefinedList = "remoteUserDefinedList",
|
||||
RemoteOwner = "remoteOwner",
|
||||
RemoteRepository = "remoteRepository",
|
||||
VariantAnalysisSystemDefinedList = "variantAnalysisSystemDefinedList",
|
||||
VariantAnalysisUserDefinedList = "variantAnalysisUserDefinedList",
|
||||
VariantAnalysisOwner = "variantAnalysisOwner",
|
||||
VariantAnalysisRepository = "variantAnalysisRepository",
|
||||
}
|
||||
|
||||
export interface SelectedLocalUserDefinedList {
|
||||
@@ -40,22 +43,22 @@ export interface SelectedLocalDatabase {
|
||||
}
|
||||
|
||||
export interface SelectedRemoteSystemDefinedList {
|
||||
kind: SelectedDbItemKind.RemoteSystemDefinedList;
|
||||
kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export interface SelectedRemoteUserDefinedList {
|
||||
kind: SelectedDbItemKind.RemoteUserDefinedList;
|
||||
export interface SelectedVariantAnalysisUserDefinedList {
|
||||
kind: SelectedDbItemKind.VariantAnalysisUserDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export interface SelectedRemoteOwner {
|
||||
kind: SelectedDbItemKind.RemoteOwner;
|
||||
kind: SelectedDbItemKind.VariantAnalysisOwner;
|
||||
ownerName: string;
|
||||
}
|
||||
|
||||
export interface SelectedRemoteRepository {
|
||||
kind: SelectedDbItemKind.RemoteRepository;
|
||||
kind: SelectedDbItemKind.VariantAnalysisRepository;
|
||||
repositoryName: string;
|
||||
listName?: string;
|
||||
}
|
||||
@@ -88,49 +91,19 @@ export interface LocalDatabase {
|
||||
storagePath: string;
|
||||
}
|
||||
|
||||
export type ExpandedDbItem =
|
||||
| RootLocalExpandedDbItem
|
||||
| LocalUserDefinedListExpandedDbItem
|
||||
| RootRemoteExpandedDbItem
|
||||
| RemoteUserDefinedListExpandedDbItem;
|
||||
|
||||
export enum ExpandedDbItemKind {
|
||||
RootLocal = "rootLocal",
|
||||
LocalUserDefinedList = "localUserDefinedList",
|
||||
RootRemote = "rootRemote",
|
||||
RemoteUserDefinedList = "remoteUserDefinedList",
|
||||
}
|
||||
|
||||
export interface RootLocalExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.RootLocal;
|
||||
}
|
||||
|
||||
export interface LocalUserDefinedListExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.LocalUserDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export interface RootRemoteExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.RootRemote;
|
||||
}
|
||||
|
||||
export interface RemoteUserDefinedListExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.RemoteUserDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export function cloneDbConfig(config: DbConfig): DbConfig {
|
||||
return {
|
||||
version: config.version,
|
||||
databases: {
|
||||
remote: {
|
||||
repositoryLists: config.databases.remote.repositoryLists.map(
|
||||
variantAnalysis: {
|
||||
repositoryLists: config.databases.variantAnalysis.repositoryLists.map(
|
||||
(list) => ({
|
||||
name: list.name,
|
||||
repositories: [...list.repositories],
|
||||
}),
|
||||
),
|
||||
owners: [...config.databases.remote.owners],
|
||||
repositories: [...config.databases.remote.repositories],
|
||||
owners: [...config.databases.variantAnalysis.owners],
|
||||
repositories: [...config.databases.variantAnalysis.repositories],
|
||||
},
|
||||
local: {
|
||||
lists: config.databases.local.lists.map((list) => ({
|
||||
@@ -140,13 +113,255 @@ export function cloneDbConfig(config: DbConfig): DbConfig {
|
||||
databases: config.databases.local.databases.map((db) => ({ ...db })),
|
||||
},
|
||||
},
|
||||
expanded: config.expanded.map(cloneDbConfigExpandedItem),
|
||||
selected: config.selected
|
||||
? cloneDbConfigSelectedItem(config.selected)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function renameLocalList(
|
||||
originalConfig: DbConfig,
|
||||
currentListName: string,
|
||||
newListName: string,
|
||||
): DbConfig {
|
||||
const config = cloneDbConfig(originalConfig);
|
||||
|
||||
const list = getLocalList(config, currentListName);
|
||||
list.name = newListName;
|
||||
|
||||
if (
|
||||
config.selected?.kind === SelectedDbItemKind.LocalUserDefinedList ||
|
||||
config.selected?.kind === SelectedDbItemKind.LocalDatabase
|
||||
) {
|
||||
if (config.selected.listName === currentListName) {
|
||||
config.selected.listName = newListName;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function renameRemoteList(
|
||||
originalConfig: DbConfig,
|
||||
currentListName: string,
|
||||
newListName: string,
|
||||
): DbConfig {
|
||||
const config = cloneDbConfig(originalConfig);
|
||||
|
||||
const list = getRemoteList(config, currentListName);
|
||||
list.name = newListName;
|
||||
|
||||
if (
|
||||
config.selected?.kind ===
|
||||
SelectedDbItemKind.VariantAnalysisUserDefinedList ||
|
||||
config.selected?.kind === SelectedDbItemKind.VariantAnalysisRepository
|
||||
) {
|
||||
if (config.selected.listName === currentListName) {
|
||||
config.selected.listName = newListName;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function renameLocalDb(
|
||||
originalConfig: DbConfig,
|
||||
currentDbName: string,
|
||||
newDbName: string,
|
||||
parentListName?: string,
|
||||
): DbConfig {
|
||||
const config = cloneDbConfig(originalConfig);
|
||||
|
||||
if (parentListName) {
|
||||
const list = getLocalList(config, parentListName);
|
||||
const dbIndex = list.databases.findIndex((db) => db.name === currentDbName);
|
||||
if (dbIndex === -1) {
|
||||
throw Error(
|
||||
`Cannot find database '${currentDbName}' in list '${parentListName}'`,
|
||||
);
|
||||
}
|
||||
list.databases[dbIndex].name = newDbName;
|
||||
} else {
|
||||
const dbIndex = config.databases.local.databases.findIndex(
|
||||
(db) => db.name === currentDbName,
|
||||
);
|
||||
if (dbIndex === -1) {
|
||||
throw Error(`Cannot find database '${currentDbName}' in local databases`);
|
||||
}
|
||||
config.databases.local.databases[dbIndex].name = newDbName;
|
||||
}
|
||||
|
||||
if (
|
||||
config.selected?.kind === SelectedDbItemKind.LocalDatabase &&
|
||||
config.selected.databaseName === currentDbName
|
||||
) {
|
||||
config.selected.databaseName = newDbName;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function removeLocalList(
|
||||
originalConfig: DbConfig,
|
||||
listName: string,
|
||||
): DbConfig {
|
||||
const config = cloneDbConfig(originalConfig);
|
||||
|
||||
config.databases.local.lists = config.databases.local.lists.filter(
|
||||
(list) => list.name !== listName,
|
||||
);
|
||||
|
||||
if (config.selected?.kind === SelectedDbItemKind.LocalUserDefinedList) {
|
||||
config.selected = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
config.selected?.kind === SelectedDbItemKind.LocalDatabase &&
|
||||
config.selected?.listName === listName
|
||||
) {
|
||||
config.selected = undefined;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function removeRemoteList(
|
||||
originalConfig: DbConfig,
|
||||
listName: string,
|
||||
): DbConfig {
|
||||
const config = cloneDbConfig(originalConfig);
|
||||
|
||||
config.databases.variantAnalysis.repositoryLists =
|
||||
config.databases.variantAnalysis.repositoryLists.filter(
|
||||
(list) => list.name !== listName,
|
||||
);
|
||||
|
||||
if (
|
||||
config.selected?.kind === SelectedDbItemKind.VariantAnalysisUserDefinedList
|
||||
) {
|
||||
config.selected = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
config.selected?.kind === SelectedDbItemKind.VariantAnalysisRepository &&
|
||||
config.selected?.listName === listName
|
||||
) {
|
||||
config.selected = undefined;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function removeLocalDb(
|
||||
originalConfig: DbConfig,
|
||||
databaseName: string,
|
||||
parentListName?: string,
|
||||
): DbConfig {
|
||||
const config = cloneDbConfig(originalConfig);
|
||||
|
||||
if (parentListName) {
|
||||
const parentList = getLocalList(config, parentListName);
|
||||
parentList.databases = parentList.databases.filter(
|
||||
(db) => db.name !== databaseName,
|
||||
);
|
||||
} else {
|
||||
config.databases.local.databases = config.databases.local.databases.filter(
|
||||
(db) => db.name !== databaseName,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
config.selected?.kind === SelectedDbItemKind.LocalDatabase &&
|
||||
config.selected?.databaseName === databaseName &&
|
||||
config.selected?.listName === parentListName
|
||||
) {
|
||||
config.selected = undefined;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function removeRemoteRepo(
|
||||
originalConfig: DbConfig,
|
||||
repoFullName: string,
|
||||
parentListName?: string,
|
||||
): DbConfig {
|
||||
const config = cloneDbConfig(originalConfig);
|
||||
|
||||
if (parentListName) {
|
||||
const parentList = getRemoteList(config, parentListName);
|
||||
parentList.repositories = parentList.repositories.filter(
|
||||
(r) => r !== repoFullName,
|
||||
);
|
||||
} else {
|
||||
config.databases.variantAnalysis.repositories =
|
||||
config.databases.variantAnalysis.repositories.filter(
|
||||
(r) => r !== repoFullName,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
config.selected?.kind === SelectedDbItemKind.VariantAnalysisRepository &&
|
||||
config.selected?.repositoryName === repoFullName &&
|
||||
config.selected?.listName === parentListName
|
||||
) {
|
||||
config.selected = undefined;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function removeRemoteOwner(
|
||||
originalConfig: DbConfig,
|
||||
ownerName: string,
|
||||
): DbConfig {
|
||||
const config = cloneDbConfig(originalConfig);
|
||||
|
||||
config.databases.variantAnalysis.owners =
|
||||
config.databases.variantAnalysis.owners.filter((o) => o !== ownerName);
|
||||
|
||||
if (
|
||||
config.selected?.kind === SelectedDbItemKind.VariantAnalysisOwner &&
|
||||
config.selected?.ownerName === ownerName
|
||||
) {
|
||||
config.selected = undefined;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes local db config from a db config object, if one is set.
|
||||
* We do this because we don't want to expose this feature to users
|
||||
* yet (since it's only partially implemented), but we also don't want
|
||||
* to remove all the code we've already implemented.
|
||||
* @param config The config object to change.
|
||||
* @returns Any removed local db config.
|
||||
*/
|
||||
export function clearLocalDbConfig(
|
||||
config: DbConfig,
|
||||
): LocalDbConfig | undefined {
|
||||
let localDbs = undefined;
|
||||
|
||||
if (config && config.databases && config.databases.local) {
|
||||
localDbs = config.databases.local;
|
||||
delete (config.databases as any).local;
|
||||
}
|
||||
|
||||
return localDbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the local db config, if the config object contains
|
||||
* database configuration.
|
||||
* @param config The config object to change.
|
||||
*/
|
||||
export function initializeLocalDbConfig(config: DbConfig): void {
|
||||
if (config.databases) {
|
||||
config.databases.local = { lists: [], databases: [] };
|
||||
}
|
||||
}
|
||||
|
||||
function cloneDbConfigSelectedItem(selected: SelectedDbItem): SelectedDbItem {
|
||||
switch (selected.kind) {
|
||||
case SelectedDbItemKind.LocalUserDefinedList:
|
||||
@@ -160,40 +375,51 @@ function cloneDbConfigSelectedItem(selected: SelectedDbItem): SelectedDbItem {
|
||||
databaseName: selected.databaseName,
|
||||
listName: selected.listName,
|
||||
};
|
||||
case SelectedDbItemKind.RemoteSystemDefinedList:
|
||||
case SelectedDbItemKind.VariantAnalysisSystemDefinedList:
|
||||
return {
|
||||
kind: SelectedDbItemKind.RemoteSystemDefinedList,
|
||||
kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList,
|
||||
listName: selected.listName,
|
||||
};
|
||||
case SelectedDbItemKind.RemoteUserDefinedList:
|
||||
case SelectedDbItemKind.VariantAnalysisUserDefinedList:
|
||||
return {
|
||||
kind: SelectedDbItemKind.RemoteUserDefinedList,
|
||||
kind: SelectedDbItemKind.VariantAnalysisUserDefinedList,
|
||||
listName: selected.listName,
|
||||
};
|
||||
case SelectedDbItemKind.RemoteOwner:
|
||||
case SelectedDbItemKind.VariantAnalysisOwner:
|
||||
return {
|
||||
kind: SelectedDbItemKind.RemoteOwner,
|
||||
kind: SelectedDbItemKind.VariantAnalysisOwner,
|
||||
ownerName: selected.ownerName,
|
||||
};
|
||||
case SelectedDbItemKind.RemoteRepository:
|
||||
case SelectedDbItemKind.VariantAnalysisRepository:
|
||||
return {
|
||||
kind: SelectedDbItemKind.RemoteRepository,
|
||||
kind: SelectedDbItemKind.VariantAnalysisRepository,
|
||||
repositoryName: selected.repositoryName,
|
||||
listName: selected.listName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function cloneDbConfigExpandedItem(item: ExpandedDbItem): ExpandedDbItem {
|
||||
switch (item.kind) {
|
||||
case ExpandedDbItemKind.RootLocal:
|
||||
case ExpandedDbItemKind.RootRemote:
|
||||
return { kind: item.kind };
|
||||
case ExpandedDbItemKind.LocalUserDefinedList:
|
||||
case ExpandedDbItemKind.RemoteUserDefinedList:
|
||||
return {
|
||||
kind: item.kind,
|
||||
listName: item.listName,
|
||||
};
|
||||
function getLocalList(config: DbConfig, listName: string): LocalList {
|
||||
const list = config.databases.local.lists.find((l) => l.name === listName);
|
||||
|
||||
if (!list) {
|
||||
throw Error(`Cannot find local list '${listName}'`);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
function getRemoteList(
|
||||
config: DbConfig,
|
||||
listName: string,
|
||||
): RemoteRepositoryList {
|
||||
const list = config.databases.variantAnalysis.repositoryLists.find(
|
||||
(l) => l.name === listName,
|
||||
);
|
||||
|
||||
if (!list) {
|
||||
throw Error(`Cannot find variant analysis list '${listName}'`);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,37 @@
|
||||
import { ExpandedDbItem, ExpandedDbItemKind } from "./config/db-config";
|
||||
import { DbItem, DbItemKind } from "./db-item";
|
||||
import { DbItem, DbItemKind, flattenDbItems } from "./db-item";
|
||||
|
||||
export function calculateNewExpandedState(
|
||||
export type ExpandedDbItem =
|
||||
| RootLocalExpandedDbItem
|
||||
| LocalUserDefinedListExpandedDbItem
|
||||
| RootRemoteExpandedDbItem
|
||||
| RemoteUserDefinedListExpandedDbItem;
|
||||
|
||||
export enum ExpandedDbItemKind {
|
||||
RootLocal = "rootLocal",
|
||||
LocalUserDefinedList = "localUserDefinedList",
|
||||
RootRemote = "rootRemote",
|
||||
RemoteUserDefinedList = "remoteUserDefinedList",
|
||||
}
|
||||
|
||||
export interface RootLocalExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.RootLocal;
|
||||
}
|
||||
|
||||
export interface LocalUserDefinedListExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.LocalUserDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export interface RootRemoteExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.RootRemote;
|
||||
}
|
||||
|
||||
export interface RemoteUserDefinedListExpandedDbItem {
|
||||
kind: ExpandedDbItemKind.RemoteUserDefinedList;
|
||||
listName: string;
|
||||
}
|
||||
|
||||
export function updateExpandedItem(
|
||||
currentExpandedItems: ExpandedDbItem[],
|
||||
dbItem: DbItem,
|
||||
itemExpanded: boolean,
|
||||
@@ -20,6 +50,34 @@ export function calculateNewExpandedState(
|
||||
}
|
||||
}
|
||||
|
||||
export function replaceExpandedItem(
|
||||
currentExpandedItems: ExpandedDbItem[],
|
||||
currentDbItem: DbItem,
|
||||
newDbItem: DbItem,
|
||||
): ExpandedDbItem[] {
|
||||
const newExpandedItems: ExpandedDbItem[] = [];
|
||||
|
||||
for (const item of currentExpandedItems) {
|
||||
if (isDbItemEqualToExpandedDbItem(currentDbItem, item)) {
|
||||
newExpandedItems.push(mapDbItemToExpandedDbItem(newDbItem));
|
||||
} else {
|
||||
newExpandedItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return newExpandedItems;
|
||||
}
|
||||
|
||||
export function cleanNonExistentExpandedItems(
|
||||
currentExpandedItems: ExpandedDbItem[],
|
||||
dbItems: DbItem[],
|
||||
): ExpandedDbItem[] {
|
||||
const flattenedDbItems = flattenDbItems(dbItems);
|
||||
return currentExpandedItems.filter((i) =>
|
||||
flattenedDbItems.some((dbItem) => isDbItemEqualToExpandedDbItem(dbItem, i)),
|
||||
);
|
||||
}
|
||||
|
||||
function mapDbItemToExpandedDbItem(dbItem: DbItem): ExpandedDbItem {
|
||||
switch (dbItem.kind) {
|
||||
case DbItemKind.RootLocal:
|
||||
@@ -60,7 +118,10 @@ function isDbItemEqualToExpandedDbItem(
|
||||
expandedDbItem.kind === ExpandedDbItemKind.RemoteUserDefinedList &&
|
||||
expandedDbItem.listName === dbItem.listName
|
||||
);
|
||||
default:
|
||||
throw Error(`Unknown db item kind ${dbItem.kind}`);
|
||||
case DbItemKind.LocalDatabase:
|
||||
case DbItemKind.RemoteSystemDefinedList:
|
||||
case DbItemKind.RemoteOwner:
|
||||
case DbItemKind.RemoteRepo:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
19
extensions/ql-vscode/src/databases/db-item-naming.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { DbItem, DbItemKind } from "./db-item";
|
||||
|
||||
export function getDbItemName(dbItem: DbItem): string | undefined {
|
||||
switch (dbItem.kind) {
|
||||
case DbItemKind.RootLocal:
|
||||
case DbItemKind.RootRemote:
|
||||
return undefined;
|
||||
case DbItemKind.LocalList:
|
||||
case DbItemKind.RemoteUserDefinedList:
|
||||
case DbItemKind.RemoteSystemDefinedList:
|
||||
return dbItem.listName;
|
||||
case DbItemKind.RemoteOwner:
|
||||
return dbItem.ownerName;
|
||||
case DbItemKind.LocalDatabase:
|
||||
return dbItem.databaseName;
|
||||
case DbItemKind.RemoteRepo:
|
||||
return dbItem.repoFullName;
|
||||
}
|
||||
}
|
||||
@@ -61,34 +61,34 @@ export function mapDbItemToSelectedDbItem(
|
||||
|
||||
case DbItemKind.RemoteUserDefinedList:
|
||||
return {
|
||||
kind: SelectedDbItemKind.RemoteUserDefinedList,
|
||||
kind: SelectedDbItemKind.VariantAnalysisUserDefinedList,
|
||||
listName: dbItem.listName,
|
||||
};
|
||||
|
||||
case DbItemKind.RemoteSystemDefinedList:
|
||||
return {
|
||||
kind: SelectedDbItemKind.RemoteSystemDefinedList,
|
||||
kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList,
|
||||
listName: dbItem.listName,
|
||||
};
|
||||
|
||||
case DbItemKind.RemoteOwner:
|
||||
return {
|
||||
kind: SelectedDbItemKind.RemoteOwner,
|
||||
kind: SelectedDbItemKind.VariantAnalysisOwner,
|
||||
ownerName: dbItem.ownerName,
|
||||
};
|
||||
|
||||
case DbItemKind.LocalDatabase:
|
||||
return {
|
||||
kind: SelectedDbItemKind.LocalDatabase,
|
||||
listName: dbItem?.parentListName,
|
||||
databaseName: dbItem.databaseName,
|
||||
listName: dbItem?.parentListName,
|
||||
};
|
||||
|
||||
case DbItemKind.RemoteRepo:
|
||||
return {
|
||||
kind: SelectedDbItemKind.RemoteRepository,
|
||||
listName: dbItem?.parentListName,
|
||||
kind: SelectedDbItemKind.VariantAnalysisRepository,
|
||||
repositoryName: dbItem.repoFullName,
|
||||
listName: dbItem?.parentListName,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,25 @@ export enum DbItemKind {
|
||||
RemoteRepo = "RemoteRepo",
|
||||
}
|
||||
|
||||
export const remoteDbKinds = [
|
||||
DbItemKind.RootRemote,
|
||||
DbItemKind.RemoteSystemDefinedList,
|
||||
DbItemKind.RemoteUserDefinedList,
|
||||
DbItemKind.RemoteOwner,
|
||||
DbItemKind.RemoteRepo,
|
||||
];
|
||||
|
||||
export const localDbKinds = [
|
||||
DbItemKind.RootLocal,
|
||||
DbItemKind.LocalList,
|
||||
DbItemKind.LocalDatabase,
|
||||
];
|
||||
|
||||
export enum DbListKind {
|
||||
Local = "Local",
|
||||
Remote = "Remote",
|
||||
}
|
||||
|
||||
export interface RootLocalDbItem {
|
||||
kind: DbItemKind.RootLocal;
|
||||
expanded: boolean;
|
||||
@@ -130,3 +149,32 @@ const SelectableDbItemKinds = [
|
||||
DbItemKind.RemoteOwner,
|
||||
DbItemKind.RemoteRepo,
|
||||
];
|
||||
|
||||
export function flattenDbItems(dbItems: DbItem[]): DbItem[] {
|
||||
const allItems: DbItem[] = [];
|
||||
|
||||
for (const dbItem of dbItems) {
|
||||
allItems.push(dbItem);
|
||||
switch (dbItem.kind) {
|
||||
case DbItemKind.RootLocal:
|
||||
allItems.push(...flattenDbItems(dbItem.children));
|
||||
break;
|
||||
case DbItemKind.LocalList:
|
||||
allItems.push(...flattenDbItems(dbItem.databases));
|
||||
break;
|
||||
case DbItemKind.RootRemote:
|
||||
allItems.push(...flattenDbItems(dbItem.children));
|
||||
break;
|
||||
case DbItemKind.RemoteUserDefinedList:
|
||||
allItems.push(...dbItem.repos);
|
||||
break;
|
||||
case DbItemKind.LocalDatabase:
|
||||
case DbItemKind.RemoteSystemDefinedList:
|
||||
case DbItemKind.RemoteOwner:
|
||||
case DbItemKind.RemoteRepo:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return allItems;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,36 @@ import { App } from "../common/app";
|
||||
import { AppEvent, AppEventEmitter } from "../common/events";
|
||||
import { ValueResult } from "../common/value-result";
|
||||
import { DbConfigStore } from "./config/db-config-store";
|
||||
import { DbItem } from "./db-item";
|
||||
import { calculateNewExpandedState } from "./db-item-expansion";
|
||||
import {
|
||||
DbItem,
|
||||
DbItemKind,
|
||||
DbListKind,
|
||||
LocalDatabaseDbItem,
|
||||
LocalListDbItem,
|
||||
RemoteUserDefinedListDbItem,
|
||||
} from "./db-item";
|
||||
import {
|
||||
updateExpandedItem,
|
||||
replaceExpandedItem,
|
||||
ExpandedDbItem,
|
||||
cleanNonExistentExpandedItems,
|
||||
} from "./db-item-expansion";
|
||||
import {
|
||||
getSelectedDbItem,
|
||||
mapDbItemToSelectedDbItem,
|
||||
} from "./db-item-selection";
|
||||
import { createLocalTree, createRemoteTree } from "./db-tree-creator";
|
||||
import { createRemoteTree } from "./db-tree-creator";
|
||||
import { DbConfigValidationError } from "./db-validation-errors";
|
||||
|
||||
export class DbManager {
|
||||
public readonly onDbItemsChanged: AppEvent<void>;
|
||||
public static readonly DB_EXPANDED_STATE_KEY = "db_expanded";
|
||||
private readonly onDbItemsChangesEventEmitter: AppEventEmitter<void>;
|
||||
|
||||
constructor(app: App, private readonly dbConfigStore: DbConfigStore) {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly dbConfigStore: DbConfigStore,
|
||||
) {
|
||||
this.onDbItemsChangesEventEmitter = app.createEventEmitter<void>();
|
||||
this.onDbItemsChanged = this.onDbItemsChangesEventEmitter.event;
|
||||
|
||||
@@ -24,25 +41,25 @@ export class DbManager {
|
||||
}
|
||||
|
||||
public getSelectedDbItem(): DbItem | undefined {
|
||||
const dbItems = this.getDbItems();
|
||||
const dbItemsResult = this.getDbItems();
|
||||
|
||||
if (dbItems.isFailure) {
|
||||
if (dbItemsResult.errors.length > 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getSelectedDbItem(dbItems.value);
|
||||
return getSelectedDbItem(dbItemsResult.value);
|
||||
}
|
||||
|
||||
public getDbItems(): ValueResult<DbItem[]> {
|
||||
public getDbItems(): ValueResult<DbItem[], DbConfigValidationError> {
|
||||
const configResult = this.dbConfigStore.getConfig();
|
||||
if (configResult.isFailure) {
|
||||
return ValueResult.fail(configResult.errors);
|
||||
}
|
||||
|
||||
return ValueResult.ok([
|
||||
createRemoteTree(configResult.value),
|
||||
createLocalTree(configResult.value),
|
||||
]);
|
||||
const expandedItems = this.getExpandedItems();
|
||||
|
||||
const remoteTree = createRemoteTree(configResult.value, expandedItems);
|
||||
return ValueResult.ok(remoteTree.children);
|
||||
}
|
||||
|
||||
public getConfigPath(): string {
|
||||
@@ -56,25 +73,155 @@ export class DbManager {
|
||||
}
|
||||
}
|
||||
|
||||
public async updateDbItemExpandedState(
|
||||
public async removeDbItem(dbItem: DbItem): Promise<void> {
|
||||
await this.dbConfigStore.removeDbItem(dbItem);
|
||||
|
||||
await this.removeDbItemFromExpandedState(dbItem);
|
||||
}
|
||||
|
||||
public async removeDbItemFromExpandedState(dbItem: DbItem): Promise<void> {
|
||||
// When collapsing or expanding a list we clean up the expanded state and remove
|
||||
// all items that don't exist anymore.
|
||||
|
||||
await this.updateDbItemExpandedState(dbItem, false);
|
||||
}
|
||||
|
||||
public async addDbItemToExpandedState(dbItem: DbItem): Promise<void> {
|
||||
// When collapsing or expanding a list we clean up the expanded state and remove
|
||||
// all items that don't exist anymore.
|
||||
|
||||
await this.updateDbItemExpandedState(dbItem, true);
|
||||
}
|
||||
|
||||
public async addNewRemoteRepo(
|
||||
nwo: string,
|
||||
parentList?: string,
|
||||
): Promise<void> {
|
||||
await this.dbConfigStore.addRemoteRepo(nwo, parentList);
|
||||
}
|
||||
|
||||
public async addNewRemoteOwner(owner: string): Promise<void> {
|
||||
await this.dbConfigStore.addRemoteOwner(owner);
|
||||
}
|
||||
|
||||
public async addNewList(
|
||||
listKind: DbListKind,
|
||||
listName: string,
|
||||
): Promise<void> {
|
||||
switch (listKind) {
|
||||
case DbListKind.Local:
|
||||
await this.dbConfigStore.addLocalList(listName);
|
||||
break;
|
||||
case DbListKind.Remote:
|
||||
await this.dbConfigStore.addRemoteList(listName);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Unknown list kind '${listKind}'`);
|
||||
}
|
||||
}
|
||||
|
||||
public async renameList(
|
||||
currentDbItem: LocalListDbItem | RemoteUserDefinedListDbItem,
|
||||
newName: string,
|
||||
): Promise<void> {
|
||||
if (currentDbItem.kind === DbItemKind.LocalList) {
|
||||
await this.dbConfigStore.renameLocalList(currentDbItem, newName);
|
||||
} else if (currentDbItem.kind === DbItemKind.RemoteUserDefinedList) {
|
||||
await this.dbConfigStore.renameRemoteList(currentDbItem, newName);
|
||||
}
|
||||
|
||||
const newDbItem = { ...currentDbItem, listName: newName };
|
||||
const newExpandedItems = replaceExpandedItem(
|
||||
this.getExpandedItems(),
|
||||
currentDbItem,
|
||||
newDbItem,
|
||||
);
|
||||
|
||||
await this.setExpandedItems(newExpandedItems);
|
||||
}
|
||||
|
||||
public async renameLocalDb(
|
||||
currentDbItem: LocalDatabaseDbItem,
|
||||
newName: string,
|
||||
): Promise<void> {
|
||||
await this.dbConfigStore.renameLocalDb(
|
||||
currentDbItem,
|
||||
newName,
|
||||
currentDbItem.parentListName,
|
||||
);
|
||||
}
|
||||
|
||||
public doesListExist(listKind: DbListKind, listName: string): boolean {
|
||||
switch (listKind) {
|
||||
case DbListKind.Local:
|
||||
return this.dbConfigStore.doesLocalListExist(listName);
|
||||
case DbListKind.Remote:
|
||||
return this.dbConfigStore.doesRemoteListExist(listName);
|
||||
default:
|
||||
throw Error(`Unknown list kind '${listKind}'`);
|
||||
}
|
||||
}
|
||||
|
||||
public doesRemoteOwnerExist(owner: string): boolean {
|
||||
return this.dbConfigStore.doesRemoteOwnerExist(owner);
|
||||
}
|
||||
|
||||
public doesRemoteRepoExist(nwo: string, listName?: string): boolean {
|
||||
return this.dbConfigStore.doesRemoteDbExist(nwo, listName);
|
||||
}
|
||||
|
||||
public doesLocalDbExist(dbName: string, listName?: string): boolean {
|
||||
return this.dbConfigStore.doesLocalDbExist(dbName, listName);
|
||||
}
|
||||
|
||||
private getExpandedItems(): ExpandedDbItem[] {
|
||||
const items = this.app.workspaceState.get<ExpandedDbItem[]>(
|
||||
DbManager.DB_EXPANDED_STATE_KEY,
|
||||
);
|
||||
|
||||
return items || [];
|
||||
}
|
||||
|
||||
private async setExpandedItems(items: ExpandedDbItem[]): Promise<void> {
|
||||
await this.app.workspaceState.update(
|
||||
DbManager.DB_EXPANDED_STATE_KEY,
|
||||
items,
|
||||
);
|
||||
}
|
||||
|
||||
private async updateExpandedItems(items: ExpandedDbItem[]): Promise<void> {
|
||||
let itemsToStore;
|
||||
|
||||
const dbItemsResult = this.getDbItems();
|
||||
|
||||
if (dbItemsResult.isFailure) {
|
||||
// Log an error but don't throw an exception since if the db items are failing
|
||||
// to be read, then there is a bigger problem than the expanded state.
|
||||
void this.app.logger.log(
|
||||
`Could not read db items when calculating expanded state: ${JSON.stringify(
|
||||
dbItemsResult.errors,
|
||||
)}`,
|
||||
);
|
||||
itemsToStore = items;
|
||||
} else {
|
||||
itemsToStore = cleanNonExistentExpandedItems(items, dbItemsResult.value);
|
||||
}
|
||||
|
||||
await this.setExpandedItems(itemsToStore);
|
||||
}
|
||||
|
||||
private async updateDbItemExpandedState(
|
||||
dbItem: DbItem,
|
||||
itemExpanded: boolean,
|
||||
): Promise<void> {
|
||||
const configResult = this.dbConfigStore.getConfig();
|
||||
if (configResult.isFailure) {
|
||||
throw Error("Cannot update expanded state if config is not loaded");
|
||||
}
|
||||
const currentExpandedItems = this.getExpandedItems();
|
||||
|
||||
const newExpandedItems = calculateNewExpandedState(
|
||||
configResult.value.expanded,
|
||||
const newExpandedItems = updateExpandedItem(
|
||||
currentExpandedItems,
|
||||
dbItem,
|
||||
itemExpanded,
|
||||
);
|
||||
|
||||
await this.dbConfigStore.updateExpandedState(newExpandedItems);
|
||||
}
|
||||
|
||||
public async addNewRemoteList(listName: string): Promise<void> {
|
||||
await this.dbConfigStore.addRemoteList(listName);
|
||||
await this.updateExpandedItems(newExpandedItems);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { window } from "vscode";
|
||||
import { App, AppMode } from "../common/app";
|
||||
import { App } from "../common/app";
|
||||
import { extLogger } from "../common";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { DbConfigStore } from "./config/db-config-store";
|
||||
import { DbManager } from "./db-manager";
|
||||
import { DbPanel } from "./ui/db-panel";
|
||||
import { DbSelectionDecorationProvider } from "./ui/db-selection-decoration-provider";
|
||||
import { isCanary, isNewQueryRunExperienceEnabled } from "../config";
|
||||
import { DatabasePanelCommands } from "../common/commands";
|
||||
|
||||
export class DbModule extends DisposableObject {
|
||||
public readonly dbManager: DbManager;
|
||||
private readonly dbConfigStore: DbConfigStore;
|
||||
private dbPanel: DbPanel | undefined;
|
||||
|
||||
private constructor(app: App) {
|
||||
super();
|
||||
@@ -19,32 +20,32 @@ export class DbModule extends DisposableObject {
|
||||
this.dbManager = new DbManager(app, this.dbConfigStore);
|
||||
}
|
||||
|
||||
public static async initialize(app: App): Promise<DbModule | undefined> {
|
||||
if (
|
||||
isCanary() &&
|
||||
isNewQueryRunExperienceEnabled() &&
|
||||
app.mode === AppMode.Development
|
||||
) {
|
||||
const dbModule = new DbModule(app);
|
||||
app.subscriptions.push(dbModule);
|
||||
public static async initialize(app: App): Promise<DbModule> {
|
||||
const dbModule = new DbModule(app);
|
||||
app.subscriptions.push(dbModule);
|
||||
|
||||
await dbModule.initialize();
|
||||
|
||||
return dbModule;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
await dbModule.initialize(app);
|
||||
return dbModule;
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
public getCommands(): DatabasePanelCommands {
|
||||
if (!this.dbPanel) {
|
||||
throw new Error("Database panel not initialized");
|
||||
}
|
||||
|
||||
return {
|
||||
...this.dbPanel.getCommands(),
|
||||
};
|
||||
}
|
||||
|
||||
private async initialize(app: App): Promise<void> {
|
||||
void extLogger.log("Initializing database module");
|
||||
|
||||
await this.dbConfigStore.initialize();
|
||||
|
||||
const dbPanel = new DbPanel(this.dbManager);
|
||||
await dbPanel.initialize();
|
||||
this.dbPanel = new DbPanel(this.dbManager, app.credentials);
|
||||
|
||||
this.push(dbPanel);
|
||||
this.push(this.dbPanel);
|
||||
this.push(this.dbConfigStore);
|
||||
|
||||
const dbSelectionDecorationProvider = new DbSelectionDecorationProvider();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
DbConfig,
|
||||
ExpandedDbItemKind,
|
||||
LocalDatabase,
|
||||
LocalList,
|
||||
RemoteRepositoryList,
|
||||
@@ -17,27 +16,32 @@ import {
|
||||
RootLocalDbItem,
|
||||
RootRemoteDbItem,
|
||||
} from "./db-item";
|
||||
import { ExpandedDbItem, ExpandedDbItemKind } from "./db-item-expansion";
|
||||
|
||||
export function createRemoteTree(dbConfig: DbConfig): RootRemoteDbItem {
|
||||
export function createRemoteTree(
|
||||
dbConfig: DbConfig,
|
||||
expandedItems: ExpandedDbItem[],
|
||||
): RootRemoteDbItem {
|
||||
const systemDefinedLists = [
|
||||
createSystemDefinedList(10, dbConfig),
|
||||
createSystemDefinedList(100, dbConfig),
|
||||
createSystemDefinedList(1000, dbConfig),
|
||||
];
|
||||
|
||||
const userDefinedRepoLists = dbConfig.databases.remote.repositoryLists.map(
|
||||
(r) => createRemoteUserDefinedList(r, dbConfig),
|
||||
);
|
||||
const owners = dbConfig.databases.remote.owners.map((o) =>
|
||||
const userDefinedRepoLists =
|
||||
dbConfig.databases.variantAnalysis.repositoryLists.map((r) =>
|
||||
createVariantAnalysisUserDefinedList(r, dbConfig, expandedItems),
|
||||
);
|
||||
const owners = dbConfig.databases.variantAnalysis.owners.map((o) =>
|
||||
createOwnerItem(o, dbConfig),
|
||||
);
|
||||
const repos = dbConfig.databases.remote.repositories.map((r) =>
|
||||
const repos = dbConfig.databases.variantAnalysis.repositories.map((r) =>
|
||||
createRepoItem(r, dbConfig),
|
||||
);
|
||||
|
||||
const expanded =
|
||||
dbConfig.expanded &&
|
||||
dbConfig.expanded.some((e) => e.kind === ExpandedDbItemKind.RootRemote);
|
||||
const expanded = expandedItems.some(
|
||||
(e) => e.kind === ExpandedDbItemKind.RootRemote,
|
||||
);
|
||||
|
||||
return {
|
||||
kind: DbItemKind.RootRemote,
|
||||
@@ -51,17 +55,20 @@ export function createRemoteTree(dbConfig: DbConfig): RootRemoteDbItem {
|
||||
};
|
||||
}
|
||||
|
||||
export function createLocalTree(dbConfig: DbConfig): RootLocalDbItem {
|
||||
export function createLocalTree(
|
||||
dbConfig: DbConfig,
|
||||
expandedItems: ExpandedDbItem[],
|
||||
): RootLocalDbItem {
|
||||
const localLists = dbConfig.databases.local.lists.map((l) =>
|
||||
createLocalList(l, dbConfig),
|
||||
createLocalList(l, dbConfig, expandedItems),
|
||||
);
|
||||
const localDbs = dbConfig.databases.local.databases.map((l) =>
|
||||
createLocalDb(l, dbConfig),
|
||||
);
|
||||
|
||||
const expanded =
|
||||
dbConfig.expanded &&
|
||||
dbConfig.expanded.some((e) => e.kind === ExpandedDbItemKind.RootLocal);
|
||||
const expanded = expandedItems.some(
|
||||
(e) => e.kind === ExpandedDbItemKind.RootLocal,
|
||||
);
|
||||
|
||||
return {
|
||||
kind: DbItemKind.RootLocal,
|
||||
@@ -78,7 +85,8 @@ function createSystemDefinedList(
|
||||
|
||||
const selected =
|
||||
dbConfig.selected &&
|
||||
dbConfig.selected.kind === SelectedDbItemKind.RemoteSystemDefinedList &&
|
||||
dbConfig.selected.kind ===
|
||||
SelectedDbItemKind.VariantAnalysisSystemDefinedList &&
|
||||
dbConfig.selected.listName === listName;
|
||||
|
||||
return {
|
||||
@@ -90,22 +98,22 @@ function createSystemDefinedList(
|
||||
};
|
||||
}
|
||||
|
||||
function createRemoteUserDefinedList(
|
||||
function createVariantAnalysisUserDefinedList(
|
||||
list: RemoteRepositoryList,
|
||||
dbConfig: DbConfig,
|
||||
expandedItems: ExpandedDbItem[],
|
||||
): RemoteUserDefinedListDbItem {
|
||||
const selected =
|
||||
dbConfig.selected &&
|
||||
dbConfig.selected.kind === SelectedDbItemKind.RemoteUserDefinedList &&
|
||||
dbConfig.selected.kind ===
|
||||
SelectedDbItemKind.VariantAnalysisUserDefinedList &&
|
||||
dbConfig.selected.listName === list.name;
|
||||
|
||||
const expanded =
|
||||
dbConfig.expanded &&
|
||||
dbConfig.expanded.some(
|
||||
(e) =>
|
||||
e.kind === ExpandedDbItemKind.RemoteUserDefinedList &&
|
||||
e.listName === list.name,
|
||||
);
|
||||
const expanded = expandedItems.some(
|
||||
(e) =>
|
||||
e.kind === ExpandedDbItemKind.RemoteUserDefinedList &&
|
||||
e.listName === list.name,
|
||||
);
|
||||
|
||||
return {
|
||||
kind: DbItemKind.RemoteUserDefinedList,
|
||||
@@ -119,7 +127,7 @@ function createRemoteUserDefinedList(
|
||||
function createOwnerItem(owner: string, dbConfig: DbConfig): RemoteOwnerDbItem {
|
||||
const selected =
|
||||
dbConfig.selected &&
|
||||
dbConfig.selected.kind === SelectedDbItemKind.RemoteOwner &&
|
||||
dbConfig.selected.kind === SelectedDbItemKind.VariantAnalysisOwner &&
|
||||
dbConfig.selected.ownerName === owner;
|
||||
|
||||
return {
|
||||
@@ -136,7 +144,7 @@ function createRepoItem(
|
||||
): RemoteRepoDbItem {
|
||||
const selected =
|
||||
dbConfig.selected &&
|
||||
dbConfig.selected.kind === SelectedDbItemKind.RemoteRepository &&
|
||||
dbConfig.selected.kind === SelectedDbItemKind.VariantAnalysisRepository &&
|
||||
dbConfig.selected.repositoryName === repo &&
|
||||
dbConfig.selected.listName === listName;
|
||||
|
||||
@@ -148,19 +156,21 @@ function createRepoItem(
|
||||
};
|
||||
}
|
||||
|
||||
function createLocalList(list: LocalList, dbConfig: DbConfig): LocalListDbItem {
|
||||
function createLocalList(
|
||||
list: LocalList,
|
||||
dbConfig: DbConfig,
|
||||
expandedItems: ExpandedDbItem[],
|
||||
): LocalListDbItem {
|
||||
const selected =
|
||||
dbConfig.selected &&
|
||||
dbConfig.selected.kind === SelectedDbItemKind.LocalUserDefinedList &&
|
||||
dbConfig.selected.listName === list.name;
|
||||
|
||||
const expanded =
|
||||
dbConfig.expanded &&
|
||||
dbConfig.expanded.some(
|
||||
(e) =>
|
||||
e.kind === ExpandedDbItemKind.LocalUserDefinedList &&
|
||||
e.listName === list.name,
|
||||
);
|
||||
const expanded = expandedItems.some(
|
||||
(e) =>
|
||||
e.kind === ExpandedDbItemKind.LocalUserDefinedList &&
|
||||
e.listName === list.name,
|
||||
);
|
||||
|
||||
return {
|
||||
kind: DbItemKind.LocalList,
|
||||
|
||||
10
extensions/ql-vscode/src/databases/db-validation-errors.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export enum DbConfigValidationErrorKind {
|
||||
InvalidJson = "InvalidJson",
|
||||
InvalidConfig = "InvalidConfig",
|
||||
DuplicateNames = "DuplicateNames",
|
||||
}
|
||||
|
||||
export interface DbConfigValidationError {
|
||||
kind: DbConfigValidationErrorKind;
|
||||
message: string;
|
||||
}
|
||||