Compare commits
694 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad98388b66 | ||
|
|
38816b9f52 | ||
|
|
1ce652629c | ||
|
|
70b8ddfa37 | ||
|
|
923a480ddd | ||
|
|
cd0d64605a | ||
|
|
fcc5f6967e | ||
|
|
74070fbc1a | ||
|
|
f8e825287c | ||
|
|
d7e9606bfa | ||
|
|
be6166497f | ||
|
|
01f6884b6c | ||
|
|
8d1480ab35 | ||
|
|
4ed6a7b95b | ||
|
|
00076a9538 | ||
|
|
3f02ff4151 | ||
|
|
9e7fefe724 | ||
|
|
0f6afac222 | ||
|
|
f7731e2e12 | ||
|
|
f4edc6e5a9 | ||
|
|
478d41648e | ||
|
|
6472ea8fde | ||
|
|
5a80d02a91 | ||
|
|
939616372c | ||
|
|
bfe78eba9a | ||
|
|
8a389f2eb7 | ||
|
|
1004f16b10 | ||
|
|
e5961f2967 | ||
|
|
25f179ca0e | ||
|
|
e7adfdc8bc | ||
|
|
281242fa3f | ||
|
|
4b832bd85c | ||
|
|
7af65ed86a | ||
|
|
116aceffc1 | ||
|
|
f6efcd5a21 | ||
|
|
bdbd123d32 | ||
|
|
f0ce52d01a | ||
|
|
31ff3577c4 | ||
|
|
98c96b09ee | ||
|
|
f4727fe7b5 | ||
|
|
e943e7fa2e | ||
|
|
6e53f28972 | ||
|
|
3b366a6f51 | ||
|
|
c906e76214 | ||
|
|
aedc063d1a | ||
|
|
ecea102292 | ||
|
|
59e20f0fcb | ||
|
|
1135f7a7f8 | ||
|
|
d6bd482c89 | ||
|
|
11e5db9aca | ||
|
|
0b4c611a5f | ||
|
|
39cbe3b373 | ||
|
|
d62ac97a7b | ||
|
|
6615c3df84 | ||
|
|
db5293a142 | ||
|
|
e93ef98a67 | ||
|
|
bd823b5745 | ||
|
|
3e716e91c8 | ||
|
|
58a06191c0 | ||
|
|
dd74372702 | ||
|
|
6fd628b49c | ||
|
|
4f11f7560b | ||
|
|
388f4299b0 | ||
|
|
b577576b00 | ||
|
|
126acc3979 | ||
|
|
c6e6b5736c | ||
|
|
5fe5f70867 | ||
|
|
c9be2ce32c | ||
|
|
bbfc5d88dd | ||
|
|
b418f479d6 | ||
|
|
f2eea4025a | ||
|
|
fdb4d42bf3 | ||
|
|
688aa304f5 | ||
|
|
65a84f940d | ||
|
|
b74306249e | ||
|
|
57faebd5b9 | ||
|
|
303e04ba64 | ||
|
|
cc2e28c886 | ||
|
|
c6e7bcd8fa | ||
|
|
a03863cd4b | ||
|
|
19d85a73e1 | ||
|
|
6030137f43 | ||
|
|
11e0f49135 | ||
|
|
b35d6cbb3a | ||
|
|
0477a9dee3 | ||
|
|
c5db597676 | ||
|
|
73b6cc475c | ||
|
|
7c233db4eb | ||
|
|
ad5ae27a0d | ||
|
|
e9c7331c29 | ||
|
|
bc4d74246a | ||
|
|
70171807c7 | ||
|
|
490b7fbe70 | ||
|
|
d1b81b3f86 | ||
|
|
7f975cc696 | ||
|
|
89ce6cfde9 | ||
|
|
3aea6874b5 | ||
|
|
ae173f24f0 | ||
|
|
ed2698ff01 | ||
|
|
7151a498c8 | ||
|
|
e582e63685 | ||
|
|
600a5de0e9 | ||
|
|
f24487833e | ||
|
|
4e967d8111 | ||
|
|
15c3805ad3 | ||
|
|
cab963cc88 | ||
|
|
c6c27f4ca3 | ||
|
|
b7ee9f9dd4 | ||
|
|
b3284d6760 | ||
|
|
e9e98403a9 | ||
|
|
74c101bb51 | ||
|
|
ca8c48418f | ||
|
|
8c72fe0f8d | ||
|
|
9cd6dafdf4 | ||
|
|
08dfd1a211 | ||
|
|
9b07be00c7 | ||
|
|
820989a8c5 | ||
|
|
9401c34509 | ||
|
|
750c0973a9 | ||
|
|
6bd8c8ee78 | ||
|
|
841efbf826 | ||
|
|
8c679ab569 | ||
|
|
0391f972ad | ||
|
|
e3a9c06e3c | ||
|
|
a76dc8f0de | ||
|
|
222d3edaff | ||
|
|
31ea6aded7 | ||
|
|
b92e98fce4 | ||
|
|
a228dba0ee | ||
|
|
5367bdea9c | ||
|
|
978e48fe2c | ||
|
|
98764a156a | ||
|
|
a85989e202 | ||
|
|
8c696e10af | ||
|
|
dc8062b784 | ||
|
|
ac5ed7b30c | ||
|
|
c5e9ef15f2 | ||
|
|
e70b083828 | ||
|
|
1b84906bce | ||
|
|
1f24cd1a7f | ||
|
|
e126dfbe36 | ||
|
|
0e2c03f572 | ||
|
|
95b082d56a | ||
|
|
56dd102db8 | ||
|
|
f5b6b4a9f2 | ||
|
|
5fc88bed32 | ||
|
|
05e5e32854 | ||
|
|
554d8854a1 | ||
|
|
c9cb7eaa38 | ||
|
|
70461d857c | ||
|
|
e1ff113701 | ||
|
|
2f66be3fcc | ||
|
|
12a3af0a75 | ||
|
|
594f422510 | ||
|
|
d5932fc1de | ||
|
|
8397f58d0b | ||
|
|
dd9ca80820 | ||
|
|
deab3e6175 | ||
|
|
a3f9518b78 | ||
|
|
79fd44f696 | ||
|
|
69ff7cb0e8 | ||
|
|
3daefcdffc | ||
|
|
a6f7ee3df8 | ||
|
|
92c4532cc2 | ||
|
|
0265befb26 | ||
|
|
aada35187f | ||
|
|
e612764c59 | ||
|
|
e834d32a29 | ||
|
|
67e2ff3d4e | ||
|
|
9285b0272d | ||
|
|
445aa81f5a | ||
|
|
2cd1a99273 | ||
|
|
b6bf683852 | ||
|
|
5bb724b5be | ||
|
|
5664844aba | ||
|
|
9644e5daee | ||
|
|
d7f01dcb29 | ||
|
|
509e273108 | ||
|
|
5c513bc821 | ||
|
|
342dbfb5a9 | ||
|
|
28aadb183b | ||
|
|
6bf73dc196 | ||
|
|
83148f6d57 | ||
|
|
988a113cb3 | ||
|
|
5136c95da7 | ||
|
|
6741f0c431 | ||
|
|
ebf06b9af6 | ||
|
|
a85a4b41f5 | ||
|
|
8690f76968 | ||
|
|
fc41360bad | ||
|
|
a2ebc69d3a | ||
|
|
f1a915e46c | ||
|
|
2d1663d046 | ||
|
|
66be432610 | ||
|
|
4ae4745a86 | ||
|
|
fef394b79d | ||
|
|
ccfba04d97 | ||
|
|
c89382ceb7 | ||
|
|
8beaa7cbc0 | ||
|
|
477c6b3819 | ||
|
|
978e00a252 | ||
|
|
5c132884a4 | ||
|
|
8e1fc2ca96 | ||
|
|
d6e4b7ef56 | ||
|
|
4e81d41086 | ||
|
|
ac5038cf50 | ||
|
|
d7e5552481 | ||
|
|
65fbb6bca8 | ||
|
|
1d275985d0 | ||
|
|
55306c9a93 | ||
|
|
ec6a725715 | ||
|
|
6b1a056ae6 | ||
|
|
75065f3148 | ||
|
|
1010239c29 | ||
|
|
2b6ec73147 | ||
|
|
97980371c8 | ||
|
|
479aa683ee | ||
|
|
1cb46d2604 | ||
|
|
be22964113 | ||
|
|
db3550a18c | ||
|
|
91a96da2ad | ||
|
|
9dc37ebf33 | ||
|
|
ff8def3e7d | ||
|
|
39c17291aa | ||
|
|
a6a7a5c5cb | ||
|
|
70ce93cbb3 | ||
|
|
abc71b5102 | ||
|
|
fed110f65f | ||
|
|
174a38a16a | ||
|
|
b4250218d5 | ||
|
|
5948008c99 | ||
|
|
070f0ca9b7 | ||
|
|
318136f5e5 | ||
|
|
a3ade9984d | ||
|
|
4429385dfd | ||
|
|
f993258a27 | ||
|
|
c5517e0aea | ||
|
|
8c0a8e07b2 | ||
|
|
a1572463a6 | ||
|
|
545902843d | ||
|
|
1a7981a22d | ||
|
|
1bc560518f | ||
|
|
281f8eeb7a | ||
|
|
f53826c09d | ||
|
|
b16aeb3887 | ||
|
|
ee73639720 | ||
|
|
9d2190a88d | ||
|
|
6d308f8688 | ||
|
|
61933c34d5 | ||
|
|
e03c2b132c | ||
|
|
9c42c6a851 | ||
|
|
283d0070f7 | ||
|
|
d3c7241854 | ||
|
|
0b9e4e33b2 | ||
|
|
b4a66a7690 | ||
|
|
ea1419add2 | ||
|
|
0534cb7514 | ||
|
|
2aacea4176 | ||
|
|
2ccd99fc5b | ||
|
|
bdc94a3b23 | ||
|
|
29d8c65b59 | ||
|
|
fb63ec7db0 | ||
|
|
b67efeeacd | ||
|
|
4b3a008a45 | ||
|
|
1186026315 | ||
|
|
2ba23ceead | ||
|
|
c065c44ff3 | ||
|
|
0d2b7916ee | ||
|
|
766f54b76e | ||
|
|
c485e39b07 | ||
|
|
d8968db1b9 | ||
|
|
40ba2c03d3 | ||
|
|
ac93074f94 | ||
|
|
1dc6111fba | ||
|
|
f9166f304b | ||
|
|
6ec727a8a2 | ||
|
|
b4da6c71f3 | ||
|
|
f43c5274cf | ||
|
|
1a135c501d | ||
|
|
0df0cca9d8 | ||
|
|
519833e7a4 | ||
|
|
d77261aaa5 | ||
|
|
24dff5981e | ||
|
|
7f730d24b0 | ||
|
|
8834111f57 | ||
|
|
3d5ef60fe0 | ||
|
|
6ab1f4c7ee | ||
|
|
77f84c6ca9 | ||
|
|
d0a12753d8 | ||
|
|
ea6e148df9 | ||
|
|
ac745a6955 | ||
|
|
d022623a9d | ||
|
|
c7a1cf7236 | ||
|
|
1b737678ba | ||
|
|
19b24f87ec | ||
|
|
38743c6180 | ||
|
|
d1a26d3893 | ||
|
|
b2bdcdbcb8 | ||
|
|
37c3b03e9a | ||
|
|
c7c79175d2 | ||
|
|
01e7b3fc0e | ||
|
|
58ea742e85 | ||
|
|
188bc53761 | ||
|
|
26e619ba71 | ||
|
|
00707fca4b | ||
|
|
51627ad971 | ||
|
|
f21ee5173c | ||
|
|
397693a2cb | ||
|
|
0648ce8ae6 | ||
|
|
183de7b6a1 | ||
|
|
b6f32ec5d7 | ||
|
|
70c2beb510 | ||
|
|
31c90a5068 | ||
|
|
2ce6fd3bb1 | ||
|
|
da5852cfbb | ||
|
|
5c83c94335 | ||
|
|
21faed0191 | ||
|
|
9d6962ede2 | ||
|
|
d6d5d27d70 | ||
|
|
5c7efe5116 | ||
|
|
1d42d4848b | ||
|
|
745544cd9c | ||
|
|
38a3778fe1 | ||
|
|
50df8cd376 | ||
|
|
034bfc230c | ||
|
|
d342b060ed | ||
|
|
45fe4aa1d4 | ||
|
|
902074f7a5 | ||
|
|
fd8f88890e | ||
|
|
db842dea7c | ||
|
|
eb59ead817 | ||
|
|
d0e81124ac | ||
|
|
8697dcc80d | ||
|
|
ad8dc1e906 | ||
|
|
ceb8b1f9c1 | ||
|
|
b77acc5468 | ||
|
|
555fa6cf78 | ||
|
|
f7de8e5d09 | ||
|
|
299280cd08 | ||
|
|
92bebef49d | ||
|
|
1ce2b365a6 | ||
|
|
47929943ba | ||
|
|
0b27912314 | ||
|
|
6a65094720 | ||
|
|
6746b07879 | ||
|
|
593c01388b | ||
|
|
dbee8dd57a | ||
|
|
426ab98623 | ||
|
|
9fd8c1c3e0 | ||
|
|
b8cbd75737 | ||
|
|
c575edec71 | ||
|
|
337e5f81ea | ||
|
|
6d1cf0887a | ||
|
|
bc7c956d6d | ||
|
|
90d72cec78 | ||
|
|
2f9c8d1bfa | ||
|
|
4b49a8f9bb | ||
|
|
9718aa5806 | ||
|
|
7444605970 | ||
|
|
cce0e146ff | ||
|
|
de24e25486 | ||
|
|
8daf3374bf | ||
|
|
b27d8a1000 | ||
|
|
1ad20fa354 | ||
|
|
b082455604 | ||
|
|
ed4b296e41 | ||
|
|
cf00f8f191 | ||
|
|
dc3e1ce096 | ||
|
|
e5ffdd0fbc | ||
|
|
2c7b67e2a4 | ||
|
|
4129962fa0 | ||
|
|
e79f732b84 | ||
|
|
49deb2e756 | ||
|
|
0be60f3e8f | ||
|
|
e294dfdf4a | ||
|
|
870874d7f3 | ||
|
|
856f97f859 | ||
|
|
cfbc44311b | ||
|
|
27a326ab3e | ||
|
|
91ab97e923 | ||
|
|
fa7ecb782e | ||
|
|
f5dbcc8cc1 | ||
|
|
c423505c04 | ||
|
|
e7eb33e0cf | ||
|
|
528f8b951b | ||
|
|
74568b86b1 | ||
|
|
039077467a | ||
|
|
ef28154b95 | ||
|
|
47cabbb331 | ||
|
|
c1a3c2f6e5 | ||
|
|
c210a7fdf4 | ||
|
|
184a98531f | ||
|
|
9a6d44b86a | ||
|
|
d0df81c524 | ||
|
|
ba8788e0a5 | ||
|
|
767c3cc35c | ||
|
|
a6d9b6bbec | ||
|
|
f139f1ebf8 | ||
|
|
d227af4c7b | ||
|
|
2b625c85e8 | ||
|
|
5ea1438036 | ||
|
|
7a7092de0d | ||
|
|
2ba39647f2 | ||
|
|
48ff62f474 | ||
|
|
82c7dd52be | ||
|
|
d0f122ad87 | ||
|
|
ef575acd59 | ||
|
|
785a5fa48a | ||
|
|
43ea7eb41d | ||
|
|
4b27d0d59c | ||
|
|
1df2e54074 | ||
|
|
b7eba71ab6 | ||
|
|
7375fce82d | ||
|
|
eac3836535 | ||
|
|
e8f750776b | ||
|
|
91a542dce6 | ||
|
|
b902bfd71d | ||
|
|
6dd6460b33 | ||
|
|
36d23e7eec | ||
|
|
b65f0490f2 | ||
|
|
a8dd368578 | ||
|
|
4d130bc2fa | ||
|
|
1b526783e2 | ||
|
|
4e4345f0c2 | ||
|
|
b2df8b6971 | ||
|
|
91ea2f089e | ||
|
|
fbaf3d1cdf | ||
|
|
7bcf62c6fe | ||
|
|
f7c0d4dc91 | ||
|
|
e81f1a1fde | ||
|
|
c14fa63321 | ||
|
|
7554c415af | ||
|
|
bbdad0fe2c | ||
|
|
b3da120d70 | ||
|
|
3689f84612 | ||
|
|
f99432153b | ||
|
|
704895ba6c | ||
|
|
f3780c6305 | ||
|
|
bb3ec207e3 | ||
|
|
e56503249d | ||
|
|
37b2e422cd | ||
|
|
4140303b5c | ||
|
|
218be87e88 | ||
|
|
c7e2d69daa | ||
|
|
ba2f44b8ae | ||
|
|
2267e9d4db | ||
|
|
6f85894a11 | ||
|
|
8cbd77cf65 | ||
|
|
641f714fa4 | ||
|
|
216f10d327 | ||
|
|
5045b2df60 | ||
|
|
857a997037 | ||
|
|
262744e6e5 | ||
|
|
4444951093 | ||
|
|
e824fda9e7 | ||
|
|
ef8bf9fd1b | ||
|
|
69dea08c47 | ||
|
|
0360bf294f | ||
|
|
996e8036c1 | ||
|
|
2e51c1a657 | ||
|
|
f1fe4a20f2 | ||
|
|
be4059864e | ||
|
|
a80098d7df | ||
|
|
f45b790591 | ||
|
|
7a1f157225 | ||
|
|
ba9b284606 | ||
|
|
1055515d59 | ||
|
|
4eee14be61 | ||
|
|
7c00768c90 | ||
|
|
9038586aab | ||
|
|
800890443e | ||
|
|
0dcc814953 | ||
|
|
64ad40e369 | ||
|
|
f736adc4f1 | ||
|
|
e89a04d442 | ||
|
|
2e6eb1c7bc | ||
|
|
fdc36ad36b | ||
|
|
3f6c1055e4 | ||
|
|
b833591d1e | ||
|
|
82f17c4e53 | ||
|
|
790503bd60 | ||
|
|
770834756a | ||
|
|
bc0506d058 | ||
|
|
14b2282015 | ||
|
|
12ea1b9598 | ||
|
|
6cfa0a93c9 | ||
|
|
ef2f9d9c90 | ||
|
|
1891763d64 | ||
|
|
0b4fc76fdb | ||
|
|
d036e8144d | ||
|
|
4f2d768e07 | ||
|
|
60d777abf1 | ||
|
|
34bd7c21a6 | ||
|
|
6cf8d0be4f | ||
|
|
b340b1a740 | ||
|
|
ce83d1df23 | ||
|
|
d63b756520 | ||
|
|
47fbf3428f | ||
|
|
889055ee19 | ||
|
|
c7f08e328d | ||
|
|
0273be9710 | ||
|
|
e94da0e765 | ||
|
|
cb15c8dbeb | ||
|
|
c7173e86a2 | ||
|
|
b3c8c25572 | ||
|
|
2bad58be71 | ||
|
|
fe4116fe05 | ||
|
|
b9b16a8beb | ||
|
|
c3b30a30e8 | ||
|
|
04e4efb26b | ||
|
|
8d1c8bc725 | ||
|
|
eb1bf8fd69 | ||
|
|
9a77748a59 | ||
|
|
d77a08fc37 | ||
|
|
cb2e5026a1 | ||
|
|
d4a4536d40 | ||
|
|
ab1966abf1 | ||
|
|
6de954143c | ||
|
|
109766d411 | ||
|
|
4bd1981a34 | ||
|
|
0415ac539c | ||
|
|
cf50a71a16 | ||
|
|
598f2eb3f2 | ||
|
|
480f90ac2f | ||
|
|
e15f15358b | ||
|
|
8516940e64 | ||
|
|
ef69a51741 | ||
|
|
9594a5e951 | ||
|
|
7b9c7f4e21 | ||
|
|
25ff95a2f3 | ||
|
|
25779b4db1 | ||
|
|
611f55b9cd | ||
|
|
b8558a67a3 | ||
|
|
3d65ecaac9 | ||
|
|
61c9503d52 | ||
|
|
01f5a65f7d | ||
|
|
612499916d | ||
|
|
a2294bb5cc | ||
|
|
0b7b6f1b13 | ||
|
|
8b2fd1a630 | ||
|
|
4bb25a7d69 | ||
|
|
83bdb54047 | ||
|
|
0d380736b3 | ||
|
|
0655e10db5 | ||
|
|
fb713f733d | ||
|
|
15cc559792 | ||
|
|
a2f3f5fbe3 | ||
|
|
54a03c8839 | ||
|
|
ce84fed6ab | ||
|
|
6ecad9db03 | ||
|
|
9543d14840 | ||
|
|
6eee7983b0 | ||
|
|
6ec0f51a54 | ||
|
|
5a29d35c98 | ||
|
|
16ebac12a1 | ||
|
|
2b6fe94cd3 | ||
|
|
bc1c08c653 | ||
|
|
66c0714606 | ||
|
|
9cb4d233d9 | ||
|
|
8fac9b1413 | ||
|
|
6c76218b21 | ||
|
|
d4a4cbb4f1 | ||
|
|
3a8beb9ab9 | ||
|
|
afdd20a957 | ||
|
|
bf5398bfc9 | ||
|
|
01085e75ba | ||
|
|
7f332e3374 | ||
|
|
39229c26d4 | ||
|
|
0f437f7a92 | ||
|
|
0df9a54d89 | ||
|
|
4f85ac11a9 | ||
|
|
118ad9d530 | ||
|
|
2a44bf78c8 | ||
|
|
cf301aa338 | ||
|
|
0e96941c89 | ||
|
|
f596f7ee74 | ||
|
|
87e42b499d | ||
|
|
8acff5ce72 | ||
|
|
47797eb7e6 | ||
|
|
0a4d28e9f0 | ||
|
|
01956072b3 | ||
|
|
49114cc143 | ||
|
|
789719a26c | ||
|
|
8d711624bb | ||
|
|
7b6b303040 | ||
|
|
86aa4ffb6f | ||
|
|
1235172147 | ||
|
|
4873d7eb12 | ||
|
|
27418898b0 | ||
|
|
3554bceac0 | ||
|
|
71d4fd7b0a | ||
|
|
151c031732 | ||
|
|
af68d99509 | ||
|
|
3f2114a7f0 | ||
|
|
4f14592dd7 | ||
|
|
acca6b34f8 | ||
|
|
f847c36fb7 | ||
|
|
1ed72c6bbe | ||
|
|
d72f3f40d2 | ||
|
|
4a46873dd3 | ||
|
|
acbd973bd2 | ||
|
|
4a321708db | ||
|
|
3f07082e55 | ||
|
|
80081af377 | ||
|
|
a42e9ebbf1 | ||
|
|
9f667ef2d2 | ||
|
|
b421a1916c | ||
|
|
ddf4407c8c | ||
|
|
aa9cb89369 | ||
|
|
cce858561f | ||
|
|
f4cc9f9fbb | ||
|
|
9c200e036a | ||
|
|
3020bf711b | ||
|
|
b1289a4598 | ||
|
|
da8d32c4bc | ||
|
|
f3a0ad2e18 | ||
|
|
331d39afcf | ||
|
|
b6ba0bbcb9 | ||
|
|
496d05c900 | ||
|
|
76534bccd7 | ||
|
|
d28cc6e3f2 | ||
|
|
a6703e11e3 | ||
|
|
cf6abc43b1 | ||
|
|
ce20c7f6a4 | ||
|
|
b8ec847253 | ||
|
|
a0c85b78c5 | ||
|
|
f0a354917e | ||
|
|
082c5e4a31 | ||
|
|
439cfcc023 | ||
|
|
297f5a9f67 | ||
|
|
e7c79f04c6 | ||
|
|
76529572f5 | ||
|
|
63d6793795 | ||
|
|
fc2e6d0432 | ||
|
|
cb707aea50 | ||
|
|
cd26f27e6e | ||
|
|
3c98f94d3e | ||
|
|
558f7f6dbf | ||
|
|
0d4dbb059b | ||
|
|
291bdbec8d | ||
|
|
98b30af0a0 | ||
|
|
992079d99f | ||
|
|
d5e7e54293 | ||
|
|
d0d12b78b2 | ||
|
|
2e06fa3f5f | ||
|
|
cab510f2dd | ||
|
|
5e6b7d846e | ||
|
|
85d7b9b2fe | ||
|
|
08721179dc | ||
|
|
918661e5ce | ||
|
|
6324a19729 | ||
|
|
bbc77c0519 | ||
|
|
54be065f3e | ||
|
|
829607f044 | ||
|
|
c3425b5146 | ||
|
|
ec192affdc | ||
|
|
66ae46f67c | ||
|
|
424e8d3145 | ||
|
|
208efb57d3 | ||
|
|
4f988de36d | ||
|
|
e66d76aca8 | ||
|
|
5d1b2926cc | ||
|
|
7b3a55e2bf | ||
|
|
560008c55e | ||
|
|
b81061a8a1 | ||
|
|
c09c26d2b3 | ||
|
|
7970b09134 | ||
|
|
0523d2a63d | ||
|
|
8aae7d30d3 | ||
|
|
a0295d62f8 | ||
|
|
4f51445609 | ||
|
|
01d24e06f3 | ||
|
|
b83ef4ed68 | ||
|
|
9dd061b2c8 | ||
|
|
feca898c85 | ||
|
|
422f0eb7e4 | ||
|
|
5c03f5b43e | ||
|
|
9ca138dc8e | ||
|
|
40b1755868 | ||
|
|
d0488ddda9 | ||
|
|
5d42cbc1c8 | ||
|
|
636f8f1b5f | ||
|
|
c0db180200 | ||
|
|
383a1215b4 | ||
|
|
0994c3e95f | ||
|
|
fe0c10aa89 | ||
|
|
5db1f76c55 | ||
|
|
779faa324c | ||
|
|
f23bc81712 | ||
|
|
704894471b | ||
|
|
a04f70e162 | ||
|
|
a150643cdf | ||
|
|
0323325015 | ||
|
|
019195b38c |
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -8,8 +8,11 @@ updates:
|
||||
labels:
|
||||
- "Update dependencies"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
|
||||
# @types/node is related to the version of VS Code we're supporting and should
|
||||
# not be updated to a newer version of Node automatically. However, patch versions
|
||||
# are unrelated to the Node version, so we allow those.
|
||||
- dependency-name: "@types/node"
|
||||
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
@@ -17,6 +20,3 @@ updates:
|
||||
day: "thursday" # Thursday is arbitrary
|
||||
labels:
|
||||
- "Update dependencies"
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
|
||||
|
||||
41
.github/workflows/cli-test.yml
vendored
41
.github/workflows/cli-test.yml
vendored
@@ -11,6 +11,7 @@ on:
|
||||
- extensions/ql-vscode/src/language-support/**
|
||||
- extensions/ql-vscode/src/query-server/**
|
||||
- extensions/ql-vscode/supported_cli_versions.json
|
||||
- extensions/ql-vscode/src/variant-analysis/run-remote-query.ts
|
||||
|
||||
jobs:
|
||||
find-nightly:
|
||||
@@ -109,3 +110,43 @@ jobs:
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
npm run test:cli-integration
|
||||
|
||||
report-failure:
|
||||
name: Report failure on the default branch
|
||||
runs-on: ubuntu-latest
|
||||
needs: [cli-test]
|
||||
if: failure() && github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Create GitHub issue
|
||||
run: |
|
||||
# Set -eu so that we fail if the gh command fails.
|
||||
set -eu
|
||||
|
||||
# Try to find an existing open issue if there is one
|
||||
ISSUE="$(gh issue list --repo "$GITHUB_REPOSITORY" --label "cli-test-failure" --state "open" --limit 1 --json number -q '.[0].number')"
|
||||
|
||||
if [[ -n "$ISSUE" ]]; then
|
||||
echo "Found open issue number $ISSUE ($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/issues/$ISSUE)"
|
||||
else
|
||||
echo "Did not find an open tracking issue. Creating one."
|
||||
|
||||
ISSUE_BODY="issue-body.md"
|
||||
printf "CLI tests have failed on the default branch.\n\n@github/code-scanning-secexp-reviewers" > "$ISSUE_BODY"
|
||||
|
||||
ISSUE="$(gh issue create --repo "$GITHUB_REPOSITORY" --label "cli-test-failure" --title "CLI test failure" --body-file "$ISSUE_BODY")"
|
||||
# `gh issue create` returns the full issue URL, not just the number.
|
||||
echo "Created issue with URL $ISSUE"
|
||||
fi
|
||||
|
||||
COMMENT_FILE="comment.md"
|
||||
RUN_URL=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID
|
||||
printf 'CLI test [%s](%s) failed on ref `%s`' "$GITHUB_RUN_ID" "$RUN_URL" "$GITHUB_REF" > "$COMMENT_FILE"
|
||||
|
||||
# `gh issue create` returns an issue URL, and `gh issue list | cut -f 1` returns an issue number.
|
||||
# Both are accepted here.
|
||||
gh issue comment "$ISSUE" --repo "$GITHUB_REPOSITORY" --body-file "$COMMENT_FILE"
|
||||
|
||||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@@ -13,4 +13,4 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
|
||||
46
.github/workflows/e2e-tests.yml
vendored
Normal file
46
.github/workflows/e2e-tests.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Run E2E Playwright tests
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
cache-dependency-path: extensions/ql-vscode/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: extensions/ql-vscode
|
||||
run: npm ci
|
||||
|
||||
- name: Start containers
|
||||
working-directory: extensions/ql-vscode/test/e2e
|
||||
run: docker-compose -f "docker-compose.yml" up -d --build
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
working-directory: extensions/ql-vscode
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
working-directory: extensions/ql-vscode/test/e2e
|
||||
run: npx playwright test
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: extensions/ql-vscode/playwright-report/
|
||||
retention-days: 30
|
||||
- name: Stop containers
|
||||
working-directory: extensions/ql-vscode/test/e2e
|
||||
if: always()
|
||||
run: docker-compose -f "docker-compose.yml" down -v
|
||||
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
cp dist/*.vsix artifacts
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
with:
|
||||
name: vscode-codeql-extension
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -54,13 +54,13 @@ jobs:
|
||||
echo "ref_name=$REF_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vscode-codeql-extension
|
||||
path: artifacts
|
||||
|
||||
- name: Upload source maps
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vscode-codeql-sourcemaps
|
||||
path: dist/vscode-codeql/out/*.map
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }}
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: vscode-codeql-extension
|
||||
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
OPEN_VSX_TOKEN: ${{ secrets.OPEN_VSX_TOKEN }}
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: vscode-codeql-extension
|
||||
|
||||
|
||||
58
.github/workflows/update-node-version.yml
vendored
Normal file
58
.github/workflows/update-node-version.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Update Node version
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '15 12 * * *' # At 12:15 PM UTC every day
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
create-pr:
|
||||
name: Create PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: extensions/ql-vscode/.nvmrc
|
||||
cache: 'npm'
|
||||
cache-dependency-path: extensions/ql-vscode/package-lock.json
|
||||
- name: Install dependencies
|
||||
working-directory: extensions/ql-vscode
|
||||
run: |
|
||||
npm ci
|
||||
shell: bash
|
||||
- name: Get current Node version
|
||||
working-directory: extensions/ql-vscode
|
||||
id: get-current-node-version
|
||||
run: |
|
||||
echo "version=$(cat .nvmrc)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- name: Update Node version
|
||||
working-directory: extensions/ql-vscode
|
||||
run: |
|
||||
npx ts-node scripts/update-node-version.ts
|
||||
shell: bash
|
||||
- name: Get current Node version
|
||||
working-directory: extensions/ql-vscode
|
||||
id: get-new-node-version
|
||||
run: |
|
||||
echo "version=$(cat .nvmrc)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
- 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-node-version
|
||||
commit-message: Bump Node version to ${{ steps.get-new-node-version.outputs.version }}
|
||||
title: Bump Node version to ${{ steps.get-new-node-version.outputs.version }}
|
||||
body: >
|
||||
The Node version used in the latest version of VS Code has been updated. This PR updates the Node version
|
||||
used for integration tests to match.
|
||||
|
||||
The previous Node version was ${{ steps.get-current-node-version.outputs.version }}. This PR updates the
|
||||
Node version to ${{ steps.get-new-node-version.outputs.version }}.
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -19,3 +19,7 @@ artifacts/
|
||||
# CodeQL metadata
|
||||
.cache/
|
||||
.codeql/
|
||||
|
||||
# E2E Reports
|
||||
**/playwright-report/**
|
||||
**/test-results/**
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"ul-style": {
|
||||
"style": "dash"
|
||||
},
|
||||
"MD013": false,
|
||||
"MD041": false
|
||||
}
|
||||
|
||||
@@ -14,21 +14,21 @@ appearance, race, religion, or sexual identity and orientation.
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
@@ -55,7 +55,7 @@ a project may be further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource@github.com. All
|
||||
reported by contacting the project team at <opensource@github.com>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
@@ -22,12 +22,12 @@ Please note that this project is released with a [Contributor Code of Conduct][c
|
||||
|
||||
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
|
||||
|
||||
* Follow the [style guide][style].
|
||||
* Write tests:
|
||||
* [Tests that don't require the VS Code API are located here](extensions/ql-vscode/test).
|
||||
* [Integration tests that do require the VS Code API are located here](extensions/ql-vscode/src/vscode-tests).
|
||||
* Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
* Write a [good commit message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
- Follow the [style guide][style].
|
||||
- Write tests:
|
||||
- [Tests that don't require the VS Code API are located here](extensions/ql-vscode/test).
|
||||
- [Integration tests that do require the VS Code API are located here](extensions/ql-vscode/src/vscode-tests).
|
||||
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
- Write a [good commit message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
|
||||
## Setting up a local build
|
||||
|
||||
@@ -99,6 +99,6 @@ More information about Storybook can be found inside the **Overview** page once
|
||||
|
||||
## Resources
|
||||
|
||||
* [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||
* [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||
* [GitHub Help](https://help.github.com)
|
||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||
- [GitHub Help](https://help.github.com)
|
||||
|
||||
19
README.md
19
README.md
@@ -6,28 +6,21 @@ 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://github.com/github/vscode-codeql/actions?query=workflow%3A%22Build+Extension%22+branch%3Amain)
|
||||
[](https://marketplace.visualstudio.com/items?itemName=github.vscode-codeql)
|
||||
|
||||
## Features
|
||||
|
||||
* Enables you to use CodeQL to query databases and discover problems in codebases.
|
||||
* Shows the flow of data through the results of path queries, which is essential for triaging security results.
|
||||
* Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/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.
|
||||
- Enables you to use CodeQL to query databases and discover problems in codebases.
|
||||
- Shows the flow of data through the results of path queries, which is essential for triaging security results.
|
||||
- Provides an easy way to run queries from the large, open source repository of [CodeQL security queries](https://github.com/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
|
||||
|
||||
This project will track new feature development in CodeQL and, whenever appropriate, bring that functionality to the Visual Studio Code experience.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This extension depends on the following two extensions for required functionality. They will be installed automatically when you install VS Code CodeQL.
|
||||
|
||||
* [Test Adapter Converter](https://marketplace.visualstudio.com/items?itemName=ms-vscode.test-adapter-converter)
|
||||
* [Test Explorer UI](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer)
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to build, install, and contribute.
|
||||
|
||||
BIN
docs/images/about-vscode-chromium.png
Normal file
BIN
docs/images/about-vscode-chromium.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
BIN
docs/images/electron-chromium-version.png
Normal file
BIN
docs/images/electron-chromium-version.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/images/electron-version.png
Normal file
BIN
docs/images/electron-version.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/images/github-database-download-prompt.png
Normal file
BIN
docs/images/github-database-download-prompt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -7,24 +7,20 @@ We should make sure the CodeQL for VS Code extension works with the Node.js vers
|
||||
|
||||
## Checking the version of Node.js supplied by VS Code
|
||||
|
||||
You can find this info by seleting "About Visual Studio Code" from the top menu.
|
||||
You can find this info by selecting "About Visual Studio Code" from the top menu.
|
||||
|
||||

|
||||
|
||||
## Updating the Node.js version
|
||||
|
||||
The following files will need to be updated:
|
||||
To update the Node.js version, run:
|
||||
|
||||
- `extensions/ql-vscode/.nvmrc` - this will enable nvm to automatically switch to the correct Node
|
||||
version when you're in the project folder. It will also change the Node version the GitHub Actions
|
||||
workflows use.
|
||||
- `extensions/ql-vscode/package.json` - the "engines.node: '[VERSION]'" setting
|
||||
- `extensions/ql-vscode/package.json` - the "@types/node: '[VERSION]'" dependency
|
||||
|
||||
Then run `npm install` to update the `extensions/ql-vscode/package-lock.json` file.
|
||||
```bash
|
||||
npx ts-node scripts/update-node-version.ts
|
||||
```
|
||||
|
||||
## Node.js version used in tests
|
||||
|
||||
Unit tests will use whatever version of Node.js is installed locally. In CI this will be the version specified in the workflow.
|
||||
|
||||
Integration tests download a copy of VS Code and then will use whatever version of Node.js is provided by VS Code. Our integration tests are currently pinned to an older version of VS Code. See [VS Code version used in tests](./vscode-version.md#vs-code-version-used-in-tests) for more information.
|
||||
Integration tests download a copy of VS Code and then will use whatever version of Node.js is provided by VS Code. See [VS Code version used in tests](./vscode-version.md#vs-code-version-used-in-tests) for more information.
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
# Releasing (write access required)
|
||||
|
||||
1. Determine 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.
|
||||
- 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. Create a release branch named after the new version (e.g. `v1.3.6`):
|
||||
* For a regular scheduled release this branch will be based on latest `main`.
|
||||
* Make sure your local copy of `main` is up to date so you are including all changes.
|
||||
* To do a minimal bug-fix release, base the release branch on the tag from the most recent release and then add only the changes you want to release.
|
||||
* Choose this option if you want to release a specific set of changes (e.g. a bug fix) and don't want to incur extra risk by including other changes that have been merged to the `main` branch.
|
||||
- For a regular scheduled release this branch will be based on latest `main`.
|
||||
- Make sure your local copy of `main` is up to date so you are including all changes.
|
||||
- To do a minimal bug-fix release, base the release branch on the tag from the most recent release and then add only the changes you want to release.
|
||||
- Choose this option if you want to release a specific set of changes (e.g. a bug fix) and don't want to incur extra risk by including other changes that have been merged to the `main` branch.
|
||||
|
||||
```bash
|
||||
git checkout -b <new_release_branch> <previous_release_tag>
|
||||
```
|
||||
|
||||
1. Run the ["Run CLI tests" workflow](https://github.com/github/vscode-codeql/actions/workflows/cli-test.yml) and make sure the tests are green.
|
||||
* You can skip this step if you are releasing from `main` and there were no merges since the most recent daily scheduled run of this workflow.
|
||||
- You can skip this step if you are releasing from `main` and there were no merges since the most recent daily scheduled run of this workflow.
|
||||
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 PRs that have been merged since the previous release and make sure they are properly accounted for.
|
||||
* Make sure all changelog entries have links back to their PR(s) if appropriate.
|
||||
- Go through PRs that have been merged since the previous release and make sure they are properly accounted for.
|
||||
- Make sure all changelog entries have links back to their PR(s) if appropriate.
|
||||
1. Double-check that the extension `package.json` and `package-lock.json` have the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
|
||||
1. Commit any changes made during steps 4 and 5 with a commit message the same as the branch name (e.g. `v1.3.6`).
|
||||
1. Open a PR for this release.
|
||||
* The PR diff should contain:
|
||||
* Any missing bits from steps 4 and 5. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
|
||||
* If releasing from a branch other than `main`, this PR will also contain the extension changes being released.
|
||||
- The PR diff should contain:
|
||||
- Any missing bits from steps 4 and 5. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
|
||||
- If releasing from a branch other than `main`, this PR will also contain the extension changes being released.
|
||||
1. Build the extension using `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. Create a new tag on the release branch with your new version (named after the release), e.g.
|
||||
@@ -37,8 +37,8 @@
|
||||
```
|
||||
|
||||
1. Merge the release PR into `main`.
|
||||
* If there are conflicts in the changelog, make sure to place any new changelog entries at the top, above the section for the current release, as these new entries are not part of the current release and should be placed in the "unreleased" section.
|
||||
* The release PR must be merged before pushing the tag to ensure that we always release a commit that is present on the `main` branch. It's not required that the commit is the head of the `main` branch, but there should be no chance of a future release accidentally not including changes from this release.
|
||||
- If there are conflicts in the changelog, make sure to place any new changelog entries at the top, above the section for the current release, as these new entries are not part of the current release and should be placed in the "unreleased" section.
|
||||
- The release PR must be merged before pushing the tag to ensure that we always release a commit that is present on the `main` branch. It's not required that the commit is the head of the `main` branch, but there should be no chance of a future release accidentally not including changes from this release.
|
||||
1. Push the new tag up:
|
||||
|
||||
```bash
|
||||
@@ -46,13 +46,13 @@
|
||||
```
|
||||
|
||||
1. Find the [Release](https://github.com/github/vscode-codeql/actions?query=workflow%3ARelease) workflow run that was just triggered by pushing the tag, and monitor the status of the release build.
|
||||
* DO NOT approve the "publish" stages of the workflow yet.
|
||||
- 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. Approve the deployments of the [Release](https://github.com/github/vscode-codeql/actions?query=workflow%3ARelease) workflow run. This will automatically publish to Open VSX and VS Code Marketplace.
|
||||
* If there is an authentication failure when publishing, be sure to check that the authentication keys haven't expired. See below.
|
||||
- If there is an authentication failure when publishing, be sure to check that the authentication keys haven't expired. See below.
|
||||
1. Go to the draft GitHub release in [the releases page](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.
|
||||
1. If documentation changes need to be published, notify documentation team that release has been made.
|
||||
|
||||
@@ -170,6 +170,8 @@ Run one of the above MRVAs, but cancel it from within VS Code:
|
||||
|
||||
Note that this test requires the feature flag: `codeQL.model.llmGeneration`
|
||||
|
||||
A package that the AI normally gives models for is `javax.servlet-api` from the `jhy/jsoup` repository.
|
||||
|
||||
1. Click "Model with AI".
|
||||
- Check that rows change to "Thinking".
|
||||
- Check that results come back and rows get filled out.
|
||||
@@ -183,6 +185,24 @@ Note that this test requires the feature flag: `codeQL.model.flowGeneration`
|
||||
2. Click "Generate".
|
||||
- Check that rows are filled out.
|
||||
|
||||
### GitHub database download
|
||||
|
||||
#### Test case 1: Download a database
|
||||
|
||||
Open a clone of the [`github/codeql`](https://github.com/github/codeql) repository as a folder.
|
||||
|
||||
1. Wait a few seconds until the CodeQL extension is fully initialized.
|
||||
- Check that the following prompt appears:
|
||||
|
||||

|
||||
|
||||
- If the prompt does not appear, ensure that the `codeQL.githubDatabase.download` setting is not set in workspace or user settings.
|
||||
|
||||
2. Click "Download".
|
||||
3. Select the "C#" and "JavaScript" databases.
|
||||
- Check that there are separate notifications for both downloads.
|
||||
- Check that both databases are added when the downloads are complete.
|
||||
|
||||
### General
|
||||
|
||||
#### Test case 1: Change to a different colour theme
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
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.
|
||||
- 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.
|
||||
|
||||
@@ -26,9 +26,9 @@ Pre-requisites:
|
||||
|
||||
Then, from the `extensions/ql-vscode` directory, use the appropriate command to run the tests:
|
||||
|
||||
* Unit tests: `npm run test:unit`
|
||||
* View Tests: `npm run test:view`
|
||||
* VSCode integration tests: `npm run test:vscode-integration`
|
||||
- Unit tests: `npm run test:unit`
|
||||
- View Tests: `npm run test:view`
|
||||
- VSCode integration tests: `npm run test:vscode-integration`
|
||||
|
||||
#### Running CLI integration tests from the terminal
|
||||
|
||||
@@ -48,9 +48,9 @@ Alternatively, you can run the tests inside of VSCode. There are several VSCode
|
||||
|
||||
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_ task
|
||||
* View Tests: run the _Launch Unit Tests - React_ task
|
||||
* VSCode integration tests: run the _Launch Unit Tests - No Workspace_ and _Launch Unit Tests - Minimal Workspace_ tasks
|
||||
- Unit tests: run the _Launch Unit Tests_ task
|
||||
- View Tests: run the _Launch Unit Tests - React_ task
|
||||
- VSCode integration tests: run the _Launch Unit Tests - No Workspace_ and _Launch Unit Tests - Minimal Workspace_ tasks
|
||||
|
||||
#### Running CLI integration tests from VSCode
|
||||
|
||||
|
||||
@@ -24,10 +24,18 @@ Also consider what percentage of our users are using each VS Code version. This
|
||||
|
||||
## How to update the VS Code version
|
||||
|
||||
To provide a good experience to users, it is recommented to update the `MIN_VERSION` in `extension.ts` first and release, and then update the `vscode` version in `package.json` and release again. By stagging this update across two releases it gives users on older VS Code versions a chance to upgrade before it silently refuses to upgrade them.
|
||||
To provide a good experience to users, it is recommented to update the `MIN_VERSION` in `extension.ts` first and release, and then update the `vscode` version in `package.json` and release again.
|
||||
By staggering this update across two releases it gives users on older VS Code versions a chance to upgrade before it silently refuses to upgrade them.
|
||||
|
||||
After updating the minimum version in `package.json`, make sure to also run the following command to update any generated
|
||||
files dependent on this version:
|
||||
|
||||
```bash
|
||||
npm run generate
|
||||
```
|
||||
|
||||
## VS Code version used in tests
|
||||
|
||||
Our integration tests are currently pinned to use an older version of VS Code due to <https://github.com/github/vscode-codeql/issues/2402>.
|
||||
This version is specified in [`jest-runner-vscode.config.base.js`](https://github.com/github/vscode-codeql/blob/d93f2b67c84e79737b0ce4bb74e31558b5f5166e/extensions/ql-vscode/test/vscode-tests/jest-runner-vscode.config.base.js#L17).
|
||||
Until this is resolved this will limit us updating our minimum supported version of VS Code.
|
||||
The integration tests use the latest stable version of VS Code. This is specified in
|
||||
the [`test/vscode-tests/jest-runner-vscode.config.base.js`](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/test/vscode-tests/jest-runner-vscode.config.base.js#L15)
|
||||
file. This shouldn't need to be updated unless there is a breaking change in VS Code that prevents the tests from running.
|
||||
|
||||
@@ -21,16 +21,17 @@ const baseConfig = {
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:github/react",
|
||||
"plugin:github/recommended",
|
||||
"plugin:github/typescript",
|
||||
"plugin:jest-dom/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:deprecation/recommended",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
"@typescript-eslint/no-use-before-define": 0,
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
@@ -39,39 +40,37 @@ const baseConfig = {
|
||||
ignoreRestSiblings: false,
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@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-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",
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
|
||||
curly: ["error", "all"],
|
||||
"escompat/no-regexp-lookbehind": "off",
|
||||
"etc/no-implicit-any-catch": "error",
|
||||
"filenames/match-regex": "off",
|
||||
"filenames/match-regexp": "off",
|
||||
"func-style": "off",
|
||||
"i18n-text/no-en": "off",
|
||||
"import/named": "off",
|
||||
"import/no-dynamic-require": "off",
|
||||
"import/no-dynamic-required": "off",
|
||||
"import/no-anonymous-default-export": "off",
|
||||
"import/no-commonjs": "off",
|
||||
"import/no-mutable-exports": "off",
|
||||
"import/no-namespace": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"import/no-webpack-loader-syntax": "off",
|
||||
"no-invalid-this": "off",
|
||||
"no-fallthrough": "off",
|
||||
"no-console": "off",
|
||||
"no-shadow": "off",
|
||||
"github/array-foreach": "off",
|
||||
"github/no-then": "off",
|
||||
"react/jsx-key": ["error", { checkFragmentShorthand: true }],
|
||||
"import/no-cycle": "error",
|
||||
// Never allow extensions in import paths, except for JSON files where they are required.
|
||||
"import/extensions": ["error", "never", { json: "always" }],
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
typescript: true,
|
||||
node: true,
|
||||
},
|
||||
"import/extensions": [".js", ".jsx", ".ts", ".tsx", ".json"],
|
||||
// vscode and sarif don't exist on-disk, but only provide types.
|
||||
"import/core-modules": ["vscode", "sarif"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -87,8 +86,10 @@ module.exports = {
|
||||
extends: [
|
||||
...baseConfig.extends,
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
"plugin:github/react",
|
||||
],
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
@@ -107,7 +108,9 @@ module.exports = {
|
||||
extends: [
|
||||
...baseConfig.extends,
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:github/react",
|
||||
],
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
@@ -137,6 +140,8 @@ module.exports = {
|
||||
},
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
// We want to allow mocking of functions in modules, so we need to allow namespace imports.
|
||||
"import/no-namespace": "off",
|
||||
"@typescript-eslint/ban-types": [
|
||||
"error",
|
||||
{
|
||||
@@ -171,5 +176,17 @@ module.exports = {
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [".storybook/**/*.tsx"],
|
||||
parserOptions: {
|
||||
project: resolve(__dirname, ".storybook/tsconfig.json"),
|
||||
},
|
||||
rules: {
|
||||
...baseConfig.rules,
|
||||
// Storybook doesn't use the automatic JSX runtime in the addon yet, so we need to allow
|
||||
// `React` to be imported.
|
||||
"import/no-namespace": ["error", { ignore: ["react"] }],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
v18.15.0
|
||||
v18.17.1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Preview } from "@storybook/react";
|
||||
import type { Preview } from "@storybook/react";
|
||||
import { themes } from "@storybook/theming";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { FunctionComponent, useCallback } from "react";
|
||||
import type { FunctionComponent } from "react";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useGlobals } from "@storybook/manager-api";
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from "react";
|
||||
import { addons, types } from "@storybook/manager-api";
|
||||
import { addons } from "@storybook/manager-api";
|
||||
import { Addon_TypesEnum } from "@storybook/types";
|
||||
import { ThemeSelector } from "./ThemeSelector";
|
||||
|
||||
const ADDON_ID = "vscode-theme-addon";
|
||||
@@ -7,7 +8,7 @@ const ADDON_ID = "vscode-theme-addon";
|
||||
addons.register(ADDON_ID, () => {
|
||||
addons.add(ADDON_ID, {
|
||||
title: "VSCode Themes",
|
||||
type: types.TOOL,
|
||||
type: Addon_TypesEnum.TOOL,
|
||||
match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)),
|
||||
render: () => <ThemeSelector />,
|
||||
});
|
||||
|
||||
@@ -8,27 +8,27 @@ import { VSCodeTheme } from "./theme";
|
||||
|
||||
const themeFiles: { [key in VSCodeTheme]: string } = {
|
||||
[VSCodeTheme.Dark]:
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-commonjs,import/no-webpack-loader-syntax
|
||||
require("!file-loader?modules!../../src/stories/vscode-theme-dark.css")
|
||||
.default,
|
||||
[VSCodeTheme.Light]:
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-commonjs,import/no-webpack-loader-syntax
|
||||
require("!file-loader?modules!../../src/stories/vscode-theme-light.css")
|
||||
.default,
|
||||
[VSCodeTheme.LightHighContrast]:
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-commonjs,import/no-webpack-loader-syntax
|
||||
require("!file-loader?modules!../../src/stories/vscode-theme-light-high-contrast.css")
|
||||
.default,
|
||||
[VSCodeTheme.DarkHighContrast]:
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-commonjs,import/no-webpack-loader-syntax
|
||||
require("!file-loader?modules!../../src/stories/vscode-theme-dark-high-contrast.css")
|
||||
.default,
|
||||
[VSCodeTheme.GitHubLightDefault]:
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-commonjs,import/no-webpack-loader-syntax
|
||||
require("!file-loader?modules!../../src/stories/vscode-theme-github-light-default.css")
|
||||
.default,
|
||||
[VSCodeTheme.GitHubDarkDefault]:
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-commonjs,import/no-webpack-loader-syntax
|
||||
require("!file-loader?modules!../../src/stories/vscode-theme-github-dark-default.css")
|
||||
.default,
|
||||
};
|
||||
|
||||
@@ -1,11 +1,46 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## 1.12.2 - 14 February 2024
|
||||
|
||||
- Stop allowing running variant analyses with a query outside of the workspace. [#3302](https://github.com/github/vscode-codeql/pull/3302)
|
||||
|
||||
## 1.12.1 - 31 January 2024
|
||||
|
||||
- Enable collection of telemetry for the `codeQL.addingDatabases.addDatabaseSourceToWorkspace` setting. [#3238](https://github.com/github/vscode-codeql/pull/3238)
|
||||
- In the CodeQL model editor, you can now select individual method rows and save changes to only the selected rows, instead of having to save the entire library model. [#3156](https://github.com/github/vscode-codeql/pull/3156)
|
||||
- If you run a query without having selected a database, we show a more intuitive prompt to help you select a database. [#3214](https://github.com/github/vscode-codeql/pull/3214)
|
||||
- Error messages returned from the CodeQL CLI are now less verbose and more user-friendly. [#3259](https://github.com/github/vscode-codeql/pull/3259)
|
||||
- The UI for browsing and running CodeQL tests has moved to use VS Code's built-in test UI. This makes the CodeQL test UI more consistent with the test UIs for other languages.
|
||||
This change means that this extension no longer depends on the "Test Explorer UI" and "Test Adapter Converter" extensions. You can uninstall those two extensions if they are
|
||||
not being used by any other extensions you may have installed. [#3232](https://github.com/github/vscode-codeql/pull/3232)
|
||||
|
||||
## 1.12.0 - 11 January 2024
|
||||
|
||||
- Add a prompt for downloading a GitHub database when opening a GitHub repository. [#3138](https://github.com/github/vscode-codeql/pull/3138)
|
||||
- Avoid showing a popup when hovering over source elements in database source files. [#3125](https://github.com/github/vscode-codeql/pull/3125)
|
||||
- Add comparison of alerts when comparing query results. This allows viewing path explanations for differences in alerts. [#3113](https://github.com/github/vscode-codeql/pull/3113)
|
||||
- Fix a bug where the CodeQL CLI and variant analysis results were corrupted after extraction in VS Code Insiders. [#3151](https://github.com/github/vscode-codeql/pull/3151) & [#3152](https://github.com/github/vscode-codeql/pull/3152)
|
||||
- Show progress when extracting the CodeQL CLI distribution during installation. [#3157](https://github.com/github/vscode-codeql/pull/3157)
|
||||
- Add option to cancel opening the model editor. [#3189](https://github.com/github/vscode-codeql/pull/3189)
|
||||
|
||||
## 1.11.0 - 13 December 2023
|
||||
|
||||
- Add a new method modeling panel to classify methods as sources/sinks/summaries while in the context of the source code. [#3128](https://github.com/github/vscode-codeql/pull/3128)
|
||||
- Adds the ability to add multiple classifications per method in the CodeQL Model Editor. [#3128](https://github.com/github/vscode-codeql/pull/3128)
|
||||
- Switch add and delete button positions in the CodeQL Model Editor. [#3123](https://github.com/github/vscode-codeql/pull/3123)
|
||||
- Add a prompt to the "Quick query" command to encourage users in single-folder workspaces to use "Create query" instead. [#3082](https://github.com/github/vscode-codeql/pull/3082)
|
||||
- Remove support for CodeQL CLI versions older than 2.11.6. [#3087](https://github.com/github/vscode-codeql/pull/3087)
|
||||
- Preserve focus on results viewer when showing a location in a file. [#3088](https://github.com/github/vscode-codeql/pull/3088)
|
||||
- The `dataflowtracking` and `tainttracking` snippets expand to the new module-based interface. [#3091](https://github.com/github/vscode-codeql/pull/3091)
|
||||
- The compare view will now show a loading message while the results are loading. [#3107](https://github.com/github/vscode-codeql/pull/3107)
|
||||
- Make top-banner of the model editor sticky [#3120](https://github.com/github/vscode-codeql/pull/3120)
|
||||
|
||||
## 1.10.0 - 16 November 2023
|
||||
|
||||
- Add new CodeQL views for managing databases and queries:
|
||||
1. A queries panel that shows all queries in your workspace. It allows you to view, create, and run queries in one place.
|
||||
2. A language selector, which allows you to quickly filter databases and queries by language.
|
||||
|
||||
|
||||
For more information, see the [documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/#filtering-databases-and-queries-by-language).
|
||||
- When adding a CodeQL database, we no longer add the database source folder to the workspace by default (since this caused bugs in single-folder workspaces). [#3047](https://github.com/github/vscode-codeql/pull/3047)
|
||||
- You can manually add individual database source folders to the workspace with the "Add Database Source to Workspace" right-click command in the databases view.
|
||||
@@ -47,7 +82,7 @@ No user facing changes.
|
||||
|
||||
## 1.9.0 - 19 September 2023
|
||||
|
||||
- Release the [CodeQL model editor](https://codeql.github.com/docs/codeql/codeql-for-visual-studio-code/using-the-codeql-model-editor) to create CodeQL model packs for Java frameworks. Open the editor using the "CodeQL: Open CodeQL Model Editor (Beta)" command. [#2823](https://github.com/github/vscode-codeql/pull/2823)
|
||||
- Release the [CodeQL model editor](https://codeql.github.com/docs/codeql-for-visual-studio-code/using-the-codeql-model-editor/) to create CodeQL model packs for Java frameworks. Open the editor using the "CodeQL: Open CodeQL Model Editor (Beta)" command. [#2823](https://github.com/github/vscode-codeql/pull/2823)
|
||||
|
||||
## 1.8.12 - 11 September 2023
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ For information about other configurations, see the separate [CodeQL help](https
|
||||
### Quick start: Installing and configuring the extension
|
||||
|
||||
1. [Install the extension](#installing-the-extension).
|
||||
*Note: vscode-codeql installs the following dependencies for required functionality: [Test Adapter Converter](https://marketplace.visualstudio.com/items?itemName=ms-vscode.test-adapter-converter), [Test Explorer UI](https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer).*
|
||||
|
||||
1. [Check access to the CodeQL CLI](#checking-access-to-the-codeql-cli).
|
||||
1. [Clone the CodeQL starter workspace](#cloning-the-codeql-starter-workspace).
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { src, dest } from "gulp";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-commonjs
|
||||
const replace = require("gulp-replace");
|
||||
|
||||
/** Inject the application insights key into the telemetry file */
|
||||
|
||||
4
extensions/ql-vscode/gulpfile.ts/chromium-version.json
Normal file
4
extensions/ql-vscode/gulpfile.ts/chromium-version.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"chromiumVersion": "114",
|
||||
"electronVersion": "25.8.0"
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from "fs-extra";
|
||||
import { resolve, join } from "path";
|
||||
import { isDevBuild } from "./dev";
|
||||
import type * as packageJsonType from "../package.json";
|
||||
import type packageJsonType from "../package.json";
|
||||
|
||||
export interface DeployedPackage {
|
||||
distPath: string;
|
||||
|
||||
@@ -8,9 +8,14 @@ import {
|
||||
copyWasmFiles,
|
||||
} from "./typescript";
|
||||
import { compileTextMateGrammar } from "./textmate";
|
||||
import { compileView, watchView } from "./webpack";
|
||||
import { packageExtension } from "./package";
|
||||
import { injectAppInsightsKey } from "./appInsights";
|
||||
import {
|
||||
checkViewTypeScript,
|
||||
compileViewEsbuild,
|
||||
watchViewCheckTypeScript,
|
||||
watchViewEsbuild,
|
||||
} from "./view";
|
||||
|
||||
export const buildWithoutPackage = series(
|
||||
cleanOutput,
|
||||
@@ -19,23 +24,33 @@ export const buildWithoutPackage = series(
|
||||
copyWasmFiles,
|
||||
checkTypeScript,
|
||||
compileTextMateGrammar,
|
||||
compileView,
|
||||
compileViewEsbuild,
|
||||
checkViewTypeScript,
|
||||
),
|
||||
);
|
||||
|
||||
export const watch = parallel(watchEsbuild, watchCheckTypeScript, watchView);
|
||||
export const watch = parallel(
|
||||
// Always build first, so that we don't have to run build manually
|
||||
compileEsbuild,
|
||||
compileViewEsbuild,
|
||||
watchEsbuild,
|
||||
watchCheckTypeScript,
|
||||
watchViewEsbuild,
|
||||
watchViewCheckTypeScript,
|
||||
);
|
||||
|
||||
export {
|
||||
cleanOutput,
|
||||
compileTextMateGrammar,
|
||||
watchEsbuild,
|
||||
watchCheckTypeScript,
|
||||
watchView,
|
||||
watchViewEsbuild,
|
||||
compileEsbuild,
|
||||
copyWasmFiles,
|
||||
checkTypeScript,
|
||||
injectAppInsightsKey,
|
||||
compileView,
|
||||
compileViewEsbuild,
|
||||
checkViewTypeScript,
|
||||
};
|
||||
export default series(
|
||||
buildWithoutPackage,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { dest, src } from "gulp";
|
||||
import { load } from "js-yaml";
|
||||
import { obj } from "through2";
|
||||
import PluginError from "plugin-error";
|
||||
import * as Vinyl from "vinyl";
|
||||
import type Vinyl from "vinyl";
|
||||
|
||||
/**
|
||||
* Replaces all rule references with the match pattern of the referenced rule.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { gray, red } from "ansi-colors";
|
||||
import { dest, src, watch } from "gulp";
|
||||
import esbuild from "gulp-esbuild";
|
||||
import ts from "gulp-typescript";
|
||||
import type { reporter } from "gulp-typescript";
|
||||
import { createProject } from "gulp-typescript";
|
||||
import del from "del";
|
||||
|
||||
function goodReporter(): ts.reporter.Reporter {
|
||||
export function goodReporter(): reporter.Reporter {
|
||||
return {
|
||||
error: (error, typescript) => {
|
||||
if (error.tsFile) {
|
||||
@@ -27,7 +28,7 @@ function goodReporter(): ts.reporter.Reporter {
|
||||
};
|
||||
}
|
||||
|
||||
const tsProject = ts.createProject("tsconfig.json");
|
||||
const tsProject = createProject("tsconfig.json");
|
||||
|
||||
export function cleanOutput() {
|
||||
return tsProject.projectDirectory
|
||||
@@ -56,7 +57,7 @@ export function compileEsbuild() {
|
||||
}
|
||||
|
||||
export function watchEsbuild() {
|
||||
watch("src/**/*.ts", compileEsbuild);
|
||||
watch(["src/**/*.ts", "!src/view/**/*.ts"], compileEsbuild);
|
||||
}
|
||||
|
||||
export function checkTypeScript() {
|
||||
@@ -66,7 +67,7 @@ export function checkTypeScript() {
|
||||
}
|
||||
|
||||
export function watchCheckTypeScript() {
|
||||
watch("src/**/*.ts", checkTypeScript);
|
||||
watch(["src/**/*.ts", "!src/view/**/*.ts"], checkTypeScript);
|
||||
}
|
||||
|
||||
export function copyWasmFiles() {
|
||||
|
||||
42
extensions/ql-vscode/gulpfile.ts/view.ts
Normal file
42
extensions/ql-vscode/gulpfile.ts/view.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { dest, src, watch } from "gulp";
|
||||
import esbuild from "gulp-esbuild";
|
||||
import { createProject } from "gulp-typescript";
|
||||
import { goodReporter } from "./typescript";
|
||||
|
||||
import chromiumVersion from "./chromium-version.json";
|
||||
|
||||
const tsProject = createProject("src/view/tsconfig.json");
|
||||
|
||||
export function compileViewEsbuild() {
|
||||
return src("./src/view/webview.tsx")
|
||||
.pipe(
|
||||
esbuild({
|
||||
outfile: "webview.js",
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
platform: "browser",
|
||||
target: `chrome${chromiumVersion.chromiumVersion}`,
|
||||
jsx: "automatic",
|
||||
sourcemap: "linked",
|
||||
sourceRoot: "..",
|
||||
loader: {
|
||||
".ttf": "file",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.pipe(dest("out"));
|
||||
}
|
||||
|
||||
export function watchViewEsbuild() {
|
||||
watch(["src/**/*.{ts,tsx}"], compileViewEsbuild);
|
||||
}
|
||||
|
||||
export function checkViewTypeScript() {
|
||||
// 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 watchViewCheckTypeScript() {
|
||||
watch(["src/**/*.{ts,tsx}"], checkViewTypeScript);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import { resolve } from "path";
|
||||
import * as webpack from "webpack";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import { isDevBuild } from "./dev";
|
||||
|
||||
export const config: webpack.Configuration = {
|
||||
mode: isDevBuild ? "development" : "production",
|
||||
entry: {
|
||||
webview: "./src/view/webview.tsx",
|
||||
},
|
||||
output: {
|
||||
path: resolve(__dirname, "..", "out"),
|
||||
filename: "[name].js",
|
||||
},
|
||||
devtool: isDevBuild ? "inline-source-map" : "source-map",
|
||||
resolve: {
|
||||
extensions: [".js", ".ts", ".tsx", ".json"],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
configFile: "src/view/tsconfig.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "less-loader",
|
||||
options: {
|
||||
javascriptEnabled: true,
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)?|ttf|eot)$/,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "fonts/[hash][ext][query]",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
hints: false,
|
||||
},
|
||||
plugins: [new MiniCssExtractPlugin()],
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
import webpack from "webpack";
|
||||
import { config } from "./webpack.config";
|
||||
|
||||
export function compileView(cb: (err?: Error) => void) {
|
||||
doWebpack(config, true, cb);
|
||||
}
|
||||
|
||||
export function watchView(cb: (err?: Error) => void) {
|
||||
const watchConfig = {
|
||||
...config,
|
||||
watch: true,
|
||||
watchOptions: {
|
||||
aggregateTimeout: 200,
|
||||
poll: 1000,
|
||||
},
|
||||
};
|
||||
doWebpack(watchConfig, false, cb);
|
||||
}
|
||||
|
||||
function doWebpack(
|
||||
internalConfig: webpack.Configuration,
|
||||
failOnError: boolean,
|
||||
cb: (err?: Error) => void,
|
||||
) {
|
||||
const resultCb = (error: Error | undefined, stats?: webpack.Stats) => {
|
||||
if (error) {
|
||||
cb(error);
|
||||
}
|
||||
if (stats) {
|
||||
console.log(
|
||||
stats.toString({
|
||||
errorDetails: true,
|
||||
colors: true,
|
||||
assets: false,
|
||||
builtAt: false,
|
||||
version: false,
|
||||
hash: false,
|
||||
entrypoints: false,
|
||||
timings: false,
|
||||
modules: false,
|
||||
errors: true,
|
||||
}),
|
||||
);
|
||||
if (stats.hasErrors()) {
|
||||
if (failOnError) {
|
||||
cb(new Error("Compilation errors detected."));
|
||||
return;
|
||||
} else {
|
||||
console.error("Compilation errors detected.");
|
||||
}
|
||||
}
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
webpack(internalConfig, resultCb);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
/** @type {import('@jest/types').Config.InitialOptions} */
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
module.exports = {
|
||||
projects: [
|
||||
"<rootDir>/src/view",
|
||||
|
||||
46740
extensions/ql-vscode/package-lock.json
generated
46740
extensions/ql-vscode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.10.0",
|
||||
"version": "1.12.2",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -14,14 +14,14 @@
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.82.0",
|
||||
"node": "^18.15.0",
|
||||
"node": "^18.17.1",
|
||||
"npm": ">=7.20.6"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"hbenl.vscode-test-explorer"
|
||||
"vscode.git"
|
||||
],
|
||||
"capabilities": {
|
||||
"untrustedWorkspaces": {
|
||||
@@ -38,7 +38,8 @@
|
||||
"onWebviewPanel:resultsView",
|
||||
"onWebviewPanel:codeQL.variantAnalysis",
|
||||
"onWebviewPanel:codeQL.dataFlowPaths",
|
||||
"onFileSystem:codeql-zip-archive"
|
||||
"onFileSystem:codeql-zip-archive",
|
||||
"workspaceContains:.git"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
"files": [
|
||||
@@ -419,8 +420,41 @@
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "Log insights",
|
||||
"title": "GitHub Databases",
|
||||
"order": 8,
|
||||
"properties": {
|
||||
"codeQL.githubDatabase.download": {
|
||||
"type": "string",
|
||||
"default": "ask",
|
||||
"enum": [
|
||||
"ask",
|
||||
"never"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Ask to download a GitHub database when a workspace is opened.",
|
||||
"Never download a GitHub databases when a workspace is opened."
|
||||
],
|
||||
"description": "Ask to download a GitHub database when a workspace is opened."
|
||||
},
|
||||
"codeQL.githubDatabase.update": {
|
||||
"type": "string",
|
||||
"default": "ask",
|
||||
"enum": [
|
||||
"ask",
|
||||
"never"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Ask to download an updated GitHub database when a new version is available.",
|
||||
"Never download an updated GitHub database when a new version is available."
|
||||
],
|
||||
"description": "Ask to download an updated GitHub database when a new version is available."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"title": "Log insights",
|
||||
"order": 9,
|
||||
"properties": {
|
||||
"codeQL.logInsights.joinOrderWarningThreshold": {
|
||||
"type": "number",
|
||||
@@ -434,7 +468,7 @@
|
||||
{
|
||||
"type": "object",
|
||||
"title": "Telemetry",
|
||||
"order": 9,
|
||||
"order": 10,
|
||||
"properties": {
|
||||
"codeQL.telemetry.enableTelemetry": {
|
||||
"type": "boolean",
|
||||
@@ -511,6 +545,14 @@
|
||||
"command": "codeQL.runVariantAnalysisContextEditor",
|
||||
"title": "CodeQL: Run Variant Analysis"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysisContextExplorer",
|
||||
"title": "CodeQL: Run Variant Analysis"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysisPublishedPack",
|
||||
"title": "CodeQL: Run Variant Analysis against published pack"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.exportSelectedVariantAnalysisResults",
|
||||
"title": "CodeQL: Export Variant Analysis Results"
|
||||
@@ -1287,6 +1329,11 @@
|
||||
"group": "9_qlCommands",
|
||||
"when": "resourceScheme != codeql-zip-archive"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysisContextExplorer",
|
||||
"group": "9_qlCommands",
|
||||
"when": "resourceExtname == .ql && config.codeQL.canary && config.codeQL.variantAnalysis.multiQuery"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFileContextExplorer",
|
||||
"group": "9_qlCommands",
|
||||
@@ -1363,6 +1410,14 @@
|
||||
"command": "codeQL.runVariantAnalysis",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysisContextExplorer",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysisPublishedPack",
|
||||
"when": "config.codeQL.canary && config.codeQL.variantAnalysis.multiQuery"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runVariantAnalysisContextEditor",
|
||||
"when": "false"
|
||||
@@ -1800,15 +1855,14 @@
|
||||
{
|
||||
"id": "codeQLMethodModeling",
|
||||
"type": "webview",
|
||||
"name": "CodeQL Method Modeling",
|
||||
"when": "config.codeQL.canary"
|
||||
"name": "CodeQL Method Modeling"
|
||||
}
|
||||
],
|
||||
"codeql-methods-usage": [
|
||||
{
|
||||
"id": "codeQLMethodsUsage",
|
||||
"name": "CodeQL Methods Usage",
|
||||
"when": "config.codeQL.canary && codeql.modelEditorOpen"
|
||||
"when": "codeql.modelEditorOpen"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1868,74 +1922,72 @@
|
||||
"lint:scenarios": "ts-node scripts/lint-scenarios.ts",
|
||||
"generate": "npm-run-all -p generate:*",
|
||||
"generate:schemas": "ts-node scripts/generate-schemas.ts",
|
||||
"generate:chromium-version": "ts-node scripts/generate-chromium-version.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",
|
||||
"prepare": "cd ../.. && husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.26.9",
|
||||
"@octokit/plugin-retry": "^6.0.1",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@vscode/codicons": "^0.0.31",
|
||||
"@vscode/codicons": "^0.0.35",
|
||||
"@vscode/debugadapter": "^1.59.0",
|
||||
"@vscode/debugprotocol": "^1.59.0",
|
||||
"@vscode/webview-ui-toolkit": "^1.0.1",
|
||||
"ajv": "^8.11.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"classnames": "^2.2.6",
|
||||
"d3": "^7.6.1",
|
||||
"d3-graphviz": "^5.0.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"immutable": "^4.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"msw": "^2.0.0",
|
||||
"msw": "^2.0.13",
|
||||
"nanoid": "^5.0.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-queue": "^7.4.1",
|
||||
"p-queue": "^8.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"semver": "^7.5.2",
|
||||
"semver": "^7.6.0",
|
||||
"source-map": "^0.7.4",
|
||||
"source-map-support": "^0.5.21",
|
||||
"stream-json": "^1.7.3",
|
||||
"styled-components": "^6.0.2",
|
||||
"tmp": "^0.1.0",
|
||||
"styled-components": "^6.1.8",
|
||||
"tmp": "^0.2.1",
|
||||
"tmp-promise": "^3.0.2",
|
||||
"tree-kill": "^1.2.2",
|
||||
"unzipper": "^0.10.5",
|
||||
"vscode-extension-telemetry": "^0.1.6",
|
||||
"vscode-jsonrpc": "^8.0.2",
|
||||
"vscode-languageclient": "^8.0.2",
|
||||
"vscode-test-adapter-api": "^1.7.0",
|
||||
"vscode-test-adapter-util": "^0.7.0",
|
||||
"zip-a-folder": "^3.1.3"
|
||||
"yauzl": "^2.10.0",
|
||||
"zip-a-folder": "^3.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.13",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@babel/preset-env": "^7.23.9",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
"@github/markdownlint-github": "^0.3.0",
|
||||
"@github/markdownlint-github": "^0.6.0",
|
||||
"@octokit/plugin-throttling": "^8.0.0",
|
||||
"@storybook/addon-a11y": "^7.4.6",
|
||||
"@playwright/test": "^1.40.1",
|
||||
"@storybook/addon-a11y": "^7.6.13",
|
||||
"@storybook/addon-actions": "^7.1.0",
|
||||
"@storybook/addon-essentials": "^7.1.0",
|
||||
"@storybook/addon-interactions": "^7.1.0",
|
||||
"@storybook/addon-links": "^7.1.0",
|
||||
"@storybook/components": "^7.1.0",
|
||||
"@storybook/components": "^7.6.7",
|
||||
"@storybook/csf": "^0.1.1",
|
||||
"@storybook/manager-api": "^7.1.0",
|
||||
"@storybook/manager-api": "^7.6.7",
|
||||
"@storybook/react": "^7.1.0",
|
||||
"@storybook/react-webpack5": "^7.1.0",
|
||||
"@storybook/theming": "^7.1.0",
|
||||
"@testing-library/dom": "^9.3.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@storybook/react-webpack5": "^7.6.12",
|
||||
"@storybook/theming": "^7.6.12",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^6.2.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/child-process-promise": "^2.2.1",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-graphviz": "^2.6.6",
|
||||
"@types/del": "^4.0.0",
|
||||
@@ -1945,45 +1997,46 @@
|
||||
"@types/jest": "^29.0.2",
|
||||
"@types/js-yaml": "^4.0.6",
|
||||
"@types/nanoid": "^3.0.0",
|
||||
"@types/node": "18.15.0",
|
||||
"@types/node": "18.17.*",
|
||||
"@types/node-fetch": "^2.5.2",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/sarif": "^2.1.2",
|
||||
"@types/semver": "^7.2.0",
|
||||
"@types/stream-json": "^1.7.1",
|
||||
"@types/styled-components": "^5.1.11",
|
||||
"@types/tar-stream": "^2.2.2",
|
||||
"@types/tar-stream": "^3.1.3",
|
||||
"@types/through2": "^2.0.36",
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/tmp": "^0.2.6",
|
||||
"@types/unzipper": "^0.10.1",
|
||||
"@types/vscode": "^1.82.0",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.16.0",
|
||||
"@vscode/test-electron": "^2.2.0",
|
||||
"@vscode/vsce": "^2.19.0",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"applicationinsights": "^2.3.5",
|
||||
"cosmiconfig": "^8.2.0",
|
||||
"applicationinsights": "^2.9.2",
|
||||
"cosmiconfig": "^9.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.8.1",
|
||||
"css-loader": "^6.10.0",
|
||||
"del": "^6.0.0",
|
||||
"esbuild": "^0.15.15",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-deprecation": "^2.0.0",
|
||||
"eslint-plugin-etc": "^2.0.2",
|
||||
"eslint-plugin-github": "^4.4.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jest-dom": "^5.0.1",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-storybook": "^0.6.4",
|
||||
"file-loader": "^6.2.0",
|
||||
"glob": "^10.0.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-esbuild": "^0.10.5",
|
||||
"gulp-esbuild": "^0.12.0",
|
||||
"gulp-replace": "^1.1.3",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"husky": "^8.0.0",
|
||||
@@ -1991,13 +2044,13 @@
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"jest-runner-vscode": "^3.0.1",
|
||||
"lint-staged": "^15.0.2",
|
||||
"markdownlint-cli2": "^0.6.0",
|
||||
"markdownlint-cli2-formatter-pretty": "^0.0.4",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"markdownlint-cli2": "^0.12.1",
|
||||
"markdownlint-cli2-formatter-pretty": "^0.0.5",
|
||||
"mini-css-extract-plugin": "^2.7.7",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"storybook": "^7.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"storybook": "^7.6.10",
|
||||
"tar-stream": "^3.0.0",
|
||||
"through2": "^4.0.2",
|
||||
"ts-jest": "^29.0.1",
|
||||
@@ -2005,9 +2058,7 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-unused-exports": "^10.0.0",
|
||||
"typescript": "^5.0.2",
|
||||
"webpack": "^5.76.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
"typescript": "^5.0.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/*.{json,css,scss}": [
|
||||
|
||||
@@ -14,15 +14,16 @@
|
||||
import { pathExists, readJson, writeJson } from "fs-extra";
|
||||
import { resolve, relative } from "path";
|
||||
|
||||
import { Octokit } from "@octokit/core";
|
||||
import { type RestEndpointMethodTypes } from "@octokit/rest";
|
||||
import type { Octokit } from "@octokit/core";
|
||||
import type { EndpointDefaults } from "@octokit/types";
|
||||
import type { RestEndpointMethodTypes } from "@octokit/rest";
|
||||
import { throttling } from "@octokit/plugin-throttling";
|
||||
|
||||
import { getFiles } from "./util/files";
|
||||
import type { GitHubApiRequest } from "../src/common/mock-gh-api/gh-api-request";
|
||||
import { isGetVariantAnalysisRequest } from "../src/common/mock-gh-api/gh-api-request";
|
||||
import { VariantAnalysis } from "../src/variant-analysis/gh-api/variant-analysis";
|
||||
import { RepositoryWithMetadata } from "../src/variant-analysis/gh-api/repository";
|
||||
import type { VariantAnalysis } from "../src/variant-analysis/gh-api/variant-analysis";
|
||||
import type { RepositoryWithMetadata } from "../src/variant-analysis/gh-api/repository";
|
||||
import { AppOctokit } from "../src/common/octokit";
|
||||
|
||||
const extensionDirectory = resolve(__dirname, "..");
|
||||
@@ -42,7 +43,7 @@ const octokit = new MyOctokit({
|
||||
throttle: {
|
||||
onRateLimit: (
|
||||
retryAfter: number,
|
||||
options: any,
|
||||
options: EndpointDefaults,
|
||||
octokit: Octokit,
|
||||
): boolean => {
|
||||
octokit.log.warn(
|
||||
@@ -53,7 +54,7 @@ const octokit = new MyOctokit({
|
||||
},
|
||||
onSecondaryRateLimit: (
|
||||
_retryAfter: number,
|
||||
options: any,
|
||||
options: EndpointDefaults,
|
||||
octokit: Octokit,
|
||||
): void => {
|
||||
octokit.log.warn(
|
||||
|
||||
@@ -9,12 +9,13 @@ function ignoreFile(file: string): boolean {
|
||||
containsPath(".storybook", file) ||
|
||||
containsPath(join("src", "stories"), file) ||
|
||||
pathsEqual(
|
||||
join("test", "vscode-tests", "jest-runner-installed-extensions.ts"),
|
||||
join("test", "vscode-tests", "jest-runner-vscode-codeql-cli.ts"),
|
||||
file,
|
||||
) ||
|
||||
basename(file) === "jest.config.ts" ||
|
||||
basename(file) === "index.tsx" ||
|
||||
basename(file) === "index.ts"
|
||||
basename(file) === "index.ts" ||
|
||||
basename(file) === "playwright.config.ts"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
42
extensions/ql-vscode/scripts/generate-chromium-version.ts
Normal file
42
extensions/ql-vscode/scripts/generate-chromium-version.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { join, resolve } from "path";
|
||||
import { outputFile, readJSON } from "fs-extra";
|
||||
import { minVersion } from "semver";
|
||||
import { getVersionInformation } from "./util/vscode-versions";
|
||||
|
||||
const extensionDirectory = resolve(__dirname, "..");
|
||||
|
||||
async function generateChromiumVersion() {
|
||||
const packageJson = await readJSON(
|
||||
resolve(extensionDirectory, "package.json"),
|
||||
);
|
||||
|
||||
const minimumVsCodeVersion = minVersion(packageJson.engines.vscode)?.version;
|
||||
if (!minimumVsCodeVersion) {
|
||||
throw new Error("Could not find minimum VS Code version");
|
||||
}
|
||||
|
||||
const versionInformation = await getVersionInformation(minimumVsCodeVersion);
|
||||
|
||||
const chromiumMajorVersion = versionInformation.chromiumVersion.split(".")[0];
|
||||
|
||||
console.log(
|
||||
`VS Code ${minimumVsCodeVersion} uses Chromium ${chromiumMajorVersion}`,
|
||||
);
|
||||
|
||||
await outputFile(
|
||||
join(extensionDirectory, "gulpfile.ts", "chromium-version.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
chromiumVersion: chromiumMajorVersion,
|
||||
electronVersion: versionInformation.electronVersion,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
generateChromiumVersion().catch((e: unknown) => {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
});
|
||||
@@ -6,6 +6,16 @@ import { format, resolveConfig } from "prettier";
|
||||
const extensionDirectory = resolve(__dirname, "..");
|
||||
|
||||
const schemas = [
|
||||
{
|
||||
path: join(extensionDirectory, "src", "packaging", "qlpack-file.ts"),
|
||||
type: "QlPackFile",
|
||||
schemaPath: join(
|
||||
extensionDirectory,
|
||||
"src",
|
||||
"packaging",
|
||||
"qlpack-file.schema.json",
|
||||
),
|
||||
},
|
||||
{
|
||||
path: join(
|
||||
extensionDirectory,
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
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";
|
||||
import type { RawSourceMap } from "source-map";
|
||||
import { SourceMapConsumer } from "source-map";
|
||||
import { unzipToDirectorySequentially } from "../src/common/unzip";
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.error(
|
||||
@@ -78,10 +79,10 @@ async function extractSourceMap() {
|
||||
releaseAssetsDirectory,
|
||||
]);
|
||||
|
||||
const file = await Open.file(
|
||||
await unzipToDirectorySequentially(
|
||||
resolve(releaseAssetsDirectory, sourcemapAsset.name),
|
||||
sourceMapsDirectory,
|
||||
);
|
||||
await file.extract({ path: sourceMapsDirectory });
|
||||
} else {
|
||||
const workflowRuns = runGhJSON<WorkflowRunListItem[]>([
|
||||
"run",
|
||||
@@ -242,7 +243,7 @@ type WorkflowRunListItem = {
|
||||
async function replaceAsync(
|
||||
str: string,
|
||||
regex: RegExp,
|
||||
replacer: (substring: string, ...args: any[]) => Promise<string>,
|
||||
replacer: (substring: string, ...args: string[]) => Promise<string>,
|
||||
) {
|
||||
const promises: Array<Promise<string>> = [];
|
||||
str.replace(regex, (match, ...args) => {
|
||||
|
||||
87
extensions/ql-vscode/scripts/update-node-version.ts
Normal file
87
extensions/ql-vscode/scripts/update-node-version.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { join, resolve } from "path";
|
||||
import { execSync } from "child_process";
|
||||
import { outputFile, readFile, readJSON } from "fs-extra";
|
||||
import { getVersionInformation } from "./util/vscode-versions";
|
||||
import { fetchJson } from "./util/fetch";
|
||||
|
||||
const extensionDirectory = resolve(__dirname, "..");
|
||||
|
||||
interface Release {
|
||||
tag_name: string;
|
||||
}
|
||||
|
||||
async function updateNodeVersion() {
|
||||
const latestVsCodeRelease = await fetchJson<Release>(
|
||||
"https://api.github.com/repos/microsoft/vscode/releases/latest",
|
||||
);
|
||||
const latestVsCodeVersion = latestVsCodeRelease.tag_name;
|
||||
|
||||
console.log(`Latest VS Code version is ${latestVsCodeVersion}`);
|
||||
|
||||
const versionInformation = await getVersionInformation(latestVsCodeVersion);
|
||||
console.log(
|
||||
`VS Code ${versionInformation.vscodeVersion} uses Electron ${versionInformation.electronVersion} and Node ${versionInformation.nodeVersion}`,
|
||||
);
|
||||
|
||||
let currentNodeVersion = (
|
||||
await readFile(join(extensionDirectory, ".nvmrc"), "utf8")
|
||||
).trim();
|
||||
if (currentNodeVersion.startsWith("v")) {
|
||||
currentNodeVersion = currentNodeVersion.slice(1);
|
||||
}
|
||||
|
||||
if (currentNodeVersion === versionInformation.nodeVersion) {
|
||||
console.log("Node version is already up to date");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Node version needs to be updated, updating now");
|
||||
|
||||
await outputFile(
|
||||
join(extensionDirectory, ".nvmrc"),
|
||||
`v${versionInformation.nodeVersion}\n`,
|
||||
);
|
||||
|
||||
console.log("Updated .nvmrc");
|
||||
|
||||
const packageJson = await readJSON(
|
||||
join(extensionDirectory, "package.json"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
// The @types/node version needs to match the first two parts of the Node
|
||||
// version, e.g. if the Node version is 18.17.3, the @types/node version
|
||||
// should be 18.17.*. This corresponds with the documentation at
|
||||
// https://github.com/definitelytyped/definitelytyped#how-do-definitely-typed-package-versions-relate-to-versions-of-the-corresponding-library
|
||||
// "The patch version of the type declaration package is unrelated to the library patch version. This allows
|
||||
// Definitely Typed to safely update type declarations for the same major/minor version of a library."
|
||||
// 18.17.* is equivalent to >=18.17.0 <18.18.0
|
||||
const typesNodeVersion = versionInformation.nodeVersion
|
||||
.split(".")
|
||||
.slice(0, 2)
|
||||
.join(".");
|
||||
|
||||
packageJson.engines.node = `^${versionInformation.nodeVersion}`;
|
||||
packageJson.devDependencies["@types/node"] = `${typesNodeVersion}.*`;
|
||||
|
||||
await outputFile(
|
||||
join(extensionDirectory, "package.json"),
|
||||
`${JSON.stringify(packageJson, null, 2)}\n`,
|
||||
);
|
||||
|
||||
console.log("Updated package.json, now running npm install");
|
||||
|
||||
execSync("npm install", { cwd: extensionDirectory, stdio: "inherit" });
|
||||
// Always use the latest patch version of @types/node
|
||||
execSync("npm upgrade @types/node", {
|
||||
cwd: extensionDirectory,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
console.log("Node version updated successfully");
|
||||
}
|
||||
|
||||
updateNodeVersion().catch((e: unknown) => {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
});
|
||||
10
extensions/ql-vscode/scripts/util/fetch.ts
Normal file
10
extensions/ql-vscode/scripts/util/fetch.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export async function fetchJson<T>(url: string): Promise<T> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Could not fetch ${url}: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
70
extensions/ql-vscode/scripts/util/vscode-versions.ts
Normal file
70
extensions/ql-vscode/scripts/util/vscode-versions.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { minVersion } from "semver";
|
||||
import { fetchJson } from "./fetch";
|
||||
|
||||
type VsCodePackageJson = {
|
||||
devDependencies: {
|
||||
electron: string;
|
||||
};
|
||||
};
|
||||
|
||||
async function getVsCodePackageJson(
|
||||
version: string,
|
||||
): Promise<VsCodePackageJson> {
|
||||
return await fetchJson(
|
||||
`https://raw.githubusercontent.com/microsoft/vscode/${version}/package.json`,
|
||||
);
|
||||
}
|
||||
|
||||
interface ElectronVersion {
|
||||
version: string;
|
||||
date: string;
|
||||
node: string;
|
||||
v8: string;
|
||||
uv: string;
|
||||
zlib: string;
|
||||
openssl: string;
|
||||
modules: string;
|
||||
chrome: string;
|
||||
files: string[];
|
||||
body?: string;
|
||||
apm?: string;
|
||||
}
|
||||
|
||||
async function getElectronReleases(): Promise<ElectronVersion[]> {
|
||||
return await fetchJson("https://releases.electronjs.org/releases.json");
|
||||
}
|
||||
|
||||
type VersionInformation = {
|
||||
vscodeVersion: string;
|
||||
electronVersion: string;
|
||||
nodeVersion: string;
|
||||
chromiumVersion: string;
|
||||
};
|
||||
|
||||
export async function getVersionInformation(
|
||||
vscodeVersion: string,
|
||||
): Promise<VersionInformation> {
|
||||
const vsCodePackageJson = await getVsCodePackageJson(vscodeVersion);
|
||||
const electronVersion = minVersion(
|
||||
vsCodePackageJson.devDependencies.electron,
|
||||
)?.version;
|
||||
if (!electronVersion) {
|
||||
throw new Error("Could not find Electron version");
|
||||
}
|
||||
|
||||
const electronReleases = await getElectronReleases();
|
||||
|
||||
const electronRelease = electronReleases.find(
|
||||
(release) => release.version === electronVersion,
|
||||
);
|
||||
if (!electronRelease) {
|
||||
throw new Error(`Could not find Electron release ${electronVersion}`);
|
||||
}
|
||||
|
||||
return {
|
||||
vscodeVersion,
|
||||
electronVersion,
|
||||
nodeVersion: electronRelease.node,
|
||||
chromiumVersion: electronRelease.chrome,
|
||||
};
|
||||
}
|
||||
@@ -30,34 +30,34 @@
|
||||
"Dataflow Tracking Class": {
|
||||
"prefix": "dataflowtracking",
|
||||
"body": [
|
||||
"class $1 extends DataFlow::Configuration {",
|
||||
"\t$1() { this = \"$1\" }",
|
||||
"\t",
|
||||
"\toverride predicate isSource(DataFlow::Node node) {",
|
||||
"module $1 implements DataFlow::ConfigSig {",
|
||||
"\tpredicate isSource(DataFlow::Node node) {",
|
||||
"\t\t${2:none()}",
|
||||
"\t}",
|
||||
"\t",
|
||||
"\toverride predicate isSink(DataFlow::Node node) {",
|
||||
"",
|
||||
"\tpredicate isSink(DataFlow::Node node) {",
|
||||
"\t\t${3:none()}",
|
||||
"\t}",
|
||||
"}"
|
||||
"}",
|
||||
"",
|
||||
"module ${4:Flow} = DataFlow::Global<$1>;"
|
||||
],
|
||||
"description": "Boilerplate for a dataflow tracking class"
|
||||
},
|
||||
"Taint Tracking Class": {
|
||||
"prefix": "tainttracking",
|
||||
"body": [
|
||||
"class $1 extends TaintTracking::Configuration {",
|
||||
"\t$1() { this = \"$1\" }",
|
||||
"\t",
|
||||
"\toverride predicate isSource(DataFlow::Node node) {",
|
||||
"module $1 implements DataFlow::ConfigSig {",
|
||||
"\tpredicate isSource(DataFlow::Node node) {",
|
||||
"\t\t${2:none()}",
|
||||
"\t}",
|
||||
"\t",
|
||||
"\toverride predicate isSink(DataFlow::Node node) {",
|
||||
"",
|
||||
"\tpredicate isSink(DataFlow::Node node) {",
|
||||
"\t\t${3:none()}",
|
||||
"\t}",
|
||||
"}"
|
||||
"}",
|
||||
"",
|
||||
"module ${4:Flow} = TaintTracking::Global<$1>;"
|
||||
],
|
||||
"description": "Boilerplate for a taint tracking class"
|
||||
},
|
||||
|
||||
15
extensions/ql-vscode/src/additional-typings.d.ts
vendored
15
extensions/ql-vscode/src/additional-typings.d.ts
vendored
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* The d3 library is designed to work in both the browser and
|
||||
* node. Consequently their typings files refer to both node
|
||||
* types like `Buffer` (which don't exist in the browser), and browser
|
||||
* types like `Blob` (which don't exist in node). Instead of sticking
|
||||
* all of `dom` in `compilerOptions.lib`, it suffices just to put in a
|
||||
* stub definition of the affected types so that compilation
|
||||
* succeeds.
|
||||
*/
|
||||
|
||||
declare type RequestInit = Record<string, unknown>;
|
||||
declare type ElementTagNameMap = any;
|
||||
declare type NodeListOf<T> = Record<string, T>;
|
||||
declare type Node = Record<string, unknown>;
|
||||
declare type XMLDocument = Record<string, unknown>;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppCommandManager } from "../common/commands";
|
||||
import type { AppCommandManager } from "../common/commands";
|
||||
import { Uri, workspace } from "vscode";
|
||||
import { join } from "path";
|
||||
import { pathExists } from "fs-extra";
|
||||
|
||||
62
extensions/ql-vscode/src/codeql-cli/cli-command.ts
Normal file
62
extensions/ql-vscode/src/codeql-cli/cli-command.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
|
||||
import type { BaseLogger } from "../common/logging";
|
||||
import type { ProgressReporter } from "../common/logging/vscode";
|
||||
import {
|
||||
getChildProcessErrorMessage,
|
||||
getErrorMessage,
|
||||
} from "../common/helpers-pure";
|
||||
|
||||
/**
|
||||
* Flags to pass to all cli commands.
|
||||
*/
|
||||
export const LOGGING_FLAGS = ["-v", "--log-to-stderr"];
|
||||
|
||||
/**
|
||||
* Runs a CodeQL CLI command without invoking the CLI server, deserializing the output as JSON.
|
||||
* @param codeQlPath The path to the CLI.
|
||||
* @param command The `codeql` command to be run, provided as an array of command/subcommand names.
|
||||
* @param commandArgs The arguments to pass to the `codeql` command.
|
||||
* @param description Description of the action being run, to be shown in log and error messages.
|
||||
* @param logger Logger to write command log messages, e.g. to an output channel.
|
||||
* @param progressReporter Used to output progress messages, e.g. to the status bar.
|
||||
* @returns A JSON object parsed from the contents of the command's stdout, if the command succeeded.
|
||||
*/
|
||||
export async function runJsonCodeQlCliCommand<OutputType>(
|
||||
codeQlPath: string,
|
||||
command: string[],
|
||||
commandArgs: string[],
|
||||
description: string,
|
||||
logger: BaseLogger,
|
||||
progressReporter?: ProgressReporter,
|
||||
): Promise<OutputType> {
|
||||
// Add logging arguments first, in case commandArgs contains positional parameters.
|
||||
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
|
||||
const argsString = args.join(" ");
|
||||
let stdout: string;
|
||||
try {
|
||||
if (progressReporter !== undefined) {
|
||||
progressReporter.report({ message: description });
|
||||
}
|
||||
void logger.log(
|
||||
`${description} using CodeQL CLI: ${codeQlPath} ${argsString}...`,
|
||||
);
|
||||
const result = await promisify(execFile)(codeQlPath, args);
|
||||
void logger.log(result.stderr);
|
||||
void logger.log("CLI command succeeded.");
|
||||
stdout = result.stdout;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`${description} failed: ${getChildProcessErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(stdout) as OutputType;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Parsing output of ${description} failed: ${getErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
102
extensions/ql-vscode/src/codeql-cli/cli-errors.ts
Normal file
102
extensions/ql-vscode/src/codeql-cli/cli-errors.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { asError, getErrorMessage } from "../common/helpers-pure";
|
||||
|
||||
// https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/exit-codes
|
||||
const EXIT_CODE_USER_ERROR = 2;
|
||||
const EXIT_CODE_CANCELLED = 98;
|
||||
|
||||
export class ExitCodeError extends Error {
|
||||
constructor(public readonly exitCode: number | null) {
|
||||
super(`Process exited with code ${exitCode}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class CliError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly stderr: string | undefined,
|
||||
public readonly cause: Error,
|
||||
public readonly commandDescription: string,
|
||||
public readonly commandArgs: string[],
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function getCliError(
|
||||
e: unknown,
|
||||
stderr: string | undefined,
|
||||
commandDescription: string,
|
||||
commandArgs: string[],
|
||||
): CliError {
|
||||
const error = asError(e);
|
||||
|
||||
if (!(error instanceof ExitCodeError) || !stderr) {
|
||||
return formatCliErrorFallback(
|
||||
error,
|
||||
stderr,
|
||||
commandDescription,
|
||||
commandArgs,
|
||||
);
|
||||
}
|
||||
|
||||
switch (error.exitCode) {
|
||||
case EXIT_CODE_USER_ERROR: {
|
||||
// This is an error that we should try to format nicely
|
||||
const fatalErrorIndex = stderr.lastIndexOf("A fatal error occurred: ");
|
||||
if (fatalErrorIndex !== -1) {
|
||||
return new CliError(
|
||||
stderr.slice(fatalErrorIndex),
|
||||
stderr,
|
||||
error,
|
||||
commandDescription,
|
||||
commandArgs,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EXIT_CODE_CANCELLED: {
|
||||
const cancellationIndex = stderr.lastIndexOf(
|
||||
"Computation was cancelled: ",
|
||||
);
|
||||
if (cancellationIndex !== -1) {
|
||||
return new CliError(
|
||||
stderr.slice(cancellationIndex),
|
||||
stderr,
|
||||
error,
|
||||
commandDescription,
|
||||
commandArgs,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return formatCliErrorFallback(error, stderr, commandDescription, commandArgs);
|
||||
}
|
||||
|
||||
function formatCliErrorFallback(
|
||||
error: Error,
|
||||
stderr: string | undefined,
|
||||
commandDescription: string,
|
||||
commandArgs: string[],
|
||||
): CliError {
|
||||
if (stderr) {
|
||||
return new CliError(
|
||||
stderr,
|
||||
undefined,
|
||||
error,
|
||||
commandDescription,
|
||||
commandArgs,
|
||||
);
|
||||
}
|
||||
|
||||
return new CliError(
|
||||
getErrorMessage(error),
|
||||
undefined,
|
||||
error,
|
||||
commandDescription,
|
||||
commandArgs,
|
||||
);
|
||||
}
|
||||
@@ -1,24 +1,49 @@
|
||||
import * as semver from "semver";
|
||||
import { runCodeQlCliCommand } from "./cli";
|
||||
import { Logger } from "../common/logging";
|
||||
import type { SemVer } from "semver";
|
||||
import { parse } from "semver";
|
||||
import { runJsonCodeQlCliCommand } from "./cli-command";
|
||||
import type { Logger } from "../common/logging";
|
||||
import { getErrorMessage } from "../common/helpers-pure";
|
||||
|
||||
interface VersionResult {
|
||||
version: string;
|
||||
features: CliFeatures | undefined;
|
||||
}
|
||||
|
||||
export interface CliFeatures {
|
||||
featuresInVersionResult?: boolean;
|
||||
mrvaPackCreate?: boolean;
|
||||
}
|
||||
|
||||
export interface VersionAndFeatures {
|
||||
version: SemVer;
|
||||
features: CliFeatures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of a CodeQL CLI.
|
||||
*/
|
||||
export async function getCodeQlCliVersion(
|
||||
codeQlPath: string,
|
||||
logger: Logger,
|
||||
): Promise<semver.SemVer | undefined> {
|
||||
): Promise<VersionAndFeatures | undefined> {
|
||||
try {
|
||||
const output: string = await runCodeQlCliCommand(
|
||||
const output: VersionResult = await runJsonCodeQlCliCommand<VersionResult>(
|
||||
codeQlPath,
|
||||
["version"],
|
||||
["--format=terse"],
|
||||
["--format=json"],
|
||||
"Checking CodeQL version",
|
||||
logger,
|
||||
);
|
||||
return semver.parse(output.trim()) || undefined;
|
||||
|
||||
const version = parse(output.version.trim()) || undefined;
|
||||
if (version === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
version,
|
||||
features: output.features ?? {},
|
||||
};
|
||||
} catch (e) {
|
||||
// Failed to run the version command. This might happen if the cli version is _really_ old, or it is corrupted.
|
||||
// Either way, we can't determine compatibility.
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
import { EOL } from "os";
|
||||
import { spawn } from "child-process-promise";
|
||||
import * as child_process from "child_process";
|
||||
import type { ChildProcessWithoutNullStreams } from "child_process";
|
||||
import { spawn as spawnChildProcess } from "child_process";
|
||||
import { readFile } from "fs-extra";
|
||||
import { dirname, join, delimiter } from "path";
|
||||
import * as sarif from "sarif";
|
||||
import { delimiter, dirname, join } from "path";
|
||||
import type { Log } from "sarif";
|
||||
import { SemVer } from "semver";
|
||||
import { Readable } from "stream";
|
||||
import type { Readable } from "stream";
|
||||
import tk from "tree-kill";
|
||||
import { promisify } from "util";
|
||||
import { CancellationToken, Disposable, Uri } from "vscode";
|
||||
import type { CancellationToken, Disposable, Uri } from "vscode";
|
||||
|
||||
import {
|
||||
BQRSInfo,
|
||||
import type {
|
||||
BqrsInfo,
|
||||
DecodedBqrs,
|
||||
DecodedBqrsChunk,
|
||||
} from "../common/bqrs-cli-types";
|
||||
import { allowCanaryQueryServer, CliConfig } from "../config";
|
||||
import {
|
||||
DistributionProvider,
|
||||
FindDistributionResultKind,
|
||||
} from "./distribution";
|
||||
import type { CliConfig } from "../config";
|
||||
import type { DistributionProvider } from "./distribution";
|
||||
import { FindDistributionResultKind } from "./distribution";
|
||||
import {
|
||||
assertNever,
|
||||
getChildProcessErrorMessage,
|
||||
getErrorMessage,
|
||||
getErrorStack,
|
||||
} from "../common/helpers-pure";
|
||||
import { walkDirectory } from "../common/files";
|
||||
import { QueryMetadata, SortDirection } from "../common/interface-types";
|
||||
import { BaseLogger, Logger } from "../common/logging";
|
||||
import { ProgressReporter } from "../common/logging/vscode";
|
||||
import { CompilationMessage } from "../query-server/legacy-messages";
|
||||
import type { QueryMetadata } from "../common/interface-types";
|
||||
import { SortDirection } from "../common/interface-types";
|
||||
import type { BaseLogger, Logger } from "../common/logging";
|
||||
import type { ProgressReporter } from "../common/logging/vscode";
|
||||
import { sarifParser } from "../common/sarif-parser";
|
||||
import { App } from "../common/app";
|
||||
import type { App } from "../common/app";
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import { LINE_ENDINGS, splitStreamAtSeparators } from "../common/split-stream";
|
||||
import type { Position } from "../query-server/messages";
|
||||
import { LOGGING_FLAGS } from "./cli-command";
|
||||
import type { CliFeatures, VersionAndFeatures } from "./cli-version";
|
||||
import { ExitCodeError, getCliError } from "./cli-errors";
|
||||
|
||||
/**
|
||||
* The version of the SARIF format that we are using.
|
||||
@@ -46,21 +47,6 @@ const SARIF_FORMAT = "sarifv2.1.0";
|
||||
*/
|
||||
const CSV_FORMAT = "csv";
|
||||
|
||||
/**
|
||||
* Flags to pass to all cli commands.
|
||||
*/
|
||||
const LOGGING_FLAGS = ["-v", "--log-to-stderr"];
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve library-path`.
|
||||
*/
|
||||
export interface QuerySetup {
|
||||
libraryPath: string[];
|
||||
dbscheme: string;
|
||||
relativeName?: string;
|
||||
compilationCache?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve queries --format bylanguage`.
|
||||
*/
|
||||
@@ -88,7 +74,7 @@ export interface DbInfo {
|
||||
/**
|
||||
* The expected output of `codeql resolve upgrades`.
|
||||
*/
|
||||
export interface UpgradesInfo {
|
||||
interface UpgradesInfo {
|
||||
scripts: string[];
|
||||
finalDbscheme: string;
|
||||
matchesTarget?: boolean;
|
||||
@@ -102,33 +88,33 @@ export type QlpacksInfo = { [name: string]: string[] };
|
||||
/**
|
||||
* The expected output of `codeql resolve languages`.
|
||||
*/
|
||||
export type LanguagesInfo = { [name: string]: string[] };
|
||||
type LanguagesInfo = { [name: string]: string[] };
|
||||
|
||||
/** Information about an ML model, as resolved by `codeql resolve ml-models`. */
|
||||
export type MlModelInfo = {
|
||||
type MlModelInfo = {
|
||||
checksum: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
/** The expected output of `codeql resolve ml-models`. */
|
||||
export type MlModelsInfo = { models: MlModelInfo[] };
|
||||
type MlModelsInfo = { models: MlModelInfo[] };
|
||||
|
||||
/** Information about a data extension predicate, as resolved by `codeql resolve extensions`. */
|
||||
export type DataExtensionResult = {
|
||||
type DataExtensionResult = {
|
||||
predicate: string;
|
||||
file: string;
|
||||
index: number;
|
||||
};
|
||||
|
||||
/** The expected output of `codeql resolve extensions`. */
|
||||
export type ResolveExtensionsResult = {
|
||||
type ResolveExtensionsResult = {
|
||||
models: MlModelInfo[];
|
||||
data: {
|
||||
[path: string]: DataExtensionResult[];
|
||||
};
|
||||
};
|
||||
|
||||
export type GenerateExtensiblePredicateMetadataResult = {
|
||||
type GenerateExtensiblePredicateMetadataResult = {
|
||||
// There are other properties in this object, but they are
|
||||
// not relevant for its use in the extension, so we omit them.
|
||||
extensible_predicates: Array<{
|
||||
@@ -137,10 +123,20 @@ export type GenerateExtensiblePredicateMetadataResult = {
|
||||
}>;
|
||||
};
|
||||
|
||||
type PackDownloadResult = {
|
||||
// There are other properties in this object, but they are
|
||||
// not relevant for its use in the extension, so we omit them.
|
||||
packs: Array<{
|
||||
name: string;
|
||||
version: string;
|
||||
}>;
|
||||
packDir: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve qlref`.
|
||||
*/
|
||||
export type QlrefInfo = { resolvedPath: string };
|
||||
type QlrefInfo = { resolvedPath: string };
|
||||
|
||||
// `codeql bqrs interpret` requires both of these to be present or
|
||||
// both absent.
|
||||
@@ -152,12 +148,30 @@ export interface SourceInfo {
|
||||
/**
|
||||
* The expected output of `codeql resolve queries`.
|
||||
*/
|
||||
export type ResolvedQueries = string[];
|
||||
type ResolvedQueries = string[];
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve tests`.
|
||||
*/
|
||||
export type ResolvedTests = string[];
|
||||
type ResolvedTests = string[];
|
||||
|
||||
/**
|
||||
* A compilation message for a test message (either an error or a warning)
|
||||
*/
|
||||
interface CompilationMessage {
|
||||
/**
|
||||
* The text of the message
|
||||
*/
|
||||
message: string;
|
||||
/**
|
||||
* The source position associated with the message
|
||||
*/
|
||||
position: Position;
|
||||
/**
|
||||
* The severity of the message
|
||||
*/
|
||||
severity: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired by `codeql test run`.
|
||||
@@ -187,11 +201,13 @@ interface BqrsDecodeOptions {
|
||||
entities?: string[];
|
||||
}
|
||||
|
||||
export type OnLineCallback = (
|
||||
type OnLineCallback = (
|
||||
line: string,
|
||||
) => Promise<string | undefined> | string | undefined;
|
||||
|
||||
type VersionChangedListener = (newVersion: SemVer | undefined) => void;
|
||||
type VersionChangedListener = (
|
||||
newVersionAndFeatures: VersionAndFeatures | undefined,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* This class manages a cli server started by `codeql execute cli-server` to
|
||||
@@ -201,7 +217,7 @@ type VersionChangedListener = (newVersion: SemVer | undefined) => void;
|
||||
*/
|
||||
export class CodeQLCliServer implements Disposable {
|
||||
/** The process for the cli server, or undefined if one doesn't exist yet */
|
||||
process?: child_process.ChildProcessWithoutNullStreams;
|
||||
process?: ChildProcessWithoutNullStreams;
|
||||
/** Queue of future commands*/
|
||||
commandQueue: Array<() => void>;
|
||||
/** Whether a command is running */
|
||||
@@ -209,8 +225,8 @@ export class CodeQLCliServer implements Disposable {
|
||||
/** A buffer with a single null byte. */
|
||||
nullBuffer: Buffer;
|
||||
|
||||
/** Version of current cli, lazily computed by the `getVersion()` method */
|
||||
private _version: SemVer | undefined;
|
||||
/** Version of current cli and its supported features, lazily computed by the `getVersion()` method */
|
||||
private _versionAndFeatures: VersionAndFeatures | undefined;
|
||||
|
||||
private _versionChangedListeners: VersionChangedListener[] = [];
|
||||
|
||||
@@ -241,15 +257,11 @@ export class CodeQLCliServer implements Disposable {
|
||||
if (this.distributionProvider.onDidChangeDistribution) {
|
||||
this.distributionProvider.onDidChangeDistribution(() => {
|
||||
this.restartCliServer();
|
||||
this._version = undefined;
|
||||
this._supportedLanguages = undefined;
|
||||
});
|
||||
}
|
||||
if (this.cliConfig.onDidChangeConfiguration) {
|
||||
this.cliConfig.onDidChangeConfiguration(() => {
|
||||
this.restartCliServer();
|
||||
this._version = undefined;
|
||||
this._supportedLanguages = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -290,6 +302,8 @@ export class CodeQLCliServer implements Disposable {
|
||||
const callback = (): void => {
|
||||
try {
|
||||
this.killProcessIfRunning();
|
||||
this._versionAndFeatures = undefined;
|
||||
this._supportedLanguages = undefined;
|
||||
} finally {
|
||||
this.runNext();
|
||||
}
|
||||
@@ -319,7 +333,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
/**
|
||||
* Launch the cli server
|
||||
*/
|
||||
private async launchProcess(): Promise<child_process.ChildProcessWithoutNullStreams> {
|
||||
private async launchProcess(): Promise<ChildProcessWithoutNullStreams> {
|
||||
const codeQlPath = await this.getCodeQlPath();
|
||||
const args = [];
|
||||
if (shouldDebugCliServer()) {
|
||||
@@ -407,7 +421,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
stderrBuffers.push(newData);
|
||||
});
|
||||
// Listen for process exit.
|
||||
process.addListener("close", (code) => reject(code));
|
||||
process.addListener("close", (code) =>
|
||||
reject(new ExitCodeError(code)),
|
||||
);
|
||||
// Write the command followed by a null terminator.
|
||||
process.stdin.write(JSON.stringify(args), "utf8");
|
||||
process.stdin.write(this.nullBuffer);
|
||||
@@ -423,19 +439,18 @@ export class CodeQLCliServer implements Disposable {
|
||||
} catch (err) {
|
||||
// Kill the process if it isn't already dead.
|
||||
this.killProcessIfRunning();
|
||||
|
||||
// Report the error (if there is a stderr then use that otherwise just report the error code or nodejs error)
|
||||
const newError =
|
||||
stderrBuffers.length === 0
|
||||
? new Error(
|
||||
`${description} failed with args:${EOL} ${argsString}${EOL}${err}`,
|
||||
)
|
||||
: new Error(
|
||||
`${description} failed with args:${EOL} ${argsString}${EOL}${Buffer.concat(
|
||||
stderrBuffers,
|
||||
).toString("utf8")}`,
|
||||
);
|
||||
newError.stack += getErrorStack(err);
|
||||
throw newError;
|
||||
const cliError = getCliError(
|
||||
err,
|
||||
stderrBuffers.length > 0
|
||||
? Buffer.concat(stderrBuffers).toString("utf8")
|
||||
: undefined,
|
||||
description,
|
||||
args,
|
||||
);
|
||||
cliError.stack += getErrorStack(err);
|
||||
throw cliError;
|
||||
} finally {
|
||||
if (!silent) {
|
||||
void this.logger.log(Buffer.concat(stderrBuffers).toString("utf8"));
|
||||
@@ -637,9 +652,10 @@ export class CodeQLCliServer implements Disposable {
|
||||
} = {},
|
||||
): Promise<OutputType> {
|
||||
let args: string[] = [];
|
||||
if (addFormat)
|
||||
if (addFormat) {
|
||||
// Add format argument first, in case commandArgs contains positional parameters.
|
||||
args = args.concat(["--format", "json"]);
|
||||
}
|
||||
args = args.concat(commandArgs);
|
||||
const result = await this.runCodeQlCliCommand(command, args, description, {
|
||||
progressReporter,
|
||||
@@ -721,29 +737,6 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the library path and dbscheme for a query.
|
||||
* @param workspaces The current open workspaces
|
||||
* @param queryPath The path to the query
|
||||
*/
|
||||
async resolveLibraryPath(
|
||||
workspaces: string[],
|
||||
queryPath: string,
|
||||
silent = false,
|
||||
): Promise<QuerySetup> {
|
||||
const subcommandArgs = [
|
||||
"--query",
|
||||
queryPath,
|
||||
...this.getAdditionalPacksArg(workspaces),
|
||||
];
|
||||
return await this.runJsonCodeQlCliCommand<QuerySetup>(
|
||||
["resolve", "library-path"],
|
||||
subcommandArgs,
|
||||
"Resolving library paths",
|
||||
{ silent },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the language for a query.
|
||||
* @param queryUri The URI of the query
|
||||
@@ -797,6 +790,11 @@ export class CodeQLCliServer implements Disposable {
|
||||
["resolve", "tests", "--strict-test-discovery"],
|
||||
subcommandArgs,
|
||||
"Resolving tests",
|
||||
{
|
||||
// This happens as part of a background process, so we don't want to
|
||||
// spam the log with messages.
|
||||
silent: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -881,10 +879,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
additionalPacks: string[],
|
||||
queryPath: string,
|
||||
): Promise<MlModelsInfo> {
|
||||
const args = (await this.cliConstraints.supportsPreciseResolveMlModels())
|
||||
? // use the dirname of the path so that we can handle query libraries
|
||||
[...this.getAdditionalPacksArg(additionalPacks), dirname(queryPath)]
|
||||
: this.getAdditionalPacksArg(additionalPacks);
|
||||
const args =
|
||||
// use the dirname of the path so that we can handle query libraries
|
||||
[...this.getAdditionalPacksArg(additionalPacks), dirname(queryPath)];
|
||||
return await this.runJsonCodeQlCliCommand<MlModelsInfo>(
|
||||
["resolve", "ml-models"],
|
||||
args,
|
||||
@@ -925,11 +922,11 @@ export class CodeQLCliServer implements Disposable {
|
||||
* @param bqrsPath The path to the bqrs.
|
||||
* @param pageSize The page size to precompute offsets into the binary file for.
|
||||
*/
|
||||
async bqrsInfo(bqrsPath: string, pageSize?: number): Promise<BQRSInfo> {
|
||||
async bqrsInfo(bqrsPath: string, pageSize?: number): Promise<BqrsInfo> {
|
||||
const subcommandArgs = (
|
||||
pageSize ? ["--paginate-rows", pageSize.toString()] : []
|
||||
).concat(bqrsPath);
|
||||
return await this.runJsonCodeQlCliCommand<BQRSInfo>(
|
||||
return await this.runJsonCodeQlCliCommand<BqrsInfo>(
|
||||
["bqrs", "info"],
|
||||
subcommandArgs,
|
||||
"Reading bqrs header",
|
||||
@@ -942,8 +939,12 @@ export class CodeQLCliServer implements Disposable {
|
||||
name?: string,
|
||||
): Promise<string> {
|
||||
const subcommandArgs = [];
|
||||
if (target) subcommandArgs.push("--target", target);
|
||||
if (name) subcommandArgs.push("--name", name);
|
||||
if (target) {
|
||||
subcommandArgs.push("--target", target);
|
||||
}
|
||||
if (name) {
|
||||
subcommandArgs.push("--name", name);
|
||||
}
|
||||
subcommandArgs.push(archivePath);
|
||||
|
||||
return await this.runCodeQlCliCommand(
|
||||
@@ -964,7 +965,9 @@ export class CodeQLCliServer implements Disposable {
|
||||
outputDirectory?: string,
|
||||
): Promise<string> {
|
||||
const subcommandArgs = ["--format=markdown"];
|
||||
if (outputDirectory) subcommandArgs.push("--output", outputDirectory);
|
||||
if (outputDirectory) {
|
||||
subcommandArgs.push("--output", outputDirectory);
|
||||
}
|
||||
subcommandArgs.push(pathToQhelp);
|
||||
|
||||
return await this.runCodeQlCliCommand(
|
||||
@@ -988,9 +991,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
const subcommandArgs = [
|
||||
"--format=text",
|
||||
`--end-summary=${endSummaryPath}`,
|
||||
...((await this.cliConstraints.supportsSourceMap())
|
||||
? ["--sourcemap"]
|
||||
: []),
|
||||
"--sourcemap",
|
||||
inputPath,
|
||||
outputPath,
|
||||
];
|
||||
@@ -1099,7 +1100,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
interpretedResultsPath: string,
|
||||
sourceInfo?: SourceInfo,
|
||||
args?: string[],
|
||||
): Promise<sarif.Log> {
|
||||
): Promise<Log> {
|
||||
const additionalArgs = [
|
||||
// TODO: This flag means that we don't group interpreted results
|
||||
// by primary location. We may want to revisit whether we call
|
||||
@@ -1383,13 +1384,10 @@ export class CodeQLCliServer implements Disposable {
|
||||
async packAdd(dir: string, queryLanguage: QueryLanguage) {
|
||||
const args = ["--dir", dir];
|
||||
args.push(`codeql/${queryLanguage}-all`);
|
||||
return this.runJsonCodeQlCliCommandWithAuthentication(
|
||||
return this.runCodeQlCliCommand(
|
||||
["pack", "add"],
|
||||
args,
|
||||
`Adding and installing ${queryLanguage} pack dependency.`,
|
||||
{
|
||||
addFormat: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1397,7 +1395,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
* Downloads a specified pack.
|
||||
* @param packs The `<package-scope/name[@version]>` of the packs to download.
|
||||
*/
|
||||
async packDownload(packs: string[]) {
|
||||
async packDownload(packs: string[]): Promise<PackDownloadResult> {
|
||||
return this.runJsonCodeQlCliCommandWithAuthentication(
|
||||
["pack", "download"],
|
||||
packs,
|
||||
@@ -1431,16 +1429,28 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a CodeQL pack and bundle it into a single file.
|
||||
*
|
||||
* @param sourcePackDir The directory of the input CodeQL pack.
|
||||
* @param workspaceFolders The workspace folders to search for additional packs.
|
||||
* @param outputBundleFile The path to the output bundle file.
|
||||
* @param outputPackDir The directory to contain the unbundled output pack.
|
||||
* @param moreOptions Additional options to be passed to `codeql pack bundle`.
|
||||
*/
|
||||
async packBundle(
|
||||
dir: string,
|
||||
sourcePackDir: string,
|
||||
workspaceFolders: string[],
|
||||
outputPath: string,
|
||||
outputBundleFile: string,
|
||||
outputPackDir: string,
|
||||
moreOptions: string[],
|
||||
): Promise<void> {
|
||||
const args = [
|
||||
"-o",
|
||||
outputPath,
|
||||
dir,
|
||||
outputBundleFile,
|
||||
sourcePackDir,
|
||||
"--pack-path",
|
||||
outputPackDir,
|
||||
...moreOptions,
|
||||
...this.getAdditionalPacksArg(workspaceFolders),
|
||||
];
|
||||
@@ -1495,27 +1505,35 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
public async getVersion() {
|
||||
if (!this._version) {
|
||||
public async getVersion(): Promise<SemVer> {
|
||||
return (await this.getVersionAndFeatures()).version;
|
||||
}
|
||||
|
||||
public async getFeatures(): Promise<CliFeatures> {
|
||||
return (await this.getVersionAndFeatures()).features;
|
||||
}
|
||||
|
||||
private async getVersionAndFeatures(): Promise<VersionAndFeatures> {
|
||||
if (!this._versionAndFeatures) {
|
||||
try {
|
||||
const newVersion = await this.refreshVersion();
|
||||
this._version = newVersion;
|
||||
const newVersionAndFeatures = await this.refreshVersion();
|
||||
this._versionAndFeatures = newVersionAndFeatures;
|
||||
this._versionChangedListeners.forEach((listener) =>
|
||||
listener(newVersion),
|
||||
listener(newVersionAndFeatures),
|
||||
);
|
||||
|
||||
// this._version is only undefined upon config change, so we reset CLI-based context key only when necessary.
|
||||
await this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeql.supportsQuickEvalCount",
|
||||
newVersion.compare(
|
||||
newVersionAndFeatures.version.compare(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_QUICK_EVAL_COUNT,
|
||||
) >= 0,
|
||||
);
|
||||
await this.app.commands.execute(
|
||||
"setContext",
|
||||
"codeql.supportsTrimCache",
|
||||
newVersion.compare(
|
||||
newVersionAndFeatures.version.compare(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_TRIM_CACHE,
|
||||
) >= 0,
|
||||
);
|
||||
@@ -1526,23 +1544,23 @@ export class CodeQLCliServer implements Disposable {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return this._version;
|
||||
return this._versionAndFeatures;
|
||||
}
|
||||
|
||||
public addVersionChangedListener(listener: VersionChangedListener) {
|
||||
if (this._version) {
|
||||
listener(this._version);
|
||||
if (this._versionAndFeatures) {
|
||||
listener(this._versionAndFeatures);
|
||||
}
|
||||
this._versionChangedListeners.push(listener);
|
||||
}
|
||||
|
||||
private async refreshVersion() {
|
||||
private async refreshVersion(): Promise<VersionAndFeatures> {
|
||||
const distribution = await this.distributionProvider.getDistribution();
|
||||
switch (distribution.kind) {
|
||||
case FindDistributionResultKind.CompatibleDistribution:
|
||||
|
||||
// eslint-disable-next-line no-fallthrough -- Intentional fallthrough
|
||||
case FindDistributionResultKind.IncompatibleDistribution:
|
||||
return distribution.version;
|
||||
return distribution.versionAndFeatures;
|
||||
|
||||
default:
|
||||
// We should not get here because if no distributions are available, then
|
||||
@@ -1587,10 +1605,10 @@ export function spawnServer(
|
||||
command: string[],
|
||||
commandArgs: string[],
|
||||
logger: Logger,
|
||||
stderrListener: (data: any) => void,
|
||||
stdoutListener?: (data: any) => void,
|
||||
stderrListener: (data: string | Buffer) => void,
|
||||
stdoutListener?: (data: string | Buffer) => void,
|
||||
progressReporter?: ProgressReporter,
|
||||
): child_process.ChildProcessWithoutNullStreams {
|
||||
): ChildProcessWithoutNullStreams {
|
||||
// Enable verbose logging.
|
||||
const args = command.concat(commandArgs).concat(LOGGING_FLAGS);
|
||||
|
||||
@@ -1601,29 +1619,32 @@ export function spawnServer(
|
||||
progressReporter.report({ message: `Starting ${name}` });
|
||||
}
|
||||
void logger.log(`Starting ${name} using CodeQL CLI: ${base} ${argsString}`);
|
||||
const child = child_process.spawn(base, args);
|
||||
const child = spawnChildProcess(base, args);
|
||||
if (!child || !child.pid) {
|
||||
throw new Error(
|
||||
`Failed to start ${name} using command ${base} ${argsString}.`,
|
||||
);
|
||||
}
|
||||
|
||||
let lastStdout: any = undefined;
|
||||
let lastStdout: string | Buffer | undefined = undefined;
|
||||
child.stdout!.on("data", (data) => {
|
||||
lastStdout = data;
|
||||
});
|
||||
// Set up event listeners.
|
||||
child.on("close", async (code, signal) => {
|
||||
if (code !== null)
|
||||
if (code !== null) {
|
||||
void logger.log(`Child process exited with code ${code}`);
|
||||
if (signal)
|
||||
}
|
||||
if (signal) {
|
||||
void logger.log(
|
||||
`Child process exited due to receipt of signal ${signal}`,
|
||||
);
|
||||
}
|
||||
// If the process exited abnormally, log the last stdout message,
|
||||
// It may be from the jvm.
|
||||
if (code !== 0 && lastStdout !== undefined)
|
||||
if (code !== 0 && lastStdout !== undefined) {
|
||||
void logger.log(`Last stdout was "${lastStdout.toString()}"`);
|
||||
}
|
||||
});
|
||||
child.stderr!.on("data", stderrListener);
|
||||
if (stdoutListener !== undefined) {
|
||||
@@ -1637,45 +1658,6 @@ export function spawnServer(
|
||||
return child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a CodeQL CLI command without invoking the CLI server, returning the output as a string.
|
||||
* @param codeQlPath The path to the CLI.
|
||||
* @param command The `codeql` command to be run, provided as an array of command/subcommand names.
|
||||
* @param commandArgs The arguments to pass to the `codeql` command.
|
||||
* @param description Description of the action being run, to be shown in log and error messages.
|
||||
* @param logger Logger to write command log messages, e.g. to an output channel.
|
||||
* @param progressReporter Used to output progress messages, e.g. to the status bar.
|
||||
* @returns The contents of the command's stdout, if the command succeeded.
|
||||
*/
|
||||
export async function runCodeQlCliCommand(
|
||||
codeQlPath: string,
|
||||
command: string[],
|
||||
commandArgs: string[],
|
||||
description: string,
|
||||
logger: Logger,
|
||||
progressReporter?: ProgressReporter,
|
||||
): Promise<string> {
|
||||
// Add logging arguments first, in case commandArgs contains positional parameters.
|
||||
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
|
||||
const argsString = args.join(" ");
|
||||
try {
|
||||
if (progressReporter !== undefined) {
|
||||
progressReporter.report({ message: description });
|
||||
}
|
||||
void logger.log(
|
||||
`${description} using CodeQL CLI: ${codeQlPath} ${argsString}...`,
|
||||
);
|
||||
const result = await promisify(child_process.execFile)(codeQlPath, args);
|
||||
void logger.log(result.stderr);
|
||||
void logger.log("CLI command succeeded.");
|
||||
return result.stdout;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`${description} failed: ${getChildProcessErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a text stream to a `Logger` interface.
|
||||
* @param stream The stream to log.
|
||||
@@ -1697,7 +1679,7 @@ function isEnvTrue(name: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldDebugIdeServer() {
|
||||
export function shouldDebugLanguageServer() {
|
||||
return isEnvTrue("IDE_SERVER_JAVA_DEBUG");
|
||||
}
|
||||
|
||||
@@ -1705,48 +1687,14 @@ export function shouldDebugQueryServer() {
|
||||
return isEnvTrue("QUERY_SERVER_JAVA_DEBUG");
|
||||
}
|
||||
|
||||
export function shouldDebugCliServer() {
|
||||
function shouldDebugCliServer() {
|
||||
return isEnvTrue("CLI_SERVER_JAVA_DEBUG");
|
||||
}
|
||||
|
||||
export class CliVersionConstraint {
|
||||
// The oldest version of the CLI that we support. This is used to determine
|
||||
// whether to show a warning about the CLI being too old on startup.
|
||||
public static OLDEST_SUPPORTED_CLI_VERSION = new SemVer("2.9.4");
|
||||
|
||||
/**
|
||||
* CLI version where building QLX packs for remote queries is supported.
|
||||
* (The options were _accepted_ by a few earlier versions, but only from
|
||||
* 2.11.3 will it actually use the existing compilation cache correctly).
|
||||
*/
|
||||
public static CLI_VERSION_QLX_REMOTE = new SemVer("2.11.3");
|
||||
|
||||
/**
|
||||
* CLI version where the `resolve ml-models` subcommand was enhanced to work with packaging.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_PRECISE_RESOLVE_ML_MODELS = new SemVer(
|
||||
"2.10.0",
|
||||
);
|
||||
|
||||
/**
|
||||
* CLI version where the `resolve extensions` subcommand exists.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_RESOLVE_EXTENSIONS = new SemVer("2.10.2");
|
||||
|
||||
/**
|
||||
* CLI version that supports the `--sourcemap` option for log generation.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_SOURCEMAP = new SemVer("2.10.3");
|
||||
|
||||
/**
|
||||
* CLI version that supports the new query server.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_NEW_QUERY_SERVER = new SemVer("2.11.1");
|
||||
|
||||
/**
|
||||
* CLI version that supports `${workspace}` references in qlpack.yml files.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_WORKSPACE_RFERENCES = new SemVer("2.11.3");
|
||||
public static OLDEST_SUPPORTED_CLI_VERSION = new SemVer("2.11.6");
|
||||
|
||||
/**
|
||||
* CLI version that supports the `--kind` option for the `resolve qlpacks` command.
|
||||
@@ -1788,6 +1736,15 @@ export class CliVersionConstraint {
|
||||
*/
|
||||
public static CLI_VERSION_WITH_TRIM_CACHE = new SemVer("2.15.1");
|
||||
|
||||
public static CLI_VERSION_WITHOUT_MRVA_EXTENSIBLE_PREDICATE_HACK = new SemVer(
|
||||
"2.16.1",
|
||||
);
|
||||
|
||||
/**
|
||||
* CLI version where there is support for multiple queries on the pack create command.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_MULTI_QUERY_PACK_CREATE = new SemVer("2.16.1");
|
||||
|
||||
constructor(private readonly cli: CodeQLCliServer) {
|
||||
/**/
|
||||
}
|
||||
@@ -1796,50 +1753,6 @@ export class CliVersionConstraint {
|
||||
return (await this.cli.getVersion()).compare(v) >= 0;
|
||||
}
|
||||
|
||||
async supportsQlxRemote() {
|
||||
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_QLX_REMOTE);
|
||||
}
|
||||
|
||||
async supportsPreciseResolveMlModels() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_PRECISE_RESOLVE_ML_MODELS,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsResolveExtensions() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsSourceMap() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_SOURCEMAP,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsNewQueryServer() {
|
||||
// This allows users to explicitly opt-out of the new query server.
|
||||
return (
|
||||
allowCanaryQueryServer() &&
|
||||
this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_NEW_QUERY_SERVER,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async supportsNewQueryServerForTests() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_NEW_QUERY_SERVER,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsWorkspaceReferences() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_WORKSPACE_RFERENCES,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsQlpacksKind() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND,
|
||||
@@ -1873,4 +1786,21 @@ export class CliVersionConstraint {
|
||||
CliVersionConstraint.CLI_VERSION_WITH_EXTENSIBLE_PREDICATE_METADATA,
|
||||
);
|
||||
}
|
||||
|
||||
async preservesExtensiblePredicatesInMrvaPack() {
|
||||
// Negated, because we _stopped_ preserving these in 2.16.1.
|
||||
return !(await this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITHOUT_MRVA_EXTENSIBLE_PREDICATE_HACK,
|
||||
));
|
||||
}
|
||||
|
||||
async supportsPackCreateWithMultipleQueries() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_MULTI_QUERY_PACK_CREATE,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsMrvaPackCreate(): Promise<boolean> {
|
||||
return (await this.cli.getFeatures()).mrvaPackCreate === true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import * as fetch from "node-fetch";
|
||||
import { pathExists, mkdtemp, createWriteStream, remove } from "fs-extra";
|
||||
import { createWriteStream, mkdtemp, pathExists, remove } from "fs-extra";
|
||||
import { tmpdir } from "os";
|
||||
import { delimiter, dirname, join } from "path";
|
||||
import * as semver from "semver";
|
||||
import { URL } from "url";
|
||||
import { ExtensionContext, Event } from "vscode";
|
||||
import { DistributionConfig } from "../config";
|
||||
import { Range, satisfies } from "semver";
|
||||
import type { Event, ExtensionContext } from "vscode";
|
||||
import type { DistributionConfig } from "../config";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
import type { VersionAndFeatures } from "./cli-version";
|
||||
import { getCodeQlCliVersion } from "./cli-version";
|
||||
import {
|
||||
ProgressCallback,
|
||||
reportStreamProgress,
|
||||
} from "../common/vscode/progress";
|
||||
import type { ProgressCallback } from "../common/vscode/progress";
|
||||
import { reportStreamProgress } from "../common/vscode/progress";
|
||||
import {
|
||||
codeQlLauncherName,
|
||||
deprecatedCodeQlLauncherName,
|
||||
extractZipArchive,
|
||||
getRequiredAssetName,
|
||||
} from "../common/distribution";
|
||||
import {
|
||||
@@ -26,6 +22,10 @@ import {
|
||||
showAndLogErrorMessage,
|
||||
showAndLogWarningMessage,
|
||||
} from "../common/logging";
|
||||
import { unzipToDirectoryConcurrently } from "../common/unzip-concurrently";
|
||||
import { reportUnzipProgress } from "../common/vscode/unzip-progress";
|
||||
import type { Release } from "./distribution/release";
|
||||
import { ReleasesApiConsumer } from "./distribution/releases-api-consumer";
|
||||
|
||||
/**
|
||||
* distribution.ts
|
||||
@@ -35,28 +35,21 @@ import {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default value for the owner name of the extension-managed distribution on GitHub.
|
||||
*
|
||||
* We set the default here rather than as a default config value so that this default is invoked
|
||||
* upon blanking the setting.
|
||||
* Repository name with owner of the stable version of the extension-managed distribution on GitHub.
|
||||
*/
|
||||
const DEFAULT_DISTRIBUTION_OWNER_NAME = "github";
|
||||
const STABLE_DISTRIBUTION_REPOSITORY_NWO = "github/codeql-cli-binaries";
|
||||
|
||||
/**
|
||||
* Default value for the repository name of the extension-managed distribution on GitHub.
|
||||
*
|
||||
* We set the default here rather than as a default config value so that this default is invoked
|
||||
* upon blanking the setting.
|
||||
* Repository name with owner of the nightly version of the extension-managed distribution on GitHub.
|
||||
*/
|
||||
const DEFAULT_DISTRIBUTION_REPOSITORY_NAME = "codeql-cli-binaries";
|
||||
const NIGHTLY_DISTRIBUTION_REPOSITORY_NWO = "dsp-testing/codeql-cli-nightlies";
|
||||
|
||||
/**
|
||||
* Range of versions of the CLI that are compatible with the extension.
|
||||
*
|
||||
* This applies to both extension-managed and CLI distributions.
|
||||
*/
|
||||
export const DEFAULT_DISTRIBUTION_VERSION_RANGE: semver.Range =
|
||||
new semver.Range("2.x");
|
||||
export const DEFAULT_DISTRIBUTION_VERSION_RANGE: Range = new Range("2.x");
|
||||
|
||||
export interface DistributionProvider {
|
||||
getCodeQlPathWithoutVersionCheck(): Promise<string | undefined>;
|
||||
@@ -67,7 +60,7 @@ export interface DistributionProvider {
|
||||
export class DistributionManager implements DistributionProvider {
|
||||
constructor(
|
||||
public readonly config: DistributionConfig,
|
||||
private readonly versionRange: semver.Range,
|
||||
private readonly versionRange: Range,
|
||||
extensionContext: ExtensionContext,
|
||||
) {
|
||||
this._onDidChangeDistribution = config.onDidChangeConfiguration;
|
||||
@@ -95,11 +88,11 @@ export class DistributionManager implements DistributionProvider {
|
||||
kind: FindDistributionResultKind.NoDistribution,
|
||||
};
|
||||
}
|
||||
const version = await getCodeQlCliVersion(
|
||||
const versionAndFeatures = await getCodeQlCliVersion(
|
||||
distribution.codeQlPath,
|
||||
extLogger,
|
||||
);
|
||||
if (version === undefined) {
|
||||
if (versionAndFeatures === undefined) {
|
||||
return {
|
||||
distribution,
|
||||
kind: FindDistributionResultKind.UnknownCompatibilityDistribution,
|
||||
@@ -126,17 +119,21 @@ export class DistributionManager implements DistributionProvider {
|
||||
distribution.kind !== DistributionKind.ExtensionManaged ||
|
||||
this.config.includePrerelease;
|
||||
|
||||
if (!semver.satisfies(version, this.versionRange, { includePrerelease })) {
|
||||
if (
|
||||
!satisfies(versionAndFeatures.version, this.versionRange, {
|
||||
includePrerelease,
|
||||
})
|
||||
) {
|
||||
return {
|
||||
distribution,
|
||||
kind: FindDistributionResultKind.IncompatibleDistribution,
|
||||
version,
|
||||
versionAndFeatures,
|
||||
};
|
||||
}
|
||||
return {
|
||||
distribution,
|
||||
kind: FindDistributionResultKind.CompatibleDistribution,
|
||||
version,
|
||||
versionAndFeatures,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -195,9 +192,8 @@ export class DistributionManager implements DistributionProvider {
|
||||
|
||||
if (process.env.PATH) {
|
||||
for (const searchDirectory of process.env.PATH.split(delimiter)) {
|
||||
const expectedLauncherPath = await getExecutableFromDirectory(
|
||||
searchDirectory,
|
||||
);
|
||||
const expectedLauncherPath =
|
||||
await getExecutableFromDirectory(searchDirectory);
|
||||
if (expectedLauncherPath) {
|
||||
return {
|
||||
codeQlPath: expectedLauncherPath,
|
||||
@@ -284,7 +280,7 @@ export class DistributionManager implements DistributionProvider {
|
||||
class ExtensionSpecificDistributionManager {
|
||||
constructor(
|
||||
private readonly config: DistributionConfig,
|
||||
private readonly versionRange: semver.Range,
|
||||
private readonly versionRange: Range,
|
||||
private readonly extensionContext: ExtensionContext,
|
||||
) {
|
||||
/**/
|
||||
@@ -421,7 +417,16 @@ class ExtensionSpecificDistributionManager {
|
||||
void extLogger.log(
|
||||
`Extracting CodeQL CLI to ${this.getDistributionStoragePath()}`,
|
||||
);
|
||||
await extractZipArchive(archivePath, this.getDistributionStoragePath());
|
||||
await unzipToDirectoryConcurrently(
|
||||
archivePath,
|
||||
this.getDistributionStoragePath(),
|
||||
progressCallback
|
||||
? reportUnzipProgress(
|
||||
`Extracting CodeQL CLI ${release.name}…`,
|
||||
progressCallback,
|
||||
)
|
||||
: undefined,
|
||||
);
|
||||
} finally {
|
||||
await remove(tmpDirectory);
|
||||
}
|
||||
@@ -444,9 +449,18 @@ class ExtensionSpecificDistributionManager {
|
||||
void extLogger.log(
|
||||
`Searching for latest release including ${requiredAssetName}.`,
|
||||
);
|
||||
|
||||
const versionRange = this.usingNightlyReleases
|
||||
? undefined
|
||||
: this.versionRange;
|
||||
const orderBySemver = !this.usingNightlyReleases;
|
||||
const includePrerelease =
|
||||
this.usingNightlyReleases || this.config.includePrerelease;
|
||||
|
||||
return this.createReleasesApiConsumer().getLatestRelease(
|
||||
this.versionRange,
|
||||
this.config.includePrerelease,
|
||||
versionRange,
|
||||
orderBySemver,
|
||||
includePrerelease,
|
||||
(release) => {
|
||||
// v2.12.3 was released with a bug that causes the extension to fail
|
||||
// so we force the extension to ignore it.
|
||||
@@ -476,19 +490,26 @@ class ExtensionSpecificDistributionManager {
|
||||
}
|
||||
|
||||
private createReleasesApiConsumer(): ReleasesApiConsumer {
|
||||
const ownerName = this.config.ownerName
|
||||
? this.config.ownerName
|
||||
: DEFAULT_DISTRIBUTION_OWNER_NAME;
|
||||
const repositoryName = this.config.repositoryName
|
||||
? this.config.repositoryName
|
||||
: DEFAULT_DISTRIBUTION_REPOSITORY_NAME;
|
||||
return new ReleasesApiConsumer(
|
||||
ownerName,
|
||||
repositoryName,
|
||||
this.distributionRepositoryNwo,
|
||||
this.config.personalAccessToken,
|
||||
);
|
||||
}
|
||||
|
||||
private get distributionRepositoryNwo(): string {
|
||||
if (this.config.channel === "nightly") {
|
||||
return NIGHTLY_DISTRIBUTION_REPOSITORY_NWO;
|
||||
} else {
|
||||
return STABLE_DISTRIBUTION_REPOSITORY_NWO;
|
||||
}
|
||||
}
|
||||
|
||||
private get usingNightlyReleases(): boolean {
|
||||
return (
|
||||
this.distributionRepositoryNwo === NIGHTLY_DISTRIBUTION_REPOSITORY_NWO
|
||||
);
|
||||
}
|
||||
|
||||
private async bumpDistributionFolderIndex(): Promise<void> {
|
||||
const index = this.extensionContext.globalState.get(
|
||||
ExtensionSpecificDistributionManager._currentDistributionFolderIndexStateKey,
|
||||
@@ -543,171 +564,6 @@ class ExtensionSpecificDistributionManager {
|
||||
private static readonly _codeQlExtractedFolderName = "codeql";
|
||||
}
|
||||
|
||||
export class ReleasesApiConsumer {
|
||||
constructor(
|
||||
ownerName: string,
|
||||
repoName: string,
|
||||
personalAccessToken?: string,
|
||||
) {
|
||||
// Specify version of the GitHub API
|
||||
this._defaultHeaders["accept"] = "application/vnd.github.v3+json";
|
||||
|
||||
if (personalAccessToken) {
|
||||
this._defaultHeaders["authorization"] = `token ${personalAccessToken}`;
|
||||
}
|
||||
|
||||
this._ownerName = ownerName;
|
||||
this._repoName = repoName;
|
||||
}
|
||||
|
||||
public async getLatestRelease(
|
||||
versionRange: semver.Range,
|
||||
includePrerelease = false,
|
||||
additionalCompatibilityCheck?: (release: GithubRelease) => boolean,
|
||||
): Promise<Release> {
|
||||
const apiPath = `/repos/${this._ownerName}/${this._repoName}/releases`;
|
||||
const allReleases: GithubRelease[] = await (
|
||||
await this.makeApiCall(apiPath)
|
||||
).json();
|
||||
const compatibleReleases = allReleases.filter((release) => {
|
||||
if (release.prerelease && !includePrerelease) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const version = semver.parse(release.tag_name);
|
||||
if (
|
||||
version === null ||
|
||||
!semver.satisfies(version, versionRange, { includePrerelease })
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
!additionalCompatibilityCheck || additionalCompatibilityCheck(release)
|
||||
);
|
||||
});
|
||||
// Tag names must all be parsable to semvers due to the previous filtering step.
|
||||
const latestRelease = compatibleReleases.sort((a, b) => {
|
||||
const versionComparison = semver.compare(
|
||||
semver.parse(b.tag_name)!,
|
||||
semver.parse(a.tag_name)!,
|
||||
);
|
||||
if (versionComparison !== 0) {
|
||||
return versionComparison;
|
||||
}
|
||||
return b.created_at.localeCompare(a.created_at, "en-US");
|
||||
})[0];
|
||||
if (latestRelease === undefined) {
|
||||
throw new Error(
|
||||
"No compatible CodeQL CLI releases were found. " +
|
||||
"Please check that the CodeQL extension is up to date.",
|
||||
);
|
||||
}
|
||||
const assets: ReleaseAsset[] = latestRelease.assets.map((asset) => {
|
||||
return {
|
||||
id: asset.id,
|
||||
name: asset.name,
|
||||
size: asset.size,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
assets,
|
||||
createdAt: latestRelease.created_at,
|
||||
id: latestRelease.id,
|
||||
name: latestRelease.name,
|
||||
};
|
||||
}
|
||||
|
||||
public async streamBinaryContentOfAsset(
|
||||
asset: ReleaseAsset,
|
||||
): Promise<fetch.Response> {
|
||||
const apiPath = `/repos/${this._ownerName}/${this._repoName}/releases/assets/${asset.id}`;
|
||||
|
||||
return await this.makeApiCall(apiPath, {
|
||||
accept: "application/octet-stream",
|
||||
});
|
||||
}
|
||||
|
||||
protected async makeApiCall(
|
||||
apiPath: string,
|
||||
additionalHeaders: { [key: string]: string } = {},
|
||||
): Promise<fetch.Response> {
|
||||
const response = await this.makeRawRequest(
|
||||
ReleasesApiConsumer._apiBase + apiPath,
|
||||
Object.assign({}, this._defaultHeaders, additionalHeaders),
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
// Check for rate limiting
|
||||
const rateLimitResetValue = response.headers.get("X-RateLimit-Reset");
|
||||
if (response.status === 403 && rateLimitResetValue) {
|
||||
const secondsToMillisecondsFactor = 1000;
|
||||
const rateLimitResetDate = new Date(
|
||||
parseInt(rateLimitResetValue, 10) * secondsToMillisecondsFactor,
|
||||
);
|
||||
throw new GithubRateLimitedError(
|
||||
response.status,
|
||||
await response.text(),
|
||||
rateLimitResetDate,
|
||||
);
|
||||
}
|
||||
throw new GithubApiError(response.status, await response.text());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private async makeRawRequest(
|
||||
requestUrl: string,
|
||||
headers: { [key: string]: string },
|
||||
redirectCount = 0,
|
||||
): Promise<fetch.Response> {
|
||||
const response = await fetch.default(requestUrl, {
|
||||
headers,
|
||||
redirect: "manual",
|
||||
});
|
||||
|
||||
const redirectUrl = response.headers.get("location");
|
||||
if (
|
||||
isRedirectStatusCode(response.status) &&
|
||||
redirectUrl &&
|
||||
redirectCount < ReleasesApiConsumer._maxRedirects
|
||||
) {
|
||||
const parsedRedirectUrl = new URL(redirectUrl);
|
||||
if (parsedRedirectUrl.protocol !== "https:") {
|
||||
throw new Error("Encountered a non-https redirect, rejecting");
|
||||
}
|
||||
if (parsedRedirectUrl.host !== "api.github.com") {
|
||||
// Remove authorization header if we are redirected outside of the GitHub API.
|
||||
//
|
||||
// This is necessary to stream release assets since AWS fails if more than one auth
|
||||
// mechanism is provided.
|
||||
delete headers["authorization"];
|
||||
}
|
||||
return await this.makeRawRequest(redirectUrl, headers, redirectCount + 1);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private readonly _defaultHeaders: { [key: string]: string } = {};
|
||||
private readonly _ownerName: string;
|
||||
private readonly _repoName: string;
|
||||
|
||||
private static readonly _apiBase = "https://api.github.com";
|
||||
private static readonly _maxRedirects = 20;
|
||||
}
|
||||
|
||||
function isRedirectStatusCode(statusCode: number): boolean {
|
||||
return (
|
||||
statusCode === 301 ||
|
||||
statusCode === 302 ||
|
||||
statusCode === 303 ||
|
||||
statusCode === 307 ||
|
||||
statusCode === 308
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Types and helper functions relating to those types.
|
||||
*/
|
||||
@@ -747,7 +603,7 @@ interface DistributionResult {
|
||||
|
||||
interface CompatibleDistributionResult extends DistributionResult {
|
||||
kind: FindDistributionResultKind.CompatibleDistribution;
|
||||
version: semver.SemVer;
|
||||
versionAndFeatures: VersionAndFeatures;
|
||||
}
|
||||
|
||||
interface UnknownCompatibilityDistributionResult extends DistributionResult {
|
||||
@@ -756,7 +612,7 @@ interface UnknownCompatibilityDistributionResult extends DistributionResult {
|
||||
|
||||
interface IncompatibleDistributionResult extends DistributionResult {
|
||||
kind: FindDistributionResultKind.IncompatibleDistribution;
|
||||
version: semver.SemVer;
|
||||
versionAndFeatures: VersionAndFeatures;
|
||||
}
|
||||
|
||||
interface NoDistributionResult {
|
||||
@@ -858,116 +714,3 @@ function warnDeprecatedLauncher() {
|
||||
`Please use "${codeQlLauncherName()}" instead. It is recommended to update to the latest CodeQL binaries.`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A release on GitHub.
|
||||
*/
|
||||
interface Release {
|
||||
assets: ReleaseAsset[];
|
||||
|
||||
/**
|
||||
* The creation date of the release on GitHub.
|
||||
*/
|
||||
createdAt: string;
|
||||
|
||||
/**
|
||||
* The id associated with the release on GitHub.
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The name associated with the release on GitHub.
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An asset corresponding to a release on GitHub.
|
||||
*/
|
||||
interface ReleaseAsset {
|
||||
/**
|
||||
* The id associated with the asset on GitHub.
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The name associated with the asset on GitHub.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The size of the asset in bytes.
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The json returned from github for a release.
|
||||
*/
|
||||
export interface GithubRelease {
|
||||
assets: GithubReleaseAsset[];
|
||||
|
||||
/**
|
||||
* The creation date of the release on GitHub, in ISO 8601 format.
|
||||
*/
|
||||
created_at: string;
|
||||
|
||||
/**
|
||||
* The id associated with the release on GitHub.
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The name associated with the release on GitHub.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Whether the release is a prerelease.
|
||||
*/
|
||||
prerelease: boolean;
|
||||
|
||||
/**
|
||||
* The tag name. This should be the version.
|
||||
*/
|
||||
tag_name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The json returned by github for an asset in a release.
|
||||
*/
|
||||
export interface GithubReleaseAsset {
|
||||
/**
|
||||
* The id associated with the asset on GitHub.
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The name associated with the asset on GitHub.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The size of the asset in bytes.
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
|
||||
export class GithubApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
public body: string,
|
||||
) {
|
||||
super(`API call failed with status code ${status}, body: ${body}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class GithubRateLimitedError extends GithubApiError {
|
||||
constructor(
|
||||
public status: number,
|
||||
public body: string,
|
||||
public rateLimitResetDate: Date,
|
||||
) {
|
||||
super(status, body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export class GithubApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
public body: string,
|
||||
) {
|
||||
super(`API call failed with status code ${status}, body: ${body}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class GithubRateLimitedError extends GithubApiError {
|
||||
constructor(
|
||||
public status: number,
|
||||
public body: string,
|
||||
public rateLimitResetDate: Date,
|
||||
) {
|
||||
super(status, body);
|
||||
}
|
||||
}
|
||||
48
extensions/ql-vscode/src/codeql-cli/distribution/release.ts
Normal file
48
extensions/ql-vscode/src/codeql-cli/distribution/release.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* A release of the CodeQL CLI hosted on GitHub.
|
||||
*/
|
||||
export interface Release {
|
||||
/**
|
||||
* The assets associated with the release on GitHub.
|
||||
*/
|
||||
assets: ReleaseAsset[];
|
||||
|
||||
/**
|
||||
* The creation date of the release on GitHub.
|
||||
*
|
||||
* This is the date that the release was uploaded to GitHub, and not the date
|
||||
* when we downloaded it or the date when we fetched the data from the GitHub API.
|
||||
*/
|
||||
createdAt: string;
|
||||
|
||||
/**
|
||||
* The id associated with the release on GitHub.
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The name associated with the release on GitHub.
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An asset attached to a release on GitHub.
|
||||
* Each release may have multiple assets, and each asset can be downloaded independently.
|
||||
*/
|
||||
export interface ReleaseAsset {
|
||||
/**
|
||||
* The id associated with the asset on GitHub.
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The name associated with the asset on GitHub.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The size of the asset in bytes.
|
||||
*/
|
||||
size: number;
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
import type { Response } from "node-fetch";
|
||||
import { default as fetch } from "node-fetch";
|
||||
import type { Range } from "semver";
|
||||
import { compare, parse, satisfies } from "semver";
|
||||
import { URL } from "url";
|
||||
import type { Release, ReleaseAsset } from "./release";
|
||||
import { GithubApiError, GithubRateLimitedError } from "./github-api-error";
|
||||
|
||||
/**
|
||||
* Communicates with the GitHub API to determine the latest compatible release and download assets.
|
||||
*/
|
||||
export class ReleasesApiConsumer {
|
||||
private static readonly apiBase = "https://api.github.com";
|
||||
private static readonly maxRedirects = 20;
|
||||
|
||||
private readonly defaultHeaders: { [key: string]: string } = {};
|
||||
|
||||
constructor(
|
||||
private readonly repositoryNwo: string,
|
||||
personalAccessToken?: string,
|
||||
) {
|
||||
// Specify version of the GitHub API
|
||||
this.defaultHeaders["accept"] = "application/vnd.github.v3+json";
|
||||
|
||||
if (personalAccessToken) {
|
||||
this.defaultHeaders["authorization"] = `token ${personalAccessToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
public async getLatestRelease(
|
||||
versionRange: Range | undefined,
|
||||
orderBySemver = true,
|
||||
includePrerelease = false,
|
||||
additionalCompatibilityCheck?: (release: GithubRelease) => boolean,
|
||||
): Promise<Release> {
|
||||
const apiPath = `/repos/${this.repositoryNwo}/releases`;
|
||||
const allReleases: GithubRelease[] = await (
|
||||
await this.makeApiCall(apiPath)
|
||||
).json();
|
||||
const compatibleReleases = allReleases.filter((release) => {
|
||||
if (release.prerelease && !includePrerelease) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (versionRange !== undefined) {
|
||||
const version = parse(release.tag_name);
|
||||
if (
|
||||
version === null ||
|
||||
!satisfies(version, versionRange, { includePrerelease })
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
!additionalCompatibilityCheck || additionalCompatibilityCheck(release)
|
||||
);
|
||||
});
|
||||
// Tag names must all be parsable to semvers due to the previous filtering step.
|
||||
const latestRelease = compatibleReleases.sort((a, b) => {
|
||||
const versionComparison = orderBySemver
|
||||
? compare(parse(b.tag_name)!, parse(a.tag_name)!)
|
||||
: b.id - a.id;
|
||||
if (versionComparison !== 0) {
|
||||
return versionComparison;
|
||||
}
|
||||
return b.created_at.localeCompare(a.created_at, "en-US");
|
||||
})[0];
|
||||
if (latestRelease === undefined) {
|
||||
throw new Error(
|
||||
"No compatible CodeQL CLI releases were found. " +
|
||||
"Please check that the CodeQL extension is up to date.",
|
||||
);
|
||||
}
|
||||
const assets: ReleaseAsset[] = latestRelease.assets.map((asset) => {
|
||||
return {
|
||||
id: asset.id,
|
||||
name: asset.name,
|
||||
size: asset.size,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
assets,
|
||||
createdAt: latestRelease.created_at,
|
||||
id: latestRelease.id,
|
||||
name: latestRelease.name,
|
||||
};
|
||||
}
|
||||
|
||||
public async streamBinaryContentOfAsset(
|
||||
asset: ReleaseAsset,
|
||||
): Promise<Response> {
|
||||
const apiPath = `/repos/${this.repositoryNwo}/releases/assets/${asset.id}`;
|
||||
|
||||
return await this.makeApiCall(apiPath, {
|
||||
accept: "application/octet-stream",
|
||||
});
|
||||
}
|
||||
|
||||
protected async makeApiCall(
|
||||
apiPath: string,
|
||||
additionalHeaders: { [key: string]: string } = {},
|
||||
): Promise<Response> {
|
||||
const response = await this.makeRawRequest(
|
||||
ReleasesApiConsumer.apiBase + apiPath,
|
||||
Object.assign({}, this.defaultHeaders, additionalHeaders),
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
// Check for rate limiting
|
||||
const rateLimitResetValue = response.headers.get("X-RateLimit-Reset");
|
||||
if (response.status === 403 && rateLimitResetValue) {
|
||||
const secondsToMillisecondsFactor = 1000;
|
||||
const rateLimitResetDate = new Date(
|
||||
parseInt(rateLimitResetValue, 10) * secondsToMillisecondsFactor,
|
||||
);
|
||||
throw new GithubRateLimitedError(
|
||||
response.status,
|
||||
await response.text(),
|
||||
rateLimitResetDate,
|
||||
);
|
||||
}
|
||||
throw new GithubApiError(response.status, await response.text());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private async makeRawRequest(
|
||||
requestUrl: string,
|
||||
headers: { [key: string]: string },
|
||||
redirectCount = 0,
|
||||
): Promise<Response> {
|
||||
const response = await fetch(requestUrl, {
|
||||
headers,
|
||||
redirect: "manual",
|
||||
});
|
||||
|
||||
const redirectUrl = response.headers.get("location");
|
||||
if (
|
||||
isRedirectStatusCode(response.status) &&
|
||||
redirectUrl &&
|
||||
redirectCount < ReleasesApiConsumer.maxRedirects
|
||||
) {
|
||||
const parsedRedirectUrl = new URL(redirectUrl);
|
||||
if (parsedRedirectUrl.protocol !== "https:") {
|
||||
throw new Error("Encountered a non-https redirect, rejecting");
|
||||
}
|
||||
if (parsedRedirectUrl.host !== "api.github.com") {
|
||||
// Remove authorization header if we are redirected outside of the GitHub API.
|
||||
//
|
||||
// This is necessary to stream release assets since AWS fails if more than one auth
|
||||
// mechanism is provided.
|
||||
delete headers["authorization"];
|
||||
}
|
||||
return await this.makeRawRequest(redirectUrl, headers, redirectCount + 1);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
function isRedirectStatusCode(statusCode: number): boolean {
|
||||
return (
|
||||
statusCode === 301 ||
|
||||
statusCode === 302 ||
|
||||
statusCode === 303 ||
|
||||
statusCode === 307 ||
|
||||
statusCode === 308
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The json returned from github for a release.
|
||||
* See https://docs.github.com/en/rest/releases/releases#get-a-release for example response and response schema.
|
||||
*
|
||||
* This type must match the format of the GitHub API and is not intended to be used outside of this file except for tests. Please use the `Release` type instead.
|
||||
*/
|
||||
export interface GithubRelease {
|
||||
assets: GithubReleaseAsset[];
|
||||
created_at: string;
|
||||
id: number;
|
||||
name: string;
|
||||
prerelease: boolean;
|
||||
tag_name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The json returned by github for an asset in a release.
|
||||
* See https://docs.github.com/en/rest/releases/releases#get-a-release for example response and response schema.
|
||||
*
|
||||
* This type must match the format of the GitHub API and is not intended to be used outside of this file except for tests. Please use the `ReleaseAsset` type instead.
|
||||
*/
|
||||
interface GithubReleaseAsset {
|
||||
id: number;
|
||||
name: string;
|
||||
size: number;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { Uri, window } from "vscode";
|
||||
import type { CodeQLCliServer } from "./cli";
|
||||
import type { Uri } from "vscode";
|
||||
import { window } from "vscode";
|
||||
import {
|
||||
getLanguageDisplayName,
|
||||
isQueryLanguage,
|
||||
@@ -62,7 +63,7 @@ export async function askForLanguage(
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const selectedItem = await window.showQuickPick(items, {
|
||||
placeHolder: "Select target language for your query",
|
||||
placeHolder: "Select target query language",
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
if (!selectedItem) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CodeQLCliServer } from "./cli";
|
||||
import { QueryMetadata } from "../common/interface-types";
|
||||
import type { CodeQLCliServer } from "./cli";
|
||||
import type { QueryMetadata } from "../common/interface-types";
|
||||
import { extLogger } from "../common/logging/vscode";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Credentials } from "./authentication";
|
||||
import { Disposable } from "./disposable-object";
|
||||
import { AppEventEmitter } from "./events";
|
||||
import { NotificationLogger } from "./logging";
|
||||
import { Memento } from "./memento";
|
||||
import { AppCommandManager } from "./commands";
|
||||
import { AppTelemetry } from "./telemetry";
|
||||
import type { Credentials } from "./authentication";
|
||||
import type { Disposable } from "./disposable-object";
|
||||
import type { AppEventEmitter } from "./events";
|
||||
import type { NotificationLogger } from "./logging";
|
||||
import type { Memento } from "./memento";
|
||||
import type { AppCommandManager } from "./commands";
|
||||
import type { AppTelemetry } from "./telemetry";
|
||||
|
||||
export interface App {
|
||||
createEventEmitter<T>(): AppEventEmitter<T>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as Octokit from "@octokit/rest";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
|
||||
/**
|
||||
* An interface providing methods for obtaining access tokens
|
||||
@@ -12,7 +12,7 @@ export interface Credentials {
|
||||
*
|
||||
* @returns An instance of Octokit.
|
||||
*/
|
||||
getOctokit(): Promise<Octokit.Octokit>;
|
||||
getOctokit(): Promise<Octokit>;
|
||||
|
||||
/**
|
||||
* Returns an OAuth access token.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* the "for the sake of extensibility" comment in messages.ts.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace ColumnKindCode {
|
||||
export namespace BqrsColumnKindCode {
|
||||
export const FLOAT = "f";
|
||||
export const INTEGER = "i";
|
||||
export const STRING = "s";
|
||||
@@ -13,55 +13,44 @@ export namespace ColumnKindCode {
|
||||
export const ENTITY = "e";
|
||||
}
|
||||
|
||||
type ColumnKind =
|
||||
| typeof ColumnKindCode.FLOAT
|
||||
| typeof ColumnKindCode.INTEGER
|
||||
| typeof ColumnKindCode.STRING
|
||||
| typeof ColumnKindCode.BOOLEAN
|
||||
| typeof ColumnKindCode.DATE
|
||||
| typeof ColumnKindCode.ENTITY;
|
||||
export type BqrsColumnKind =
|
||||
| typeof BqrsColumnKindCode.FLOAT
|
||||
| typeof BqrsColumnKindCode.INTEGER
|
||||
| typeof BqrsColumnKindCode.STRING
|
||||
| typeof BqrsColumnKindCode.BOOLEAN
|
||||
| typeof BqrsColumnKindCode.DATE
|
||||
| typeof BqrsColumnKindCode.ENTITY;
|
||||
|
||||
export interface Column {
|
||||
export interface BqrsSchemaColumn {
|
||||
name?: string;
|
||||
kind: ColumnKind;
|
||||
kind: BqrsColumnKind;
|
||||
}
|
||||
|
||||
export interface ResultSetSchema {
|
||||
export interface BqrsResultSetSchema {
|
||||
name: string;
|
||||
rows: number;
|
||||
columns: Column[];
|
||||
pagination?: PaginationInfo;
|
||||
columns: BqrsSchemaColumn[];
|
||||
pagination?: BqrsPaginationInfo;
|
||||
}
|
||||
|
||||
export function getResultSetSchema(
|
||||
resultSetName: string,
|
||||
resultSets: BQRSInfo,
|
||||
): ResultSetSchema | undefined {
|
||||
for (const schema of resultSets["result-sets"]) {
|
||||
if (schema.name === resultSetName) {
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
interface PaginationInfo {
|
||||
interface BqrsPaginationInfo {
|
||||
"step-size": number;
|
||||
offsets: number[];
|
||||
}
|
||||
|
||||
export interface BQRSInfo {
|
||||
"result-sets": ResultSetSchema[];
|
||||
export interface BqrsInfo {
|
||||
"result-sets": BqrsResultSetSchema[];
|
||||
}
|
||||
|
||||
export type BqrsId = number;
|
||||
|
||||
export interface EntityValue {
|
||||
url?: UrlValue;
|
||||
export interface BqrsEntityValue {
|
||||
url?: BqrsUrlValue;
|
||||
label?: string;
|
||||
id?: BqrsId;
|
||||
}
|
||||
|
||||
export interface LineColumnLocation {
|
||||
export interface BqrsLineColumnLocation {
|
||||
uri: string;
|
||||
startLine: number;
|
||||
startColumn: number;
|
||||
@@ -69,7 +58,7 @@ export interface LineColumnLocation {
|
||||
endColumn: number;
|
||||
}
|
||||
|
||||
export interface WholeFileLocation {
|
||||
export interface BqrsWholeFileLocation {
|
||||
uri: string;
|
||||
startLine: never;
|
||||
startColumn: never;
|
||||
@@ -77,37 +66,17 @@ export interface WholeFileLocation {
|
||||
endColumn: never;
|
||||
}
|
||||
|
||||
export type ResolvableLocationValue = WholeFileLocation | LineColumnLocation;
|
||||
export type BqrsUrlValue =
|
||||
| BqrsWholeFileLocation
|
||||
| BqrsLineColumnLocation
|
||||
| string;
|
||||
|
||||
export type UrlValue = ResolvableLocationValue | string;
|
||||
|
||||
export type CellValue = EntityValue | number | string | boolean;
|
||||
|
||||
export type ResultRow = CellValue[];
|
||||
|
||||
export interface RawResultSet {
|
||||
readonly schema: ResultSetSchema;
|
||||
readonly rows: readonly ResultRow[];
|
||||
}
|
||||
|
||||
// TODO: This function is not necessary. It generates a tuple that is slightly easier
|
||||
// to handle than the ResultSetSchema and DecodedBqrsChunk. But perhaps it is unnecessary
|
||||
// boilerplate.
|
||||
export function transformBqrsResultSet(
|
||||
schema: ResultSetSchema,
|
||||
page: DecodedBqrsChunk,
|
||||
): RawResultSet {
|
||||
return {
|
||||
schema,
|
||||
rows: Array.from(page.tuples),
|
||||
};
|
||||
}
|
||||
export type BqrsCellValue = BqrsEntityValue | number | string | boolean;
|
||||
|
||||
export type BqrsKind =
|
||||
| "String"
|
||||
| "Float"
|
||||
| "Integer"
|
||||
| "String"
|
||||
| "Boolean"
|
||||
| "Date"
|
||||
| "Entity";
|
||||
@@ -116,8 +85,9 @@ interface BqrsColumn {
|
||||
name?: string;
|
||||
kind: BqrsKind;
|
||||
}
|
||||
|
||||
export interface DecodedBqrsChunk {
|
||||
tuples: CellValue[][];
|
||||
tuples: BqrsCellValue[][];
|
||||
next?: number;
|
||||
columns: BqrsColumn[];
|
||||
}
|
||||
|
||||
216
extensions/ql-vscode/src/common/bqrs-raw-results-mapper.ts
Normal file
216
extensions/ql-vscode/src/common/bqrs-raw-results-mapper.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import type {
|
||||
BqrsCellValue as BqrsCellValue,
|
||||
BqrsColumnKind as BqrsColumnKind,
|
||||
DecodedBqrsChunk,
|
||||
BqrsEntityValue as BqrsEntityValue,
|
||||
BqrsLineColumnLocation,
|
||||
BqrsResultSetSchema,
|
||||
BqrsUrlValue as BqrsUrlValue,
|
||||
BqrsWholeFileLocation,
|
||||
BqrsSchemaColumn,
|
||||
} from "./bqrs-cli-types";
|
||||
import { BqrsColumnKindCode } from "./bqrs-cli-types";
|
||||
import type {
|
||||
CellValue,
|
||||
Column,
|
||||
EntityValue,
|
||||
RawResultSet,
|
||||
Row,
|
||||
UrlValue,
|
||||
UrlValueResolvable,
|
||||
} from "./raw-result-types";
|
||||
import { ColumnKind } from "./raw-result-types";
|
||||
import { assertNever } from "./helpers-pure";
|
||||
import { isEmptyPath } from "./bqrs-utils";
|
||||
|
||||
export function bqrsToResultSet(
|
||||
schema: BqrsResultSetSchema,
|
||||
chunk: DecodedBqrsChunk,
|
||||
): RawResultSet {
|
||||
const name = schema.name;
|
||||
const totalRowCount = schema.rows;
|
||||
|
||||
const columns = schema.columns.map(mapColumn);
|
||||
|
||||
const rows = chunk.tuples.map(
|
||||
(tuple): Row => tuple.map((cell): CellValue => mapCellValue(cell)),
|
||||
);
|
||||
|
||||
const resultSet: RawResultSet = {
|
||||
name,
|
||||
totalRowCount,
|
||||
columns,
|
||||
rows,
|
||||
};
|
||||
|
||||
if (chunk.next) {
|
||||
resultSet.nextPageOffset = chunk.next;
|
||||
}
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
function mapColumn(column: BqrsSchemaColumn): Column {
|
||||
const result: Column = {
|
||||
kind: mapColumnKind(column.kind),
|
||||
};
|
||||
|
||||
if (column.name) {
|
||||
result.name = column.name;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function mapColumnKind(kind: BqrsColumnKind): ColumnKind {
|
||||
switch (kind) {
|
||||
case BqrsColumnKindCode.STRING:
|
||||
return ColumnKind.String;
|
||||
case BqrsColumnKindCode.FLOAT:
|
||||
return ColumnKind.Float;
|
||||
case BqrsColumnKindCode.INTEGER:
|
||||
return ColumnKind.Integer;
|
||||
case BqrsColumnKindCode.BOOLEAN:
|
||||
return ColumnKind.Boolean;
|
||||
case BqrsColumnKindCode.DATE:
|
||||
return ColumnKind.Date;
|
||||
case BqrsColumnKindCode.ENTITY:
|
||||
return ColumnKind.Entity;
|
||||
default:
|
||||
assertNever(kind);
|
||||
}
|
||||
}
|
||||
|
||||
function mapCellValue(cellValue: BqrsCellValue): CellValue {
|
||||
switch (typeof cellValue) {
|
||||
case "string":
|
||||
return {
|
||||
type: "string",
|
||||
value: cellValue,
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
type: "number",
|
||||
value: cellValue,
|
||||
};
|
||||
case "boolean":
|
||||
return {
|
||||
type: "boolean",
|
||||
value: cellValue,
|
||||
};
|
||||
case "object":
|
||||
return {
|
||||
type: "entity",
|
||||
value: mapEntityValue(cellValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function mapEntityValue(cellValue: BqrsEntityValue): EntityValue {
|
||||
const result: EntityValue = {};
|
||||
|
||||
if (cellValue.id) {
|
||||
result.id = cellValue.id;
|
||||
}
|
||||
if (cellValue.label) {
|
||||
result.label = cellValue.label;
|
||||
}
|
||||
if (cellValue.url) {
|
||||
result.url = mapUrlValue(cellValue.url);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function mapUrlValue(urlValue: BqrsUrlValue): UrlValue | undefined {
|
||||
if (typeof urlValue === "string") {
|
||||
const location = tryGetLocationFromString(urlValue);
|
||||
if (location !== undefined) {
|
||||
return location;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "string",
|
||||
value: urlValue,
|
||||
};
|
||||
}
|
||||
|
||||
if (isWholeFileLoc(urlValue)) {
|
||||
return {
|
||||
type: "wholeFileLocation",
|
||||
uri: urlValue.uri,
|
||||
};
|
||||
}
|
||||
|
||||
if (isLineColumnLoc(urlValue)) {
|
||||
return {
|
||||
type: "lineColumnLocation",
|
||||
uri: urlValue.uri,
|
||||
startLine: urlValue.startLine,
|
||||
startColumn: urlValue.startColumn,
|
||||
endLine: urlValue.endLine,
|
||||
endColumn: urlValue.endColumn,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isLineColumnLoc(loc: BqrsUrlValue): loc is BqrsLineColumnLocation {
|
||||
return (
|
||||
typeof loc !== "string" &&
|
||||
!isEmptyPath(loc.uri) &&
|
||||
"startLine" in loc &&
|
||||
"startColumn" in loc &&
|
||||
"endLine" in loc &&
|
||||
"endColumn" in loc
|
||||
);
|
||||
}
|
||||
|
||||
function isWholeFileLoc(loc: BqrsUrlValue): loc is BqrsWholeFileLocation {
|
||||
return (
|
||||
typeof loc !== "string" && !isEmptyPath(loc.uri) && !isLineColumnLoc(loc)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The CodeQL filesystem libraries use this pattern in `getURL()` predicates
|
||||
* to describe the location of an entire filesystem resource.
|
||||
* Such locations appear as `StringLocation`s instead of `FivePartLocation`s.
|
||||
*
|
||||
* Folder resources also get similar URLs, but with the `folder` scheme.
|
||||
* They are deliberately ignored here, since there is no suitable location to show the user.
|
||||
*/
|
||||
const FILE_LOCATION_REGEX = /file:\/\/(.+):([0-9]+):([0-9]+):([0-9]+):([0-9]+)/;
|
||||
|
||||
function tryGetLocationFromString(loc: string): UrlValueResolvable | undefined {
|
||||
const matches = FILE_LOCATION_REGEX.exec(loc);
|
||||
if (matches && matches.length > 1 && matches[1]) {
|
||||
if (isWholeFileMatch(matches)) {
|
||||
return {
|
||||
type: "wholeFileLocation",
|
||||
uri: matches[1],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: "lineColumnLocation",
|
||||
uri: matches[1],
|
||||
startLine: Number(matches[2]),
|
||||
startColumn: Number(matches[3]),
|
||||
endLine: Number(matches[4]),
|
||||
endColumn: Number(matches[5]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isWholeFileMatch(matches: RegExpExecArray): boolean {
|
||||
return (
|
||||
matches[2] === "0" &&
|
||||
matches[3] === "0" &&
|
||||
matches[4] === "0" &&
|
||||
matches[5] === "0"
|
||||
);
|
||||
}
|
||||
@@ -1,111 +1,21 @@
|
||||
import {
|
||||
UrlValue,
|
||||
ResolvableLocationValue,
|
||||
LineColumnLocation,
|
||||
WholeFileLocation,
|
||||
} from "./bqrs-cli-types";
|
||||
import { createRemoteFileRef } from "../common/location-link-utils";
|
||||
|
||||
/**
|
||||
* The CodeQL filesystem libraries use this pattern in `getURL()` predicates
|
||||
* to describe the location of an entire filesystem resource.
|
||||
* Such locations appear as `StringLocation`s instead of `FivePartLocation`s.
|
||||
*
|
||||
* Folder resources also get similar URLs, but with the `folder` scheme.
|
||||
* They are deliberately ignored here, since there is no suitable location to show the user.
|
||||
*/
|
||||
const FILE_LOCATION_REGEX = /file:\/\/(.+):([0-9]+):([0-9]+):([0-9]+):([0-9]+)/;
|
||||
/**
|
||||
* Gets a resolvable source file location for the specified `LocationValue`, if possible.
|
||||
* @param loc The location to test.
|
||||
*/
|
||||
export function tryGetResolvableLocation(
|
||||
loc: UrlValue | undefined,
|
||||
): ResolvableLocationValue | undefined {
|
||||
let resolvedLoc;
|
||||
if (loc === undefined) {
|
||||
resolvedLoc = undefined;
|
||||
} else if (isWholeFileLoc(loc) || isLineColumnLoc(loc)) {
|
||||
resolvedLoc = loc as ResolvableLocationValue;
|
||||
} else if (isStringLoc(loc)) {
|
||||
resolvedLoc = tryGetLocationFromString(loc);
|
||||
} else {
|
||||
resolvedLoc = undefined;
|
||||
}
|
||||
|
||||
return resolvedLoc;
|
||||
}
|
||||
|
||||
export function tryGetLocationFromString(
|
||||
loc: string,
|
||||
): ResolvableLocationValue | undefined {
|
||||
const matches = FILE_LOCATION_REGEX.exec(loc);
|
||||
if (matches && matches.length > 1 && matches[1]) {
|
||||
if (isWholeFileMatch(matches)) {
|
||||
return {
|
||||
uri: matches[1],
|
||||
} as WholeFileLocation;
|
||||
} else {
|
||||
return {
|
||||
uri: matches[1],
|
||||
startLine: Number(matches[2]),
|
||||
startColumn: Number(matches[3]),
|
||||
endLine: Number(matches[4]),
|
||||
endColumn: Number(matches[5]),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function isWholeFileMatch(matches: RegExpExecArray): boolean {
|
||||
return (
|
||||
matches[2] === "0" &&
|
||||
matches[3] === "0" &&
|
||||
matches[4] === "0" &&
|
||||
matches[5] === "0"
|
||||
);
|
||||
}
|
||||
import type { UrlValue } from "./raw-result-types";
|
||||
import { isUrlValueResolvable } from "./raw-result-types";
|
||||
|
||||
/**
|
||||
* Checks whether the file path is empty. If so, we do not want to render this location
|
||||
* as a link.
|
||||
*
|
||||
* @param uri A file uri
|
||||
*/
|
||||
export function isEmptyPath(uriStr: string) {
|
||||
return !uriStr || uriStr === "file:/";
|
||||
}
|
||||
|
||||
export function isLineColumnLoc(loc: UrlValue): loc is LineColumnLocation {
|
||||
return (
|
||||
typeof loc !== "string" &&
|
||||
!isEmptyPath(loc.uri) &&
|
||||
"startLine" in loc &&
|
||||
"startColumn" in loc &&
|
||||
"endLine" in loc &&
|
||||
"endColumn" in loc
|
||||
);
|
||||
}
|
||||
|
||||
export function isWholeFileLoc(loc: UrlValue): loc is WholeFileLocation {
|
||||
return (
|
||||
typeof loc !== "string" && !isEmptyPath(loc.uri) && !isLineColumnLoc(loc)
|
||||
);
|
||||
}
|
||||
|
||||
export function isStringLoc(loc: UrlValue): loc is string {
|
||||
return typeof loc === "string";
|
||||
}
|
||||
|
||||
export function tryGetRemoteLocation(
|
||||
loc: UrlValue | undefined,
|
||||
fileLinkPrefix: string,
|
||||
sourceLocationPrefix: string | undefined,
|
||||
): string | undefined {
|
||||
const resolvableLocation = tryGetResolvableLocation(loc);
|
||||
if (!resolvableLocation) {
|
||||
if (!loc || !isUrlValueResolvable(loc)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -115,22 +25,19 @@ export function tryGetRemoteLocation(
|
||||
// "file:${sourceLocationPrefix}/relative/path/to/file"
|
||||
// So we need to strip off the first part to get the relative path.
|
||||
if (sourceLocationPrefix) {
|
||||
if (!resolvableLocation.uri.startsWith(`file:${sourceLocationPrefix}/`)) {
|
||||
if (!loc.uri.startsWith(`file:${sourceLocationPrefix}/`)) {
|
||||
return undefined;
|
||||
}
|
||||
trimmedLocation = resolvableLocation.uri.replace(
|
||||
`file:${sourceLocationPrefix}/`,
|
||||
"",
|
||||
);
|
||||
trimmedLocation = loc.uri.replace(`file:${sourceLocationPrefix}/`, "");
|
||||
} else {
|
||||
// If the source location prefix is empty (e.g. for older remote queries), we assume that the database
|
||||
// was created on a Linux actions runner and has the format:
|
||||
// "file:/home/runner/work/<repo>/<repo>/relative/path/to/file"
|
||||
// So we need to drop the first 6 parts of the path.
|
||||
if (!resolvableLocation.uri.startsWith("file:/home/runner/work/")) {
|
||||
if (!loc.uri.startsWith("file:/home/runner/work/")) {
|
||||
return undefined;
|
||||
}
|
||||
const locationParts = resolvableLocation.uri.split("/");
|
||||
const locationParts = loc.uri.split("/");
|
||||
trimmedLocation = locationParts.slice(6, locationParts.length).join("/");
|
||||
}
|
||||
|
||||
@@ -138,11 +45,16 @@ export function tryGetRemoteLocation(
|
||||
fileLinkPrefix,
|
||||
filePath: trimmedLocation,
|
||||
};
|
||||
|
||||
if (loc.type === "wholeFileLocation") {
|
||||
return createRemoteFileRef(fileLink);
|
||||
}
|
||||
|
||||
return createRemoteFileRef(
|
||||
fileLink,
|
||||
resolvableLocation.startLine,
|
||||
resolvableLocation.endLine,
|
||||
resolvableLocation.startColumn,
|
||||
resolvableLocation.endColumn,
|
||||
loc.startLine,
|
||||
loc.endLine,
|
||||
loc.startColumn,
|
||||
loc.endColumn,
|
||||
);
|
||||
}
|
||||
|
||||
3
extensions/ql-vscode/src/common/bytes.ts
Normal file
3
extensions/ql-vscode/src/common/bytes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function readableBytesMb(numBytes: number): string {
|
||||
return `${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { CommandManager } from "../packages/commands";
|
||||
import type { Uri, Range, TextDocumentShowOptions } from "vscode";
|
||||
import type { Uri, Range, TextDocumentShowOptions, TestItem } from "vscode";
|
||||
import type { AstItem } from "../language-support";
|
||||
import type { DbTreeViewItem } from "../databases/ui/db-tree-view-item";
|
||||
import type { DatabaseItem } from "../databases/local-databases";
|
||||
import type { QueryHistoryInfo } from "../query-history/query-history-info";
|
||||
import type { TestTreeNode } from "../query-testing/test-tree-node";
|
||||
import type {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisScannedRepository,
|
||||
@@ -146,6 +145,7 @@ export type LocalQueryCommands = {
|
||||
"codeQL.quickQuery": () => Promise<void>;
|
||||
"codeQL.getCurrentQuery": () => Promise<string>;
|
||||
"codeQL.createQuery": () => Promise<void>;
|
||||
"codeQLQuickQuery.createQuery": () => Promise<void>;
|
||||
};
|
||||
|
||||
// Debugger commands
|
||||
@@ -275,9 +275,11 @@ export type VariantAnalysisCommands = {
|
||||
"codeQL.openVariantAnalysisView": (
|
||||
variantAnalysisId: number,
|
||||
) => Promise<void>;
|
||||
"codeQL.runVariantAnalysis": (uri?: Uri) => Promise<void>;
|
||||
"codeQL.runVariantAnalysisContextEditor": (uri?: Uri) => Promise<void>;
|
||||
"codeQL.runVariantAnalysis": () => Promise<void>;
|
||||
"codeQL.runVariantAnalysisContextEditor": (uri: Uri) => Promise<void>;
|
||||
"codeQL.runVariantAnalysisContextExplorer": ExplorerSelectionCommandFunction<Uri>;
|
||||
"codeQLQueries.runVariantAnalysisContextMenu": TreeViewContextSingleSelectionCommandFunction<QueryTreeViewItem>;
|
||||
"codeQL.runVariantAnalysisPublishedPack": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type DatabasePanelCommands = {
|
||||
@@ -333,11 +335,9 @@ export type SummaryLanguageSupportCommands = {
|
||||
};
|
||||
|
||||
export type TestUICommands = {
|
||||
"codeQLTests.showOutputDifferences": (node: TestTreeNode) => Promise<void>;
|
||||
"codeQLTests.acceptOutput": (node: TestTreeNode) => Promise<void>;
|
||||
"codeQLTests.acceptOutputContextTestItem": (
|
||||
node: TestTreeNode,
|
||||
) => Promise<void>;
|
||||
"codeQLTests.showOutputDifferences": (node: TestItem) => Promise<void>;
|
||||
"codeQLTests.acceptOutput": (node: TestItem) => Promise<void>;
|
||||
"codeQLTests.acceptOutputContextTestItem": (node: TestItem) => Promise<void>;
|
||||
};
|
||||
|
||||
export type MockGitHubApiServerCommands = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DisposableObject } from "./disposable-object";
|
||||
import { getErrorMessage } from "./helpers-pure";
|
||||
import { Logger } from "./logging";
|
||||
import type { BaseLogger } from "./logging";
|
||||
|
||||
/**
|
||||
* Base class for "discovery" operations, which scan the file system to find specific kinds of
|
||||
@@ -13,7 +13,7 @@ export abstract class Discovery extends DisposableObject {
|
||||
|
||||
constructor(
|
||||
protected readonly name: string,
|
||||
private readonly logger: Logger,
|
||||
protected readonly logger: BaseLogger,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Avoid explicitly referencing Disposable type in vscode.
|
||||
// This file cannot have dependencies on the vscode API.
|
||||
export interface Disposable {
|
||||
dispose(): any;
|
||||
dispose(): unknown;
|
||||
}
|
||||
|
||||
export type DisposeHandler = (disposable: Disposable) => void;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { platform } from "os";
|
||||
import { Open } from "unzipper";
|
||||
import { join } from "path";
|
||||
import { pathExists, chmod } from "fs-extra";
|
||||
|
||||
/**
|
||||
* Get the name of the codeql cli installation we prefer to install, based on our current platform.
|
||||
@@ -19,31 +16,6 @@ export function getRequiredAssetName(): string {
|
||||
}
|
||||
}
|
||||
|
||||
export async function extractZipArchive(
|
||||
archivePath: string,
|
||||
outPath: string,
|
||||
): Promise<void> {
|
||||
const archive = await Open.file(archivePath);
|
||||
await archive.extract({
|
||||
concurrency: 4,
|
||||
path: outPath,
|
||||
});
|
||||
// Set file permissions for extracted files
|
||||
await Promise.all(
|
||||
archive.files.map(async (file) => {
|
||||
// Only change file permissions if within outPath (path.join normalises the path)
|
||||
const extractedPath = join(outPath, file.path);
|
||||
if (
|
||||
extractedPath.indexOf(outPath) !== 0 ||
|
||||
!(await pathExists(extractedPath))
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return chmod(extractedPath, file.externalFileAttributes >>> 16);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function codeQlLauncherName(): string {
|
||||
return platform() === "win32" ? "codeql.exe" : "codeql";
|
||||
}
|
||||
|
||||
@@ -84,12 +84,13 @@ export interface ErrorLike {
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
function isErrorLike(error: any): error is ErrorLike {
|
||||
if (
|
||||
function isErrorLike(error: unknown): error is ErrorLike {
|
||||
return (
|
||||
error !== undefined &&
|
||||
error !== null &&
|
||||
typeof error === "object" &&
|
||||
"message" in error &&
|
||||
typeof error.message === "string" &&
|
||||
(error.stack === undefined || typeof error.stack === "string")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
(!("stack" in error) || typeof error.stack === "string")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Disposable } from "./disposable-object";
|
||||
import type { Disposable } from "./disposable-object";
|
||||
|
||||
export interface AppEvent<T> {
|
||||
(listener: (event: T) => void): Disposable;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { basename, dirname, join } from "path";
|
||||
import { EnvironmentContext } from "./app";
|
||||
import type { EnvironmentContext } from "./app";
|
||||
|
||||
/**
|
||||
* A node in the tree of files. This will be either a `FileTreeDirectory` or a `FileTreeLeaf`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { pathExists, stat, readdir, opendir } from "fs-extra";
|
||||
import { isAbsolute, join, relative, resolve } from "path";
|
||||
import { pathExists, stat, readdir, opendir, lstatSync } from "fs-extra";
|
||||
import { dirname, isAbsolute, join, relative, resolve } from "path";
|
||||
import { tmpdir as osTmpdir } from "os";
|
||||
|
||||
/**
|
||||
@@ -91,18 +91,23 @@ export async function readDirFullPaths(path: string): Promise<string[]> {
|
||||
* Symbolic links are ignored.
|
||||
*
|
||||
* @param dir the directory to walk
|
||||
* @param includeDirectories whether to include directories in the results
|
||||
*
|
||||
* @return An iterator of the full path to all files recursively found in the directory.
|
||||
*/
|
||||
export async function* walkDirectory(
|
||||
dir: string,
|
||||
includeDirectories = false,
|
||||
): AsyncIterableIterator<string> {
|
||||
const seenFiles = new Set<string>();
|
||||
for await (const d of await opendir(dir)) {
|
||||
const entry = join(dir, d.name);
|
||||
seenFiles.add(entry);
|
||||
if (d.isDirectory()) {
|
||||
yield* walkDirectory(entry);
|
||||
if (includeDirectories) {
|
||||
yield entry;
|
||||
}
|
||||
yield* walkDirectory(entry, includeDirectories);
|
||||
} else if (d.isFile()) {
|
||||
yield entry;
|
||||
}
|
||||
@@ -119,11 +124,55 @@ export interface IOError {
|
||||
readonly code: string;
|
||||
}
|
||||
|
||||
export function isIOError(e: any): e is IOError {
|
||||
return e.code !== undefined && typeof e.code === "string";
|
||||
export function isIOError(e: unknown): e is IOError {
|
||||
return (
|
||||
e !== undefined &&
|
||||
e !== null &&
|
||||
typeof e === "object" &&
|
||||
"code" in e &&
|
||||
typeof e.code === "string"
|
||||
);
|
||||
}
|
||||
|
||||
// This function is a wrapper around `os.tmpdir()` to make it easier to mock in tests.
|
||||
export function tmpdir(): string {
|
||||
return osTmpdir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the common parent directory of an arbitrary number of absolute paths. The result
|
||||
* will be an absolute path.
|
||||
* @param paths The array of paths.
|
||||
* @returns The common parent directory of the paths.
|
||||
*/
|
||||
export function findCommonParentDir(...paths: string[]): string {
|
||||
if (paths.length === 0) {
|
||||
throw new Error("At least one path must be provided");
|
||||
}
|
||||
if (paths.some((path) => !isAbsolute(path))) {
|
||||
throw new Error("All paths must be absolute");
|
||||
}
|
||||
|
||||
paths = paths.map((path) => normalizePath(path));
|
||||
|
||||
// If there's only one path and it's a file, return its dirname
|
||||
if (paths.length === 1) {
|
||||
return lstatSync(paths[0]).isFile() ? dirname(paths[0]) : paths[0];
|
||||
}
|
||||
|
||||
let commonDir = paths[0];
|
||||
while (!paths.every((path) => containsPath(commonDir, path))) {
|
||||
if (isTopLevelPath(commonDir)) {
|
||||
throw new Error(
|
||||
"Reached filesystem root and didn't find a common parent directory",
|
||||
);
|
||||
}
|
||||
commonDir = dirname(commonDir);
|
||||
}
|
||||
|
||||
return commonDir;
|
||||
}
|
||||
|
||||
function isTopLevelPath(path: string): boolean {
|
||||
return dirname(path) === path;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,18 @@
|
||||
|
||||
import { RedactableError } from "./errors";
|
||||
|
||||
// Matches any type that is not an array. This is useful to help avoid
|
||||
// nested arrays, or for cases like createSingleSelectionCommand to avoid T
|
||||
// accidentally getting instantiated as DatabaseItem[] instead of DatabaseItem.
|
||||
export type NotArray =
|
||||
| string
|
||||
| bigint
|
||||
| number
|
||||
| boolean
|
||||
| (object & {
|
||||
length?: never;
|
||||
});
|
||||
|
||||
/**
|
||||
* This error is used to indicate a runtime failure of an exhaustivity check enforced at compile time.
|
||||
*/
|
||||
@@ -27,26 +39,26 @@ export function assertNever(value: never): never {
|
||||
/**
|
||||
* Use to perform array filters where the predicate is asynchronous.
|
||||
*/
|
||||
export const asyncFilter = async function <T>(
|
||||
export async function asyncFilter<T>(
|
||||
arr: T[],
|
||||
predicate: (arg0: T) => Promise<boolean>,
|
||||
) {
|
||||
const results = await Promise.all(arr.map(predicate));
|
||||
return arr.filter((_, index) => results[index]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This regex matches strings of the form `owner/repo` where:
|
||||
* - `owner` is made up of alphanumeric characters, hyphens, underscores, or periods
|
||||
* - `repo` is made up of alphanumeric characters, hyphens, underscores, or periods
|
||||
*/
|
||||
export const REPO_REGEX = /^[a-zA-Z0-9-_\.]+\/[a-zA-Z0-9-_\.]+$/;
|
||||
export const REPO_REGEX = /^[a-zA-Z0-9-_.]+\/[a-zA-Z0-9-_.]+$/;
|
||||
|
||||
/**
|
||||
* This regex matches GiHub organization and user strings. These are made up for alphanumeric
|
||||
* characters, hyphens, underscores or periods.
|
||||
*/
|
||||
export const OWNER_REGEX = /^[a-zA-Z0-9-_\.]+$/;
|
||||
export const OWNER_REGEX = /^[a-zA-Z0-9-_.]+$/;
|
||||
|
||||
export function getErrorMessage(e: unknown): string {
|
||||
if (e instanceof RedactableError) {
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import * as sarif from "sarif";
|
||||
import {
|
||||
RawResultSet,
|
||||
ResultRow,
|
||||
ResultSetSchema,
|
||||
Column,
|
||||
ResolvableLocationValue,
|
||||
} from "../common/bqrs-cli-types";
|
||||
import {
|
||||
import type { Log, Result } from "sarif";
|
||||
import type {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisScannedRepositoryResult,
|
||||
VariantAnalysisScannedRepositoryState,
|
||||
} from "../variant-analysis/shared/variant-analysis";
|
||||
import {
|
||||
import type {
|
||||
RepositoriesFilterSortState,
|
||||
RepositoriesFilterSortStateWithIds,
|
||||
} from "../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { ErrorLike } from "../common/errors";
|
||||
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
|
||||
import { Method } from "../model-editor/method";
|
||||
import { ModeledMethod } from "../model-editor/modeled-method";
|
||||
import {
|
||||
import type { ErrorLike } from "../common/errors";
|
||||
import type { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
|
||||
import type { Method } from "../model-editor/method";
|
||||
import type { ModeledMethod } from "../model-editor/modeled-method";
|
||||
import type {
|
||||
MethodModelingPanelViewState,
|
||||
ModelEditorViewState,
|
||||
} from "../model-editor/shared/view-state";
|
||||
import { Mode } from "../model-editor/shared/mode";
|
||||
import { QueryLanguage } from "./query-language";
|
||||
import type { Mode } from "../model-editor/shared/mode";
|
||||
import type { QueryLanguage } from "./query-language";
|
||||
import type {
|
||||
Column,
|
||||
RawResultSet,
|
||||
Row,
|
||||
UrlValueResolvable,
|
||||
} from "./raw-result-types";
|
||||
import type { AccessPathSuggestionOptions } from "../model-editor/suggestions";
|
||||
|
||||
/**
|
||||
* This module contains types and code that are shared between
|
||||
@@ -35,10 +35,13 @@ export const SELECT_TABLE_NAME = "#select";
|
||||
export const ALERTS_TABLE_NAME = "alerts";
|
||||
export const GRAPH_TABLE_NAME = "graph";
|
||||
|
||||
export type RawTableResultSet = { t: "RawResultSet" } & RawResultSet;
|
||||
export type InterpretedResultSet<T> = {
|
||||
type RawTableResultSet = {
|
||||
t: "RawResultSet";
|
||||
resultSet: RawResultSet;
|
||||
};
|
||||
|
||||
type InterpretedResultSet<T> = {
|
||||
t: "InterpretedResultSet";
|
||||
readonly schema: ResultSetSchema;
|
||||
name: string;
|
||||
interpretation: InterpretationT<T>;
|
||||
};
|
||||
@@ -74,7 +77,7 @@ export type SarifInterpretationData = {
|
||||
* they appear in the sarif file.
|
||||
*/
|
||||
sortState?: InterpretedResultsSortState;
|
||||
} & sarif.Log;
|
||||
} & Log;
|
||||
|
||||
export type GraphInterpretationData = {
|
||||
t: "GraphInterpretationData";
|
||||
@@ -208,7 +211,7 @@ export type FromResultsViewMsg =
|
||||
*/
|
||||
interface ViewSourceFileMsg {
|
||||
t: "viewSourceFile";
|
||||
loc: ResolvableLocationValue;
|
||||
loc: UrlValueResolvable;
|
||||
databaseUri: string;
|
||||
}
|
||||
|
||||
@@ -334,13 +337,15 @@ interface ChangeCompareMessage {
|
||||
newResultSetName: string;
|
||||
}
|
||||
|
||||
export type ToCompareViewMessage = SetComparisonsMessage;
|
||||
export type ToCompareViewMessage =
|
||||
| SetComparisonQueryInfoMessage
|
||||
| SetComparisonsMessage;
|
||||
|
||||
/**
|
||||
* Message to the compare view that specifies the query results to compare.
|
||||
* Message to the compare view that sets the metadata of the compared queries.
|
||||
*/
|
||||
export interface SetComparisonsMessage {
|
||||
readonly t: "setComparisons";
|
||||
export interface SetComparisonQueryInfoMessage {
|
||||
readonly t: "setComparisonQueryInfo";
|
||||
readonly stats: {
|
||||
fromQuery?: {
|
||||
name: string;
|
||||
@@ -353,26 +358,44 @@ export interface SetComparisonsMessage {
|
||||
time: string;
|
||||
};
|
||||
};
|
||||
readonly columns: readonly Column[];
|
||||
readonly commonResultSetNames: string[];
|
||||
readonly currentResultSetName: string;
|
||||
readonly rows: QueryCompareResult | undefined;
|
||||
readonly message: string | undefined;
|
||||
readonly databaseUri: string;
|
||||
readonly commonResultSetNames: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to the compare view that specifies the query results to compare.
|
||||
*/
|
||||
export interface SetComparisonsMessage {
|
||||
readonly t: "setComparisons";
|
||||
readonly currentResultSetName: string;
|
||||
readonly result: QueryCompareResult | undefined;
|
||||
readonly message: string | undefined;
|
||||
}
|
||||
|
||||
export type QueryCompareResult =
|
||||
| RawQueryCompareResult
|
||||
| InterpretedQueryCompareResult;
|
||||
|
||||
/**
|
||||
* from is the set of rows that have changes in the "from" query.
|
||||
* to is the set of rows that have changes in the "to" query.
|
||||
* They are in the same order, so element 1 in "from" corresponds to
|
||||
* element 1 in "to".
|
||||
*
|
||||
* If an array element is null, that means that the element was removed
|
||||
* (or added) in the comparison.
|
||||
*/
|
||||
export type QueryCompareResult = {
|
||||
from: ResultRow[];
|
||||
to: ResultRow[];
|
||||
export type RawQueryCompareResult = {
|
||||
kind: "raw";
|
||||
columns: readonly Column[];
|
||||
from: Row[];
|
||||
to: Row[];
|
||||
};
|
||||
|
||||
/**
|
||||
* from is the set of results that have changes in the "from" query.
|
||||
* to is the set of results that have changes in the "to" query.
|
||||
*/
|
||||
export type InterpretedQueryCompareResult = {
|
||||
kind: "interpreted";
|
||||
sourceLocationPrefix: string;
|
||||
from: Result[];
|
||||
to: Result[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -592,13 +615,19 @@ interface RevealMethodMessage {
|
||||
methodSignature: string;
|
||||
}
|
||||
|
||||
interface SetAccessPathSuggestionsMessage {
|
||||
t: "setAccessPathSuggestions";
|
||||
accessPathSuggestions: AccessPathSuggestionOptions;
|
||||
}
|
||||
|
||||
export type ToModelEditorMessage =
|
||||
| SetExtensionPackStateMessage
|
||||
| SetMethodsMessage
|
||||
| SetModeledMethodsMessage
|
||||
| SetModifiedMethodsMessage
|
||||
| SetInProgressMethodsMessage
|
||||
| RevealMethodMessage;
|
||||
| RevealMethodMessage
|
||||
| SetAccessPathSuggestionsMessage;
|
||||
|
||||
export type FromModelEditorMessage =
|
||||
| CommonFromViewMessages
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Memento } from "./memento";
|
||||
import type { Memento } from "./memento";
|
||||
|
||||
/**
|
||||
* Provides a utility method to invoke a function only if a minimum time interval has elapsed since
|
||||
|
||||
@@ -10,9 +10,9 @@ import { readFile } from "fs-extra";
|
||||
* @param path The path to the file.
|
||||
* @param handler Callback to be invoked for each top-level JSON object in order.
|
||||
*/
|
||||
export async function readJsonlFile(
|
||||
export async function readJsonlFile<T>(
|
||||
path: string,
|
||||
handler: (value: any) => Promise<void>,
|
||||
handler: (value: T) => Promise<void>,
|
||||
): Promise<void> {
|
||||
const logSummary = await readFile(path, "utf-8");
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function readJsonlFile(
|
||||
const jsonSummaryObjects: string[] = logSummary.split(/\r?\n\r?\n/g);
|
||||
|
||||
for (const obj of jsonSummaryObjects) {
|
||||
const jsonObj = JSON.parse(obj);
|
||||
const jsonObj = JSON.parse(obj) as T;
|
||||
await handler(jsonObj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FileLink } from "../variant-analysis/shared/analysis-result";
|
||||
import type { FileLink } from "../variant-analysis/shared/analysis-result";
|
||||
|
||||
export function createRemoteFileRef(
|
||||
fileLink: FileLink,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Logger } from "./logger";
|
||||
import type { Logger } from "./logger";
|
||||
|
||||
export interface NotificationLogger extends Logger {
|
||||
showErrorMessage(message: string): Promise<void>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NotificationLogger } from "./notification-logger";
|
||||
import { AppTelemetry } from "../telemetry";
|
||||
import { RedactableError } from "../errors";
|
||||
import type { NotificationLogger } from "./notification-logger";
|
||||
import type { AppTelemetry } from "../telemetry";
|
||||
import type { RedactableError } from "../errors";
|
||||
|
||||
interface ShowAndLogOptions {
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { appendFile, ensureFile } from "fs-extra";
|
||||
import { isAbsolute } from "path";
|
||||
import { getErrorMessage } from "../helpers-pure";
|
||||
import { Logger, LogOptions } from "./logger";
|
||||
import type { Logger, LogOptions } from "./logger";
|
||||
|
||||
/**
|
||||
* An implementation of {@link Logger} that sends the output both to another {@link Logger}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const extLogger = new OutputChannelLogger("CodeQL Extension Log");
|
||||
export const queryServerLogger = new OutputChannelLogger("CodeQL Query Server");
|
||||
|
||||
// Logger for messages from the language server.
|
||||
export const ideServerLogger = new OutputChannelLogger(
|
||||
export const languageServerLogger = new OutputChannelLogger(
|
||||
"CodeQL Language Server",
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { window as Window, OutputChannel, Progress } from "vscode";
|
||||
import { Logger, LogOptions } from "../logger";
|
||||
import type { OutputChannel, Progress } from "vscode";
|
||||
import { window as Window } from "vscode";
|
||||
import type { Logger, LogOptions } from "../logger";
|
||||
import { DisposableObject } from "../../disposable-object";
|
||||
import { NotificationLogger } from "../notification-logger";
|
||||
import type { NotificationLogger } from "../notification-logger";
|
||||
|
||||
/**
|
||||
* A logger that writes messages to an output channel in the VS Code Output tab.
|
||||
@@ -63,7 +64,7 @@ export class OutputChannelLogger
|
||||
message: string,
|
||||
show: (message: string, ...items: string[]) => Thenable<string | undefined>,
|
||||
): Promise<void> {
|
||||
const label = "Show Log";
|
||||
const label = "View extension logs";
|
||||
const result = await show(message, label);
|
||||
|
||||
if (result === label) {
|
||||
|
||||
@@ -40,5 +40,5 @@ export interface Memento {
|
||||
* @param key A string.
|
||||
* @param value A value. MUST not contain cyclic references.
|
||||
*/
|
||||
update(key: string, value: any): Thenable<void>;
|
||||
update<T>(key: string, value: T | undefined): Thenable<void>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Repository } from "../../variant-analysis/gh-api/repository";
|
||||
import {
|
||||
import type { Repository } from "../../variant-analysis/gh-api/repository";
|
||||
import type {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisRepoTask,
|
||||
} from "../../variant-analysis/gh-api/variant-analysis";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { join, resolve } from "path";
|
||||
import { pathExists } from "fs-extra";
|
||||
import { setupServer, SetupServer } from "msw/node";
|
||||
import type { SetupServer } from "msw/node";
|
||||
import { setupServer } from "msw/node";
|
||||
|
||||
import { DisposableObject } from "../disposable-object";
|
||||
|
||||
|
||||
@@ -2,24 +2,24 @@ import { ensureDir, writeFile } from "fs-extra";
|
||||
import { join } from "path";
|
||||
|
||||
import fetch from "node-fetch";
|
||||
import { SetupServer } from "msw/node";
|
||||
import type { SetupServer } from "msw/node";
|
||||
|
||||
import { DisposableObject } from "../disposable-object";
|
||||
import { gzipDecode } from "../zlib";
|
||||
|
||||
import {
|
||||
import type {
|
||||
AutoModelResponse,
|
||||
BasicErrorResponse,
|
||||
CodeSearchResponse,
|
||||
GetVariantAnalysisRepoResultRequest,
|
||||
GitHubApiRequest,
|
||||
RequestKind,
|
||||
} from "./gh-api-request";
|
||||
import {
|
||||
import { RequestKind } from "./gh-api-request";
|
||||
import type {
|
||||
VariantAnalysis,
|
||||
VariantAnalysisRepoTask,
|
||||
} from "../../variant-analysis/gh-api/variant-analysis";
|
||||
import { Repository } from "../../variant-analysis/gh-api/repository";
|
||||
import type { Repository } from "../../variant-analysis/gh-api/repository";
|
||||
|
||||
export class Recorder extends DisposableObject {
|
||||
private currentRecordedScenario: GitHubApiRequest[] = [];
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { join } from "path";
|
||||
import { readdir, readJson, readFile } from "fs-extra";
|
||||
import { http, RequestHandler } from "msw";
|
||||
import type { RequestHandler } from "msw";
|
||||
import { http } from "msw";
|
||||
import type { GitHubApiRequest } from "./gh-api-request";
|
||||
import {
|
||||
GitHubApiRequest,
|
||||
isAutoModelRequest,
|
||||
isCodeSearchRequest,
|
||||
isGetRepoRequest,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { pathExists } from "fs-extra";
|
||||
import { env, QuickPickItem, Uri, window } from "vscode";
|
||||
import type { QuickPickItem } from "vscode";
|
||||
import { env, Uri, window } from "vscode";
|
||||
|
||||
import {
|
||||
getMockGitHubApiServerScenariosPath,
|
||||
@@ -7,8 +8,9 @@ import {
|
||||
} from "../../../config";
|
||||
import { DisposableObject } from "../../disposable-object";
|
||||
import { MockGitHubApiServer } from "../mock-gh-api-server";
|
||||
import { MockGitHubApiServerCommands } from "../../commands";
|
||||
import { App, AppMode } from "../../app";
|
||||
import type { MockGitHubApiServerCommands } from "../../commands";
|
||||
import type { App } from "../../app";
|
||||
import { AppMode } from "../../app";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as Octokit from "@octokit/rest";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { retry } from "@octokit/plugin-retry";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
export const AppOctokit = Octokit.Octokit.defaults({
|
||||
export const AppOctokit = Octokit.defaults({
|
||||
request: {
|
||||
fetch,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { join } from "path";
|
||||
import { dirname, join, parse } from "path";
|
||||
import { pathExists } from "fs-extra";
|
||||
|
||||
export const QLPACK_FILENAMES = ["qlpack.yml", "codeql-pack.yml"];
|
||||
@@ -8,7 +8,13 @@ export const QLPACK_LOCK_FILENAMES = [
|
||||
];
|
||||
export const FALLBACK_QLPACK_FILENAME = QLPACK_FILENAMES[0];
|
||||
|
||||
export async function getQlPackPath(
|
||||
/**
|
||||
* Gets the path to the QL pack file (a qlpack.yml or
|
||||
* codeql-pack.yml).
|
||||
* @param packRoot The root of the pack.
|
||||
* @returns The path to the qlpack file, or undefined if it doesn't exist.
|
||||
*/
|
||||
export async function getQlPackFilePath(
|
||||
packRoot: string,
|
||||
): Promise<string | undefined> {
|
||||
for (const filename of QLPACK_FILENAMES) {
|
||||
@@ -21,3 +27,28 @@ export async function getQlPackPath(
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively find the directory containing qlpack.yml or codeql-pack.yml. If
|
||||
* no such directory is found, the directory containing the query file is returned.
|
||||
* @param queryFile The query file to start from.
|
||||
* @returns The path to the pack root or undefined if it doesn't exist.
|
||||
*/
|
||||
export async function findPackRoot(
|
||||
queryFile: string,
|
||||
): Promise<string | undefined> {
|
||||
let dir = dirname(queryFile);
|
||||
while (!(await getQlPackFilePath(dir))) {
|
||||
dir = dirname(dir);
|
||||
if (isFileSystemRoot(dir)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
function isFileSystemRoot(dir: string): boolean {
|
||||
const pathObj = parse(dir);
|
||||
return pathObj.root === dir && pathObj.base === "";
|
||||
}
|
||||
|
||||
26
extensions/ql-vscode/src/common/qlpack-language.ts
Normal file
26
extensions/ql-vscode/src/common/qlpack-language.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { QueryLanguage } from "./query-language";
|
||||
import { loadQlpackFile } from "../packaging/qlpack-file-loader";
|
||||
|
||||
/**
|
||||
* @param qlpackPath The path to the `qlpack.yml` or `codeql-pack.yml` file.
|
||||
* @return the language of the given qlpack file, or undefined if the file is
|
||||
* not a valid qlpack file or does not contain exactly one language.
|
||||
*/
|
||||
export async function getQlPackLanguage(
|
||||
qlpackPath: string,
|
||||
): Promise<QueryLanguage | undefined> {
|
||||
const qlPack = await loadQlpackFile(qlpackPath);
|
||||
const dependencies = qlPack?.dependencies;
|
||||
if (!dependencies) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchingLanguages = Object.values(QueryLanguage).filter(
|
||||
(language) => `codeql/${language}-all` in dependencies,
|
||||
);
|
||||
if (matchingLanguages.length !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return matchingLanguages[0];
|
||||
}
|
||||
90
extensions/ql-vscode/src/common/raw-result-types.ts
Normal file
90
extensions/ql-vscode/src/common/raw-result-types.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
export enum ColumnKind {
|
||||
String = "string",
|
||||
Float = "float",
|
||||
Integer = "integer",
|
||||
Boolean = "boolean",
|
||||
Date = "date",
|
||||
Entity = "entity",
|
||||
}
|
||||
|
||||
export type Column = {
|
||||
name?: string;
|
||||
kind: ColumnKind;
|
||||
};
|
||||
|
||||
type UrlValueString = {
|
||||
type: "string";
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type UrlValueWholeFileLocation = {
|
||||
type: "wholeFileLocation";
|
||||
uri: string;
|
||||
};
|
||||
|
||||
export type UrlValueLineColumnLocation = {
|
||||
type: "lineColumnLocation";
|
||||
uri: string;
|
||||
startLine: number;
|
||||
startColumn: number;
|
||||
endLine: number;
|
||||
endColumn: number;
|
||||
};
|
||||
|
||||
export type UrlValueResolvable =
|
||||
| UrlValueWholeFileLocation
|
||||
| UrlValueLineColumnLocation;
|
||||
|
||||
export function isUrlValueResolvable(
|
||||
value: UrlValue,
|
||||
): value is UrlValueResolvable {
|
||||
return (
|
||||
value.type === "wholeFileLocation" || value.type === "lineColumnLocation"
|
||||
);
|
||||
}
|
||||
|
||||
export type UrlValue = UrlValueString | UrlValueResolvable;
|
||||
|
||||
export type EntityValue = {
|
||||
url?: UrlValue;
|
||||
label?: string;
|
||||
id?: number;
|
||||
};
|
||||
|
||||
type CellValueEntity = {
|
||||
type: "entity";
|
||||
value: EntityValue;
|
||||
};
|
||||
|
||||
type CellValueNumber = {
|
||||
type: "number";
|
||||
value: number;
|
||||
};
|
||||
|
||||
type CellValueString = {
|
||||
type: "string";
|
||||
value: string;
|
||||
};
|
||||
|
||||
type CellValueBoolean = {
|
||||
type: "boolean";
|
||||
value: boolean;
|
||||
};
|
||||
|
||||
export type CellValue =
|
||||
| CellValueEntity
|
||||
| CellValueNumber
|
||||
| CellValueString
|
||||
| CellValueBoolean;
|
||||
|
||||
export type Row = CellValue[];
|
||||
|
||||
export type RawResultSet = {
|
||||
name: string;
|
||||
totalRowCount: number;
|
||||
|
||||
columns: Column[];
|
||||
rows: Row[];
|
||||
|
||||
nextPageOffset?: number;
|
||||
};
|
||||
@@ -1,11 +1,12 @@
|
||||
export type DeepReadonly<T> = T extends Array<infer R>
|
||||
? DeepReadonlyArray<R>
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-types
|
||||
T extends Function
|
||||
? T
|
||||
: T extends object
|
||||
? DeepReadonlyObject<T>
|
||||
: T;
|
||||
export type DeepReadonly<T> =
|
||||
T extends Array<infer R>
|
||||
? DeepReadonlyArray<R>
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-types
|
||||
T extends Function
|
||||
? T
|
||||
: T extends object
|
||||
? DeepReadonlyObject<T>
|
||||
: T;
|
||||
|
||||
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as Sarif from "sarif";
|
||||
import type { Log, Tool } from "sarif";
|
||||
import { createReadStream } from "fs-extra";
|
||||
import { connectTo } from "stream-json/Assembler";
|
||||
import { getErrorMessage } from "./helpers-pure";
|
||||
import { withParser } from "stream-json/filters/Pick";
|
||||
|
||||
const DUMMY_TOOL: Sarif.Tool = { driver: { name: "" } };
|
||||
const DUMMY_TOOL: Tool = { driver: { name: "" } };
|
||||
|
||||
export async function sarifParser(
|
||||
interpretedResultsPath: string,
|
||||
): Promise<Sarif.Log> {
|
||||
): Promise<Log> {
|
||||
try {
|
||||
// Parse the SARIF file into token streams, filtering out only the results array.
|
||||
const pipeline = createReadStream(interpretedResultsPath).pipe(
|
||||
@@ -38,7 +38,7 @@ export async function sarifParser(
|
||||
});
|
||||
|
||||
asm.on("done", (asm) => {
|
||||
const log: Sarif.Log = {
|
||||
const log: Log = {
|
||||
version: "2.1.0",
|
||||
runs: [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as Sarif from "sarif";
|
||||
import type { Location, Region } from "sarif";
|
||||
import type { HighlightedRegion } from "../variant-analysis/shared/analysis-result";
|
||||
import { ResolvableLocationValue } from "../common/bqrs-cli-types";
|
||||
import type { UrlValueResolvable } from "./raw-result-types";
|
||||
import { isEmptyPath } from "./bqrs-utils";
|
||||
|
||||
export interface SarifLink {
|
||||
@@ -16,7 +16,7 @@ interface NoLocation {
|
||||
}
|
||||
|
||||
type ParsedSarifLocation =
|
||||
| (ResolvableLocationValue & {
|
||||
| (UrlValueResolvable & {
|
||||
userVisibleFile: string;
|
||||
})
|
||||
// Resolvable locations have a `uri` field, but it will sometimes include
|
||||
@@ -47,7 +47,7 @@ export function parseSarifPlainTextMessage(
|
||||
// Technically we could have any uri in the target but we don't output that yet.
|
||||
// The possibility of escaping outside the link is not mentioned in the sarif spec but we always output sartif this way.
|
||||
const linkRegex =
|
||||
/(?<=(?<!\\)(\\\\)*)\[(?<linkText>([^\\\]\[]|\\\\|\\\]|\\\[)*)\]\((?<linkTarget>[0-9]+)\)/g;
|
||||
/(?<=(?<!\\)(\\\\)*)\[(?<linkText>([^\\\][]|\\\\|\\\]|\\\[)*)\]\((?<linkTarget>[0-9]+)\)/g;
|
||||
let result: RegExpExecArray | null;
|
||||
let curIndex = 0;
|
||||
while ((result = linkRegex.exec(message)) !== null) {
|
||||
@@ -103,15 +103,19 @@ export function getPathRelativeToSourceLocationPrefix(
|
||||
* @param sourceLocationPrefix a file path (usually a full path) to the database containing the source location.
|
||||
*/
|
||||
export function parseSarifLocation(
|
||||
loc: Sarif.Location,
|
||||
loc: Location,
|
||||
sourceLocationPrefix: string,
|
||||
): ParsedSarifLocation {
|
||||
const physicalLocation = loc.physicalLocation;
|
||||
if (physicalLocation === undefined) return { hint: "no physical location" };
|
||||
if (physicalLocation.artifactLocation === undefined)
|
||||
if (physicalLocation === undefined) {
|
||||
return { hint: "no physical location" };
|
||||
}
|
||||
if (physicalLocation.artifactLocation === undefined) {
|
||||
return { hint: "no artifact location" };
|
||||
if (physicalLocation.artifactLocation.uri === undefined)
|
||||
}
|
||||
if (physicalLocation.artifactLocation.uri === undefined) {
|
||||
return { hint: "artifact location has no uri" };
|
||||
}
|
||||
if (isEmptyPath(physicalLocation.artifactLocation.uri)) {
|
||||
return { hint: "artifact location has empty uri" };
|
||||
}
|
||||
@@ -133,6 +137,7 @@ export function parseSarifLocation(
|
||||
// If the region property is absent, the physicalLocation object refers to the entire file.
|
||||
// Source: https://docs.oasis-open.org/sarif/sarif/v2.1.0/cs01/sarif-v2.1.0-cs01.html#_Toc16012638.
|
||||
return {
|
||||
type: "wholeFileLocation",
|
||||
uri: effectiveLocation,
|
||||
userVisibleFile,
|
||||
} as ParsedSarifLocation;
|
||||
@@ -140,6 +145,7 @@ export function parseSarifLocation(
|
||||
const region = parseSarifRegion(physicalLocation.region);
|
||||
|
||||
return {
|
||||
type: "lineColumnLocation",
|
||||
uri: effectiveLocation,
|
||||
userVisibleFile,
|
||||
...region,
|
||||
@@ -147,7 +153,7 @@ export function parseSarifLocation(
|
||||
}
|
||||
}
|
||||
|
||||
export function parseSarifRegion(region: Sarif.Region): {
|
||||
export function parseSarifRegion(region: Region): {
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
startColumn: number;
|
||||
@@ -228,14 +234,14 @@ export function parseHighlightedLine(
|
||||
const highlightStartColumn = isSingleLineHighlight
|
||||
? highlightedRegion.startColumn
|
||||
: isFirstHighlightedLine
|
||||
? highlightedRegion.startColumn
|
||||
: 0;
|
||||
? highlightedRegion.startColumn
|
||||
: 0;
|
||||
|
||||
const highlightEndColumn = isSingleLineHighlight
|
||||
? highlightedRegion.endColumn
|
||||
: isLastHighlightedLine
|
||||
? highlightedRegion.endColumn
|
||||
: line.length + 1;
|
||||
? highlightedRegion.endColumn
|
||||
: line.length + 1;
|
||||
|
||||
const plainSection1 = line.substring(0, highlightStartColumn - 1);
|
||||
const highlightedSection = line.substring(
|
||||
|
||||
117
extensions/ql-vscode/src/common/short-paths.ts
Normal file
117
extensions/ql-vscode/src/common/short-paths.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { platform } from "os";
|
||||
import { basename, dirname, join, normalize, resolve } from "path";
|
||||
import { lstat, readdir } from "fs/promises";
|
||||
import type { BaseLogger } from "./logging";
|
||||
|
||||
/**
|
||||
* Expands a path that potentially contains 8.3 short names (e.g. "C:\PROGRA~1" instead of "C:\Program Files").
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/8.3_filename if you're not familiar with Windows 8.3 short names.
|
||||
*
|
||||
* @param shortPath The path to expand.
|
||||
* @returns A normalized, absolute path, with any short components expanded.
|
||||
*/
|
||||
export async function expandShortPaths(
|
||||
shortPath: string,
|
||||
logger: BaseLogger,
|
||||
): Promise<string> {
|
||||
const absoluteShortPath = normalize(resolve(shortPath));
|
||||
if (platform() !== "win32") {
|
||||
// POSIX doesn't have short paths.
|
||||
return absoluteShortPath;
|
||||
}
|
||||
|
||||
void logger.log(`Expanding short paths in: ${absoluteShortPath}`);
|
||||
// A quick check to see if there might be any short components.
|
||||
// There might be a case where a short component doesn't contain a `~`, but if there is, I haven't
|
||||
// found it.
|
||||
// This may find long components that happen to have a '~', but that's OK.
|
||||
if (absoluteShortPath.indexOf("~") < 0) {
|
||||
// No short components to expand.
|
||||
void logger.log(`Skipping due to no short components`);
|
||||
return absoluteShortPath;
|
||||
}
|
||||
|
||||
return await expandShortPathRecursive(absoluteShortPath, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a single short path component
|
||||
* @param dir The absolute path of the directory containing the short path component.
|
||||
* @param shortBase The shot path component to expand.
|
||||
* @returns The expanded path component.
|
||||
*/
|
||||
async function expandShortPathComponent(
|
||||
dir: string,
|
||||
shortBase: string,
|
||||
logger: BaseLogger,
|
||||
): Promise<string> {
|
||||
void logger.log(`Expanding short path component: ${shortBase}`);
|
||||
|
||||
const fullPath = join(dir, shortBase);
|
||||
|
||||
// Use `lstat` instead of `stat` to avoid following symlinks.
|
||||
const stats = await lstat(fullPath, { bigint: true });
|
||||
if (stats.dev === BigInt(0) || stats.ino === BigInt(0)) {
|
||||
// No inode info, so we won't be able to find this in the directory listing.
|
||||
void logger.log(`No inode info available. Skipping.`);
|
||||
return shortBase;
|
||||
}
|
||||
void logger.log(`dev/inode: ${stats.dev}/${stats.ino}`);
|
||||
|
||||
try {
|
||||
// Enumerate the children of the parent directory, and try to find one with the same dev/inode.
|
||||
const children = await readdir(dir);
|
||||
for (const child of children) {
|
||||
void logger.log(`considering child: ${child}`);
|
||||
try {
|
||||
const childStats = await lstat(join(dir, child), { bigint: true });
|
||||
void logger.log(`child dev/inode: ${childStats.dev}/${childStats.ino}`);
|
||||
if (childStats.dev === stats.dev && childStats.ino === stats.ino) {
|
||||
// Found a match.
|
||||
void logger.log(`Found a match: ${child}`);
|
||||
return child;
|
||||
}
|
||||
} catch (e) {
|
||||
// Can't read stats for the child, so skip it.
|
||||
void logger.log(`Error reading stats for child: ${e}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Can't read the directory, so we won't be able to find this in the directory listing.
|
||||
void logger.log(`Error reading directory: ${e}`);
|
||||
return shortBase;
|
||||
}
|
||||
|
||||
void logger.log(`No match found. Returning original.`);
|
||||
return shortBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the short path components in a path, including those in ancestor directories.
|
||||
* @param shortPath The path to expand.
|
||||
* @returns The expanded path.
|
||||
*/
|
||||
async function expandShortPathRecursive(
|
||||
shortPath: string,
|
||||
logger: BaseLogger,
|
||||
): Promise<string> {
|
||||
const shortBase = basename(shortPath);
|
||||
if (shortBase.length === 0) {
|
||||
// We've reached the root.
|
||||
return shortPath;
|
||||
}
|
||||
|
||||
const dir = await expandShortPathRecursive(dirname(shortPath), logger);
|
||||
void logger.log(`dir: ${dir}`);
|
||||
void logger.log(`base: ${shortBase}`);
|
||||
if (shortBase.indexOf("~") < 0) {
|
||||
// This component doesn't have a short name, so just append it to the (long) parent.
|
||||
void logger.log(`Component is not a short name`);
|
||||
return join(dir, shortBase);
|
||||
}
|
||||
|
||||
// This component looks like it has a short name, so try to expand it.
|
||||
const longBase = await expandShortPathComponent(dir, shortBase, logger);
|
||||
return join(dir, longBase);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user