Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b38b884715 | ||
|
|
cc6f2d8886 | ||
|
|
197ab99db8 | ||
|
|
6292adf491 | ||
|
|
112d40ff1c | ||
|
|
b92d6bab7c | ||
|
|
0a4879c9a8 | ||
|
|
7d4d57104a | ||
|
|
f06c9abb35 | ||
|
|
85eaa8b275 | ||
|
|
21dda65871 | ||
|
|
39fdd0cad5 | ||
|
|
3fb2c71390 | ||
|
|
b40f648a87 | ||
|
|
57216249c2 | ||
|
|
fbadc15ae9 | ||
|
|
89b00eaef8 | ||
|
|
4bc5086cfb | ||
|
|
7a79d39e23 | ||
|
|
41ae5a4b5f | ||
|
|
0493e316c0 | ||
|
|
137e17c2e1 | ||
|
|
31db2ffb82 | ||
|
|
df18ff3052 | ||
|
|
74555510b4 | ||
|
|
a2b8e7d193 | ||
|
|
b59638bd2e | ||
|
|
b0e19926da | ||
|
|
2e1b83588c | ||
|
|
ab441ef75c | ||
|
|
b4478e9b54 | ||
|
|
a715ce13c9 | ||
|
|
005372abba | ||
|
|
3f22587a7c | ||
|
|
b95533e8c0 | ||
|
|
210d8a3c64 | ||
|
|
c2d3829a72 | ||
|
|
cd427ee119 | ||
|
|
ad4c30ecf8 | ||
|
|
db7f5f5114 | ||
|
|
7c9fa03da8 | ||
|
|
615dd691bf | ||
|
|
64ba2cabad | ||
|
|
a9dcb2d705 | ||
|
|
4c81cdec98 | ||
|
|
db529d5247 | ||
|
|
4f568ea331 | ||
|
|
6d41362251 | ||
|
|
7f65a54060 | ||
|
|
0c6ca81437 | ||
|
|
b2422216b5 | ||
|
|
71f374d797 | ||
|
|
7e78a6bc5c | ||
|
|
a4532fdc61 | ||
|
|
7c5135d7d0 | ||
|
|
cdd6738748 | ||
|
|
6f16192865 | ||
|
|
8151739f87 | ||
|
|
72fc53ba9c | ||
|
|
3e6ee01c4e | ||
|
|
f6485dac95 | ||
|
|
48f15b5fc7 | ||
|
|
f856e3ac2c | ||
|
|
38a64017f2 | ||
|
|
20b15b6e1d | ||
|
|
e119218828 | ||
|
|
f494988ba6 | ||
|
|
2561db1721 | ||
|
|
089b23f0aa | ||
|
|
fbed7dd1ca | ||
|
|
06ef67f22d | ||
|
|
3d647f68e1 | ||
|
|
6a36dc34cc | ||
|
|
b48aaeac7b | ||
|
|
2da1065027 | ||
|
|
3536124fbc | ||
|
|
10b4e08bf8 | ||
|
|
b1f426672c | ||
|
|
087cae287f | ||
|
|
3d8032c9b7 | ||
|
|
6470238311 | ||
|
|
0093af8994 | ||
|
|
2bfcd119db | ||
|
|
5932bdba96 | ||
|
|
1afe6b56fa | ||
|
|
72776e8254 | ||
|
|
d2d1a09723 | ||
|
|
793b82333f | ||
|
|
b3abff3e88 | ||
|
|
890549f9e7 | ||
|
|
66825d6a37 | ||
|
|
d42982ee4c | ||
|
|
7df634f050 | ||
|
|
46606aa7b5 | ||
|
|
de5704974d | ||
|
|
977b061048 | ||
|
|
560f694f73 | ||
|
|
7a58d360fd | ||
|
|
9601d6c140 | ||
|
|
db66184c35 | ||
|
|
93e7daea49 | ||
|
|
1a18c6d056 | ||
|
|
7eb12e0004 | ||
|
|
d3192b7e3b | ||
|
|
e7ab2969d7 | ||
|
|
49a35343f6 | ||
|
|
c361671e36 | ||
|
|
b71452b87c | ||
|
|
06170f9713 | ||
|
|
920515c071 | ||
|
|
6a124685bd | ||
|
|
75f76ecd23 | ||
|
|
5a0b1b290f |
46
.github/workflows/main.yml
vendored
46
.github/workflows/main.yml
vendored
@@ -2,6 +2,7 @@ name: Build Extension
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -50,12 +51,30 @@ jobs:
|
||||
name: vscode-codeql-extension
|
||||
path: artifacts
|
||||
|
||||
find-nightly:
|
||||
name: Find Nightly Release
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
url: ${{ steps.get-url.outputs.nightly-url }}
|
||||
steps:
|
||||
- name: Get Nightly Release URL
|
||||
id: get-url
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
shell: bash
|
||||
# This workflow step gets an unstable testing version of the CodeQL CLI. It should not be used outside of these tests.
|
||||
run: |
|
||||
LATEST=`gh api repos/dsp-testing/codeql-cli-nightlies/releases --jq '.[].tag_name' --method GET --raw-field 'per_page=1'`
|
||||
echo "::set-output name=nightly-url::https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/$LATEST"
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [find-nightly]
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
version: [stable, nightly]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -88,7 +107,12 @@ jobs:
|
||||
- name: Install CodeQL
|
||||
run: |
|
||||
mkdir codeql-home
|
||||
curl -L --silent https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql.zip -o codeql-home/codeql.zip
|
||||
if [ ${{ matrix.version }} = "stable" ]
|
||||
then
|
||||
curl -L --silent https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql.zip -o codeql-home/codeql.zip
|
||||
else
|
||||
curl -L --silent ${{ needs.find-nightly.outputs.url }}/codeql.zip -o codeql-home/codeql.zip
|
||||
fi
|
||||
unzip -q -o codeql-home/codeql.zip -d codeql-home
|
||||
unzip -q -o codeql-home/codeql.zip codeql/codeql.exe -d codeql-home
|
||||
rm codeql-home/codeql.zip
|
||||
@@ -123,12 +147,14 @@ jobs:
|
||||
cli-test:
|
||||
name: CLI Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [find-nightly]
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
version: ['v2.2.6', 'v2.3.3', 'v2.4.6', 'v2.5.5']
|
||||
version: ['v2.3.3', 'v2.4.6', 'v2.5.9', 'v2.6.3', 'nightly']
|
||||
env:
|
||||
CLI_VERSION: ${{ matrix.version }}
|
||||
NIGHTLY_URL: ${{ needs.find-nightly.outputs.url }}
|
||||
TEST_CODEQL_PATH: '${{ github.workspace }}/codeql'
|
||||
|
||||
steps:
|
||||
@@ -151,10 +177,26 @@ jobs:
|
||||
npm run build
|
||||
shell: bash
|
||||
|
||||
- name: Decide on ref of CodeQL repo
|
||||
id: choose-ref
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ matrix.version }}" == "nightly" ]]
|
||||
then
|
||||
REF="codeql-cli/latest"
|
||||
elif [[ "${{ matrix.version }}" == "v2.2.6" || "${{ matrix.version }}" == "v2.3.3" ]]
|
||||
then
|
||||
REF="codeql-cli/v2.4.5"
|
||||
else
|
||||
REF="codeql-cli/${{ matrix.version }}"
|
||||
fi
|
||||
echo "::set-output name=ref::$REF"
|
||||
|
||||
- name: Checkout QL
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: github/codeql
|
||||
ref: ${{ steps.choose-ref.outputs.ref }}
|
||||
path: codeql
|
||||
|
||||
- name: Run CLI tests (Linux)
|
||||
|
||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -6,10 +6,6 @@
|
||||
|
||||
name: Release
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/workflows/release.yml'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
push:
|
||||
@@ -129,6 +125,7 @@ jobs:
|
||||
body: This PR was automatically generated by the GitHub Actions release workflow in this repository.
|
||||
branch: ${{ format('version/bump-to-{0}', steps.bump-patch-version.outputs.next_version) }}
|
||||
base: main
|
||||
draft: true
|
||||
|
||||
vscode-publish:
|
||||
name: Publish to VS Code Marketplace
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -22,7 +22,8 @@
|
||||
"common/temp": true,
|
||||
"**/.vscode-test": true
|
||||
},
|
||||
"typescript.tsdk": "./common/temp/node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
|
||||
"typescript.tsdk": "./extensions/ql-vscode/node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
|
||||
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
||||
**/* @github/codeql-vscode-reviewers
|
||||
@@ -1,5 +1,42 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## 1.5.6 - 07 October 2021
|
||||
|
||||
- Add progress messages to LGTM download option. This makes the two-step process (selecting a project, then selecting a language) more clear. [#960](https://github.com/github/vscode-codeql/pull/960)
|
||||
- Remove line about selecting a language from the dropdown when downloading database from LGTM. This makes the download progress visible when the popup is not expanded. [#957](https://github.com/github/vscode-codeql/pull/957)
|
||||
- Fix a bug where copying the version information fails when a CodeQL CLI cannot be found. [#958](https://github.com/github/vscode-codeql/pull/958)
|
||||
- Avoid a race condition when deleting databases that can cause occasional errors. [#959](https://github.com/github/vscode-codeql/pull/959)
|
||||
- Update CodeQL logos. [#965](https://github.com/github/vscode-codeql/pull/965)
|
||||
|
||||
## 1.5.5 - 08 September 2021
|
||||
|
||||
- Fix bug where a query is sometimes run before the file is saved. [#947](https://github.com/github/vscode-codeql/pull/947)
|
||||
- Fix broken contextual queries, including _View AST_. [#949](https://github.com/github/vscode-codeql/pull/949)
|
||||
|
||||
## 1.5.4 - 02 September 2021
|
||||
|
||||
- Add support for filename pattern in history view. [#930](https://github.com/github/vscode-codeql/pull/930)
|
||||
- Add an option _View Results (CSV)_ to view the results of a non-alert query. The existing options for alert queries have been renamed to _View Alerts_ to avoid confusion. [#929](https://github.com/github/vscode-codeql/pull/929)
|
||||
- Allow users to specify the number of paths to display for each alert. [#931](https://github.com/github/vscode-codeql/pull/931)
|
||||
- Adjust pagination controls in _CodeQL Query Results_ to always be visible [#936](https://github.com/github/vscode-codeql/pull/936)
|
||||
- Fix bug where _View AST_ fails due to recent refactoring in the standard library and query packs. [#939](https://github.com/github/vscode-codeql/pull/939)
|
||||
|
||||
## 1.5.3 - 18 August 2021
|
||||
|
||||
- Add a command _CodeQL: Run Query on Multiple Databases_, which lets users select multiple databases to run a query on. [#898](https://github.com/github/vscode-codeql/pull/898)
|
||||
- Autodetect what language a query targets. This refines the _CodeQL: Run Query on Multiple Databases_ command to only show relevant databases. [#915](https://github.com/github/vscode-codeql/pull/915)
|
||||
- Adjust test log output to display diffs only when comparing failed test results with expected test results. [#920](https://github.com/github/vscode-codeql/pull/920)
|
||||
|
||||
## 1.5.2 - 13 July 2021
|
||||
|
||||
- Add the _Add Database Source to Workspace_ command to the right-click context menu in the databases view. This lets users re-add a database's source folder to the workspace and browse the source code. [#891](https://github.com/github/vscode-codeql/pull/891)
|
||||
- Fix markdown rendering in the description of the `codeQL.cli.executablePath` setting. [#908](https://github.com/github/vscode-codeql/pull/908)
|
||||
- Fix the _Open Query Results_ command in the query history view. [#909](https://github.com/github/vscode-codeql/pull/909)
|
||||
|
||||
## 1.5.1 - 23 June 2021
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 1.5.0 - 14 June 2021
|
||||
|
||||
- Display CodeQL CLI version being downloaded during an upgrade. [#862](https://github.com/github/vscode-codeql/pull/862)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 499 KiB After Width: | Height: | Size: 31 KiB |
@@ -1,14 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="27px" height="16px" viewBox="0 0 27 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 59 (86127) - https://sketch.com -->
|
||||
<title>Slice</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="light" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="QL" transform="translate(1.000000, 1.000000)">
|
||||
<rect id="Rectangle-41" stroke="#2088FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" x="0" y="0" width="25" height="14" rx="2"></rect>
|
||||
<line x1="17" y1="5" x2="19" y2="5" id="Stroke-15" stroke="#2088FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></line>
|
||||
<line x1="17" y1="9" x2="21" y2="9" id="Stroke-15" stroke="#2088FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></line>
|
||||
<path d="M8.85227273,7 C8.85227273,7.51894199 8.76988719,7.97537682 8.60511364,8.36931818 C8.44034009,8.76325955 8.21591051,9.08711994 7.93181818,9.34090909 L8.76420455,10.3863636 L7.61647727,10.3863636 L7.14772727,9.80965909 C6.83143781,9.92897787 6.49147909,9.98863636 6.12784091,9.98863636 C5.61079287,9.98863636 5.14678236,9.8712133 4.73579545,9.63636364 C4.32480855,9.40151398 4.00000119,9.06108178 3.76136364,8.61505682 C3.52272608,8.16903186 3.40340909,7.63068497 3.40340909,7 C3.40340909,6.36552713 3.52272608,5.8257598 3.76136364,5.38068182 C4.00000119,4.93560384 4.32480855,4.59611859 4.73579545,4.36221591 C5.14678236,4.12831322 5.61079287,4.01136364 6.12784091,4.01136364 C6.642995,4.01136364 7.10605855,4.12831322 7.51704545,4.36221591 C7.92803236,4.59611859 8.2533132,4.93560384 8.49289773,5.38068182 C8.73248226,5.8257598 8.85227273,6.36552713 8.85227273,7 Z M5.70170455,7.88636364 L6.74715909,7.88636364 L7.17897727,8.44034091 C7.31344764,8.27935526 7.41808675,8.07859969 7.49289773,7.83806818 C7.56770871,7.59753668 7.60511364,7.31818341 7.60511364,7 C7.60511364,6.38257267 7.47064528,5.91145996 7.20170455,5.58664773 C6.93276381,5.2618355 6.57481284,5.09943182 6.12784091,5.09943182 C5.68086898,5.09943182 5.32291801,5.2618355 5.05397727,5.58664773 C4.78503653,5.91145996 4.65056818,6.38257267 4.65056818,7 C4.65056818,7.61553338 4.78503653,8.08617261 5.05397727,8.41193182 C5.32291801,8.73769102 5.68086898,8.90056818 6.12784091,8.90056818 C6.23958389,8.90056818 6.34564344,8.89015162 6.44602273,8.86931818 L5.70170455,7.88636364 Z M10.1813315,10 L10.1813315,4 L11.4114451,4 L11.4114451,8.98579545 L13.9057633,8.98579545 L13.9057633,10 L10.1813315,10 Z" fill="#2088FF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.19789 8C8.19789 8.51894 8.1155 8.97538 7.95073 9.36932C7.78595 9.76326 7.56152 10.0871 7.27743 10.3409L8.10982 11.3864H6.96209L6.49334 10.8097C6.17705 10.929 5.83709 10.9886 5.47346 10.9886C4.95641 10.9886 4.4924 10.8712 4.08141 10.6364C3.67042 10.4015 3.34562 10.0611 3.10698 9.61506C2.86834 9.16903 2.74902 8.63068 2.74902 8C2.74902 7.36553 2.86834 6.82576 3.10698 6.38068C3.34562 5.9356 3.67042 5.59612 4.08141 5.36222C4.4924 5.12831 4.95641 5.01136 5.47346 5.01136C5.98861 5.01136 6.45167 5.12831 6.86266 5.36222C7.27365 5.59612 7.59893 5.9356 7.83851 6.38068C8.0781 6.82576 8.19789 7.36553 8.19789 8ZM5.04732 8.88636H6.09277L6.52459 9.44034C6.65906 9.27936 6.7637 9.0786 6.83851 8.83807C6.91332 8.59754 6.95073 8.31818 6.95073 8C6.95073 7.38257 6.81626 6.91146 6.54732 6.58665C6.27838 6.26184 5.92043 6.09943 5.47346 6.09943C5.02648 6.09943 4.66853 6.26184 4.39959 6.58665C4.13065 6.91146 3.99618 7.38257 3.99618 8C3.99618 8.61553 4.13065 9.08617 4.39959 9.41193C4.66853 9.73769 5.02648 9.90057 5.47346 9.90057C5.5852 9.90057 5.69126 9.89015 5.79164 9.86932L5.04732 8.88636ZM9.52695 11V5H10.7571V9.9858H13.2514V11H9.52695Z" fill="#24292F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 1.5H3C2.17157 1.5 1.5 2.17157 1.5 3V13C1.5 13.8284 2.17157 14.5 3 14.5H13C13.8284 14.5 14.5 13.8284 14.5 13V3C14.5 2.17157 13.8284 1.5 13 1.5ZM3 0C1.34315 0 0 1.34315 0 3V13C0 14.6569 1.34315 16 3 16H13C14.6569 16 16 14.6569 16 13V3C16 1.34315 14.6569 0 13 0H3Z" fill="#24292F"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.6 KiB |
1922
extensions/ql-vscode/package-lock.json
generated
1922
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.5.0",
|
||||
"version": "1.5.6",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -38,6 +38,7 @@
|
||||
"onView:codeQLAstViewer",
|
||||
"onView:test-explorer",
|
||||
"onCommand:codeQL.checkForUpdatesToCLI",
|
||||
"onCommand:codeQL.authenticateToGitHub",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseFolder",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseArchive",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseInternet",
|
||||
@@ -131,7 +132,7 @@
|
||||
"scope": "window",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.exe` on Windows. If empty, the extension will look for a CodeQL executable on your shell PATH, or if CodeQL is not on your PATH, download and manage its own CodeQL executable."
|
||||
"markdownDescription": "Path to the CodeQL executable that should be used by the CodeQL extension. The executable is named `codeql` on Linux/Mac and `codeql.exe` on Windows. If empty, the extension will look for a CodeQL executable on your shell PATH, or if CodeQL is not on your PATH, download and manage its own CodeQL executable."
|
||||
},
|
||||
"codeQL.runningQueries.numberOfThreads": {
|
||||
"type": "integer",
|
||||
@@ -179,6 +180,13 @@
|
||||
"default": false,
|
||||
"description": "Enable debug logging and tuple counting when running CodeQL queries. This information is useful for debugging query performance."
|
||||
},
|
||||
"codeQL.runningQueries.maxPaths": {
|
||||
"type": "integer",
|
||||
"default": 4,
|
||||
"minimum": 1,
|
||||
"maximum": 256,
|
||||
"markdownDescription": "Max number of paths to display for each alert found by a path query (`@kind path-problem`)."
|
||||
},
|
||||
"codeQL.runningQueries.autoSave": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@@ -205,7 +213,7 @@
|
||||
"codeQL.queryHistory.format": {
|
||||
"type": "string",
|
||||
"default": "%q on %d - %s, %r result count [%t]",
|
||||
"description": "Default string for how to label query history items. %t is the time of the query, %q is the query name, %d is the database name, %r is the number of results, and %s is a status string."
|
||||
"markdownDescription": "Default string for how to label query history items.\n* %t is the time of the query\n* %q is the human-readable query name\n* %f is the query file name\n* %d is the database name\n* %r is the number of results\n* %s is a status string"
|
||||
},
|
||||
"codeQL.runningTests.additionalTestArguments": {
|
||||
"scope": "window",
|
||||
@@ -232,14 +240,49 @@
|
||||
"default": false,
|
||||
"scope": "application",
|
||||
"description": "Specifies whether or not to write telemetry events to the extension log."
|
||||
},
|
||||
"codeQL.remoteQueries.repositoryLists": {
|
||||
"type": [
|
||||
"object",
|
||||
null
|
||||
],
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": null,
|
||||
"markdownDescription": "[For internal use only] Lists of GitHub repositories that you want to query remotely. This should be a JSON object where each key is a user-specified name for this repository list, and the value is an array of GitHub repositories (of the form `<owner>/<repo>`)."
|
||||
},
|
||||
"codeQL.remoteQueries.controllerRepo": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"pattern": "^$|^(?:[a-zA-Z0-9]+-)*[a-zA-Z0-9]+/[a-zA-Z0-9-_]+$",
|
||||
"patternErrorMessage": "Please enter a valid GitHub repository",
|
||||
"markdownDescription": "[For internal use only] The name of the GitHub repository where you can view the progress and results of the \"Run Remote query\" command. The repository should be of the form `<owner>/<repo>`)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "codeQL.authenticateToGitHub",
|
||||
"title": "CodeQL: Authenticate to GitHub"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQuery",
|
||||
"title": "CodeQL: Run Query"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"title": "CodeQL: Run Query on Multiple Databases"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runRemoteQuery",
|
||||
"title": "CodeQL: Run Remote Query"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueries",
|
||||
"title": "CodeQL: Run Queries in Selected Files"
|
||||
@@ -336,6 +379,10 @@
|
||||
"command": "codeQLDatabases.openDatabaseFolder",
|
||||
"title": "Show Database Directory"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.addDatabaseSource",
|
||||
"title": "Add Database Source to Workspace"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.chooseDatabaseFolder",
|
||||
"title": "CodeQL: Choose Database from Folder"
|
||||
@@ -433,8 +480,12 @@
|
||||
"title": "View Results (CSV)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifResults",
|
||||
"title": "View Results (SARIF)"
|
||||
"command": "codeQLQueryHistory.viewCsvAlerts",
|
||||
"title": "View Alerts (CSV)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifAlerts",
|
||||
"title": "View Alerts (SARIF)"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewDil",
|
||||
@@ -575,6 +626,11 @@
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLDatabases"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.addDatabaseSource",
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLDatabases"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.openQuery",
|
||||
"group": "9_qlCommands",
|
||||
@@ -608,10 +664,15 @@
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewCsvResults",
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory && viewItem != interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewCsvAlerts",
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifResults",
|
||||
"command": "codeQLQueryHistory.viewSarifAlerts",
|
||||
"group": "9_qlCommands",
|
||||
"when": "view == codeQLQueryHistory && viewItem == interpretedResultsItem"
|
||||
},
|
||||
@@ -654,10 +715,22 @@
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "codeQL.authenticateToGitHub",
|
||||
"when": "config.codeQL.canary"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQuery",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runRemoteQuery",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueries",
|
||||
"when": "false"
|
||||
@@ -690,6 +763,10 @@
|
||||
"command": "codeQLDatabases.openDatabaseFolder",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.addDatabaseSource",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.sortByName",
|
||||
"when": "false"
|
||||
@@ -751,7 +828,11 @@
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifResults",
|
||||
"command": "codeQLQueryHistory.viewCsvAlerts",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLQueryHistory.viewSarifAlerts",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
@@ -800,6 +881,14 @@
|
||||
"command": "codeQL.runQuery",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runRemoteQuery",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAst",
|
||||
"when": "resourceScheme == codeql-zip-archive"
|
||||
@@ -868,6 +957,7 @@
|
||||
"format-staged": "lint-staged"
|
||||
},
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^18.5.6",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"classnames": "~2.2.6",
|
||||
"fs-extra": "^9.0.1",
|
||||
@@ -898,7 +988,7 @@
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/google-protobuf": "^3.2.7",
|
||||
"@types/gulp": "^4.0.6",
|
||||
"@types/gulp": "^4.0.9",
|
||||
"@types/gulp-replace": "0.0.31",
|
||||
"@types/gulp-sourcemaps": "0.0.32",
|
||||
"@types/js-yaml": "^3.12.5",
|
||||
@@ -969,5 +1059,8 @@
|
||||
"tsfmt -r",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"glob-parent": "~6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
62
extensions/ql-vscode/src/authentication.ts
Normal file
62
extensions/ql-vscode/src/authentication.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as Octokit from '@octokit/rest';
|
||||
|
||||
const GITHUB_AUTH_PROVIDER_ID = 'github';
|
||||
|
||||
// 'repo' scope should be enough for triggering workflows. For a comprehensive list, see:
|
||||
// https://docs.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps
|
||||
const SCOPES = ['repo'];
|
||||
|
||||
/**
|
||||
* Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication).
|
||||
*/
|
||||
export class Credentials {
|
||||
private octokit: Octokit.Octokit | undefined;
|
||||
|
||||
// Explicitly make the constructor private, so that we can't accidentally call the constructor from outside the class
|
||||
// without also initializing the class.
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() { }
|
||||
|
||||
static async initialize(context: vscode.ExtensionContext): Promise<Credentials> {
|
||||
const c = new Credentials();
|
||||
c.registerListeners(context);
|
||||
c.octokit = await c.createOctokit(false);
|
||||
return c;
|
||||
}
|
||||
|
||||
private async createOctokit(createIfNone: boolean): Promise<Octokit.Octokit | undefined> {
|
||||
const session = await vscode.authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone });
|
||||
|
||||
if (session) {
|
||||
return new Octokit.Octokit({
|
||||
auth: session.accessToken
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
registerListeners(context: vscode.ExtensionContext): void {
|
||||
// Sessions are changed when a user logs in or logs out.
|
||||
context.subscriptions.push(vscode.authentication.onDidChangeSessions(async e => {
|
||||
if (e.provider.id === GITHUB_AUTH_PROVIDER_ID) {
|
||||
this.octokit = await this.createOctokit(false);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async getOctokit(): Promise<Octokit.Octokit> {
|
||||
if (this.octokit) {
|
||||
return this.octokit;
|
||||
}
|
||||
|
||||
this.octokit = await this.createOctokit(true);
|
||||
// octokit shouldn't be undefined, since we've set "createIfNone: true".
|
||||
// The following block is mainly here to prevent a compiler error.
|
||||
if (!this.octokit) {
|
||||
throw new Error('Did not initialize Octokit.');
|
||||
}
|
||||
return this.octokit;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Readable } from 'stream';
|
||||
import { StringDecoder } from 'string_decoder';
|
||||
import * as tk from 'tree-kill';
|
||||
import { promisify } from 'util';
|
||||
import { CancellationToken, Disposable } from 'vscode';
|
||||
import { CancellationToken, Disposable, Uri } from 'vscode';
|
||||
|
||||
import { BQRSInfo, DecodedBqrsChunk } from './pure/bqrs-cli-types';
|
||||
import { CliConfig } from './config';
|
||||
@@ -43,6 +43,16 @@ export interface QuerySetup {
|
||||
compilationCache?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve queries --format bylanguage`.
|
||||
*/
|
||||
export interface QueryInfoByLanguage {
|
||||
// Using `unknown` as a placeholder. For now, the value is only ever an empty object.
|
||||
byLanguage: Record<string, Record<string, unknown>>;
|
||||
noDeclaredLanguage: Record<string, unknown>;
|
||||
multipleDeclaredLanguages: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve database`.
|
||||
*/
|
||||
@@ -71,6 +81,11 @@ export interface UpgradesInfo {
|
||||
*/
|
||||
export type QlpacksInfo = { [name: string]: string[] };
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve languages`.
|
||||
*/
|
||||
export type LanguagesInfo = { [name: string]: string[] };
|
||||
|
||||
/**
|
||||
* The expected output of `codeql resolve qlref`.
|
||||
*/
|
||||
@@ -108,6 +123,7 @@ export interface TestCompleted {
|
||||
expected: string;
|
||||
diff: string[] | undefined;
|
||||
failureDescription?: string;
|
||||
failureStage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -482,6 +498,20 @@ export class CodeQLCliServer implements Disposable {
|
||||
return await this.runJsonCodeQlCliCommand<QuerySetup>(['resolve', 'library-path'], subcommandArgs, 'Resolving library paths');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the language for a query.
|
||||
* @param queryUri The URI of the query
|
||||
*/
|
||||
async resolveQueryByLanguage(workspaces: string[], queryUri: Uri): Promise<QueryInfoByLanguage> {
|
||||
const subcommandArgs = [
|
||||
'--format', 'bylanguage',
|
||||
queryUri.fsPath,
|
||||
'--additional-packs',
|
||||
workspaces.join(path.delimiter)
|
||||
];
|
||||
return JSON.parse(await this.runCodeQlCliCommand(['resolve', 'queries'], subcommandArgs, 'Resolving query by language'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all available QL tests in a given directory.
|
||||
* @param testPath Root of directory tree to search for tests.
|
||||
@@ -619,6 +649,11 @@ export class CodeQLCliServer implements Disposable {
|
||||
this.cliConfig.numberThreads.toString(),
|
||||
);
|
||||
|
||||
args.push(
|
||||
'--max-paths',
|
||||
this.cliConfig.maxPaths.toString(),
|
||||
);
|
||||
|
||||
args.push(resultsPath);
|
||||
await this.runCodeQlCliCommand(['bqrs', 'interpret'], args, 'Interpreting query results');
|
||||
}
|
||||
@@ -724,6 +759,14 @@ export class CodeQLCliServer implements Disposable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about the available languages.
|
||||
* @returns A dictionary mapping language name to the directory it comes from
|
||||
*/
|
||||
async resolveLanguages(): Promise<LanguagesInfo> {
|
||||
return await this.runJsonCodeQlCliCommand<LanguagesInfo>(['resolve', 'languages'], [], 'Resolving languages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets information about queries in a query suite.
|
||||
* @param suite The suite to resolve.
|
||||
@@ -732,11 +775,15 @@ export class CodeQLCliServer implements Disposable {
|
||||
* the default CLI search path is used.
|
||||
* @returns A list of query files found.
|
||||
*/
|
||||
resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {
|
||||
async resolveQueriesInSuite(suite: string, additionalPacks: string[], searchPath?: string[]): Promise<string[]> {
|
||||
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
|
||||
if (searchPath !== undefined) {
|
||||
args.push('--search-path', path.join(...searchPath));
|
||||
}
|
||||
if (await this.cliConstraints.supportsAllowLibraryPacksInResolveQueries()) {
|
||||
// All of our usage of `codeql resolve queries` needs to handle library packs.
|
||||
args.push('--allow-library-packs');
|
||||
}
|
||||
args.push(suite);
|
||||
return this.runJsonCodeQlCliCommand<string[]>(
|
||||
['resolve', 'queries'],
|
||||
@@ -1021,6 +1068,12 @@ export class CliVersionConstraint {
|
||||
*/
|
||||
public static CLI_VERSION_WITH_DB_REGISTRATION = new SemVer('2.4.1');
|
||||
|
||||
/**
|
||||
* CLI version where the `--allow-library-packs` option to `codeql resolve queries` was
|
||||
* introduced.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES = new SemVer('2.6.1');
|
||||
|
||||
constructor(private readonly cli: CodeQLCliServer) {
|
||||
/**/
|
||||
}
|
||||
@@ -1045,6 +1098,10 @@ export class CliVersionConstraint {
|
||||
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_QLREF);
|
||||
}
|
||||
|
||||
public async supportsAllowLibraryPacksInResolveQueries() {
|
||||
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES);
|
||||
}
|
||||
|
||||
async supportsDatabaseRegistration() {
|
||||
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ const CACHE_SIZE_SETTING = new Setting('cacheSize', RUNNING_QUERIES_SETTING);
|
||||
const TIMEOUT_SETTING = new Setting('timeout', RUNNING_QUERIES_SETTING);
|
||||
const MEMORY_SETTING = new Setting('memory', RUNNING_QUERIES_SETTING);
|
||||
const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING);
|
||||
const MAX_PATHS = new Setting('maxPaths', RUNNING_QUERIES_SETTING);
|
||||
const RUNNING_TESTS_SETTING = new Setting('runningTests', ROOT_SETTING);
|
||||
const RESULTS_DISPLAY_SETTING = new Setting('resultsDisplay', ROOT_SETTING);
|
||||
|
||||
@@ -112,12 +113,13 @@ export interface QueryHistoryConfig {
|
||||
onDidChangeConfiguration: Event<void>;
|
||||
}
|
||||
|
||||
const CLI_SETTINGS = [ADDITIONAL_TEST_ARGUMENTS_SETTING, NUMBER_OF_TEST_THREADS_SETTING, NUMBER_OF_THREADS_SETTING];
|
||||
const CLI_SETTINGS = [ADDITIONAL_TEST_ARGUMENTS_SETTING, NUMBER_OF_TEST_THREADS_SETTING, NUMBER_OF_THREADS_SETTING, MAX_PATHS];
|
||||
|
||||
export interface CliConfig {
|
||||
additionalTestArguments: string[];
|
||||
numberTestThreads: number;
|
||||
numberThreads: number;
|
||||
maxPaths: number;
|
||||
onDidChangeConfiguration?: Event<void>;
|
||||
}
|
||||
|
||||
@@ -264,6 +266,10 @@ export class CliConfigListener extends ConfigListener implements CliConfig {
|
||||
return NUMBER_OF_THREADS_SETTING.getValue<number>();
|
||||
}
|
||||
|
||||
public get maxPaths(): number {
|
||||
return MAX_PATHS.getValue<number>();
|
||||
}
|
||||
|
||||
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||
this.handleDidChangeConfigurationForRelevantSettings(CLI_SETTINGS, e);
|
||||
}
|
||||
@@ -291,3 +297,35 @@ export function isCanary() {
|
||||
* Avoids caching in the AST viewer if the user is also a canary user.
|
||||
*/
|
||||
export const NO_CACHE_AST_VIEWER = new Setting('disableCache', AST_VIEWER_SETTING);
|
||||
|
||||
// Settings for remote queries
|
||||
const REMOTE_QUERIES_SETTING = new Setting('remoteQueries', ROOT_SETTING);
|
||||
|
||||
/**
|
||||
* Lists of GitHub repositories that you want to query remotely via the "Run Remote query" command.
|
||||
* Note: This command is only available for internal users.
|
||||
*
|
||||
* This setting should be a JSON object where each key is a user-specified name (string),
|
||||
* and the value is an array of GitHub repositories (of the form `<owner>/<repo>`).
|
||||
*/
|
||||
const REMOTE_REPO_LISTS = new Setting('repositoryLists', REMOTE_QUERIES_SETTING);
|
||||
|
||||
export function getRemoteRepositoryLists(): Record<string, string[]> | undefined {
|
||||
return REMOTE_REPO_LISTS.getValue<Record<string, string[]>>() || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the "controller" repository that you want to use with the "Run Remote query" command.
|
||||
* Note: This command is only available for internal users.
|
||||
*
|
||||
* This setting should be a GitHub repository of the form `<owner>/<repo>`.
|
||||
*/
|
||||
const REMOTE_CONTROLLER_REPO = new Setting('controllerRepo', REMOTE_QUERIES_SETTING);
|
||||
|
||||
export function getRemoteControllerRepo(): string | undefined {
|
||||
return REMOTE_CONTROLLER_REPO.getValue<string>() || undefined;
|
||||
}
|
||||
|
||||
export async function setRemoteControllerRepo(repo: string | undefined) {
|
||||
await REMOTE_CONTROLLER_REPO.updateValue(repo, ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ProgressCallback } from '../commandRunner';
|
||||
import { KeyType } from './keyType';
|
||||
import { qlpackOfDatabase, resolveQueries } from './queryResolver';
|
||||
|
||||
const SELECT_QUERY_NAME = '#select';
|
||||
export const SELECT_QUERY_NAME = '#select';
|
||||
export const TEMPLATE_NAME = 'selectedSourceFile';
|
||||
|
||||
export interface FullLocationLink extends vscode.LocationLink {
|
||||
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
} from './keyType';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { DatabaseItem } from '../databases';
|
||||
import { QlPacksForLanguage } from '../helpers';
|
||||
|
||||
export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<string> {
|
||||
export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<QlPacksForLanguage> {
|
||||
if (db.contents === undefined) {
|
||||
throw new Error('Database is invalid and cannot infer QLPack.');
|
||||
}
|
||||
@@ -21,28 +22,85 @@ export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem):
|
||||
return await helpers.getQlPackForDbscheme(cli, dbscheme);
|
||||
}
|
||||
|
||||
|
||||
export async function resolveQueries(cli: CodeQLCliServer, qlpack: string, keyType: KeyType): Promise<string[]> {
|
||||
/**
|
||||
* Finds the contextual queries with the specified key in a list of CodeQL packs.
|
||||
*
|
||||
* @param cli The CLI instance to use.
|
||||
* @param qlpacks The list of packs to search.
|
||||
* @param keyType The contextual query key of the query to search for.
|
||||
* @returns The found queries from the first pack in which any matching queries were found.
|
||||
*/
|
||||
async function resolveQueriesFromPacks(cli: CodeQLCliServer, qlpacks: string[], keyType: KeyType): Promise<string[]> {
|
||||
const suiteFile = (await tmp.file({
|
||||
postfix: '.qls'
|
||||
})).path;
|
||||
const suiteYaml = {
|
||||
qlpack,
|
||||
include: {
|
||||
kind: kindOfKeyType(keyType),
|
||||
'tags contain': tagOfKeyType(keyType)
|
||||
}
|
||||
};
|
||||
const suiteYaml = [];
|
||||
for (const qlpack of qlpacks) {
|
||||
suiteYaml.push({
|
||||
from: qlpack,
|
||||
queries: '.',
|
||||
include: {
|
||||
kind: kindOfKeyType(keyType),
|
||||
'tags contain': tagOfKeyType(keyType)
|
||||
}
|
||||
});
|
||||
}
|
||||
await fs.writeFile(suiteFile, yaml.safeDump(suiteYaml), 'utf8');
|
||||
|
||||
const queries = await cli.resolveQueriesInSuite(suiteFile, helpers.getOnDiskWorkspaceFolders());
|
||||
if (queries.length === 0) {
|
||||
void helpers.showAndLogErrorMessage(
|
||||
`No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(keyType)}") could be found in the current library path. \
|
||||
Try upgrading the CodeQL libraries. If that doesn't work, then ${nameOfKeyType(keyType)} queries are not yet available \
|
||||
for this language.`
|
||||
);
|
||||
throw new Error(`Couldn't find any queries tagged ${tagOfKeyType(keyType)} for qlpack ${qlpack}`);
|
||||
}
|
||||
return queries;
|
||||
}
|
||||
|
||||
export async function resolveQueries(cli: CodeQLCliServer, qlpacks: QlPacksForLanguage, keyType: KeyType): Promise<string[]> {
|
||||
const cliCanHandleLibraryPack = await cli.cliConstraints.supportsAllowLibraryPacksInResolveQueries();
|
||||
const packsToSearch: string[] = [];
|
||||
let blameCli: boolean;
|
||||
|
||||
if (cliCanHandleLibraryPack) {
|
||||
// The CLI can handle both library packs and query packs, so search both packs in order.
|
||||
packsToSearch.push(qlpacks.dbschemePack);
|
||||
if (qlpacks.queryPack !== undefined) {
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
// If we don't find the query, it's because it's not there, not because the CLI was unable to
|
||||
// search the pack.
|
||||
blameCli = false;
|
||||
} else {
|
||||
// Older CLIs can't handle `codeql resolve queries` with a suite that references a library pack.
|
||||
if (qlpacks.dbschemePackIsLibraryPack) {
|
||||
if (qlpacks.queryPack !== undefined) {
|
||||
// Just search the query pack, because some older library/query releases still had the
|
||||
// contextual queries in the query pack.
|
||||
packsToSearch.push(qlpacks.queryPack);
|
||||
}
|
||||
// If we don't find it, it's because the CLI was unable to search the library pack that
|
||||
// actually contains the query. Blame any failure on the CLI, not the packs.
|
||||
blameCli = true;
|
||||
} else {
|
||||
// We have an old CLI, but the dbscheme pack is old enough that it's still a unified pack with
|
||||
// both libraries and queries. Just search that pack.
|
||||
packsToSearch.push(qlpacks.dbschemePack);
|
||||
// Any CLI should be able to search the single query pack, so if we don't find it, it's
|
||||
// because the language doesn't support it.
|
||||
blameCli = false;
|
||||
}
|
||||
}
|
||||
|
||||
const queries = await resolveQueriesFromPacks(cli, packsToSearch, keyType);
|
||||
if (queries.length > 0) {
|
||||
return queries;
|
||||
}
|
||||
|
||||
// No queries found. Determine the correct error message for the various scenarios.
|
||||
const errorMessage = blameCli ?
|
||||
`Your current version of the CodeQL CLI, '${(await cli.getVersion()).version}', \
|
||||
is unable to use contextual queries from recent versions of the standard CodeQL libraries. \
|
||||
Please upgrade to the latest version of the CodeQL CLI.`
|
||||
:
|
||||
`No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(keyType)}") could be found in the current library path. \
|
||||
Try upgrading the CodeQL libraries. If that doesn't work, then ${nameOfKeyType(keyType)} queries are not yet available \
|
||||
for this language.`;
|
||||
|
||||
void helpers.showAndLogErrorMessage(errorMessage);
|
||||
throw new Error(`Couldn't find any queries tagged ${tagOfKeyType(keyType)} in any of the following packs: ${packsToSearch.join(', ')}.`);
|
||||
}
|
||||
|
||||
@@ -175,8 +175,8 @@ export class TemplatePrintAstProvider {
|
||||
throw new Error('Can\'t infer database from the provided source.');
|
||||
}
|
||||
|
||||
const qlpack = await qlpackOfDatabase(this.cli, db);
|
||||
const queries = await resolveQueries(this.cli, qlpack, KeyType.PrintAstQuery);
|
||||
const qlpacks = await qlpackOfDatabase(this.cli, db);
|
||||
const queries = await resolveQueries(this.cli, qlpacks, KeyType.PrintAstQuery);
|
||||
if (queries.length > 1) {
|
||||
throw new Error('Found multiple Print AST queries. Can\'t continue');
|
||||
}
|
||||
|
||||
@@ -72,6 +72,11 @@ export async function promptImportLgtmDatabase(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
progress({
|
||||
message: 'Choose project',
|
||||
step: 1,
|
||||
maxStep: 2
|
||||
});
|
||||
const lgtmUrl = await window.showInputBox({
|
||||
prompt:
|
||||
'Enter the project slug or URL on LGTM (e.g., g/github/codeql or https://lgtm.com/projects/g/github/codeql)',
|
||||
@@ -81,7 +86,7 @@ export async function promptImportLgtmDatabase(
|
||||
}
|
||||
|
||||
if (looksLikeLgtmUrl(lgtmUrl)) {
|
||||
const databaseUrl = await convertToDatabaseUrl(lgtmUrl);
|
||||
const databaseUrl = await convertToDatabaseUrl(lgtmUrl, progress);
|
||||
if (databaseUrl) {
|
||||
const item = await databaseArchiveFetcher(
|
||||
databaseUrl,
|
||||
@@ -405,7 +410,9 @@ function convertRawLgtmSlug(maybeSlug: string): string | undefined {
|
||||
}
|
||||
|
||||
// exported for testing
|
||||
export async function convertToDatabaseUrl(lgtmUrl: string) {
|
||||
export async function convertToDatabaseUrl(
|
||||
lgtmUrl: string,
|
||||
progress: ProgressCallback) {
|
||||
try {
|
||||
lgtmUrl = convertRawLgtmSlug(lgtmUrl) || lgtmUrl;
|
||||
|
||||
@@ -421,7 +428,7 @@ export async function convertToDatabaseUrl(lgtmUrl: string) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const language = await promptForLanguage(projectJson);
|
||||
const language = await promptForLanguage(projectJson, progress);
|
||||
if (!language) {
|
||||
return;
|
||||
}
|
||||
@@ -439,8 +446,14 @@ export async function convertToDatabaseUrl(lgtmUrl: string) {
|
||||
}
|
||||
|
||||
async function promptForLanguage(
|
||||
projectJson: any
|
||||
projectJson: any,
|
||||
progress: ProgressCallback
|
||||
): Promise<string | undefined> {
|
||||
progress({
|
||||
message: 'Choose language',
|
||||
step: 2,
|
||||
maxStep: 2
|
||||
});
|
||||
if (!projectJson?.languages?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ export class DatabaseUI extends DisposableObject {
|
||||
'codeQLDatabases.chooseDatabaseLgtm',
|
||||
this.handleChooseDatabaseLgtm,
|
||||
{
|
||||
title: 'Adding database from LGTM. Choose a language from the dropdown, if requested.',
|
||||
title: 'Adding database from LGTM',
|
||||
})
|
||||
);
|
||||
this.push(
|
||||
@@ -348,6 +348,12 @@ export class DatabaseUI extends DisposableObject {
|
||||
this.handleOpenFolder
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLDatabases.addDatabaseSource',
|
||||
this.handleAddSource
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLDatabases.removeOrphanedDatabases',
|
||||
@@ -632,6 +638,24 @@ export class DatabaseUI extends DisposableObject {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the source folder of a CodeQL database to the workspace.
|
||||
* When a database is first added in the "Databases" view, its source folder is added to the workspace.
|
||||
* If the source folder is removed from the workspace for some reason, we want to be able to re-add it if need be.
|
||||
*/
|
||||
private handleAddSource = async (
|
||||
databaseItem: DatabaseItem,
|
||||
multiSelect: DatabaseItem[] | undefined
|
||||
): Promise<void> => {
|
||||
if (multiSelect?.length) {
|
||||
for (const dbItem of multiSelect) {
|
||||
await this.databaseManager.addDatabaseSourceArchiveFolder(dbItem);
|
||||
}
|
||||
} else {
|
||||
await this.databaseManager.addDatabaseSourceArchiveFolder(databaseItem);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current database directory. If we don't already have a
|
||||
* current database, ask the user for one, and return that, or
|
||||
|
||||
@@ -587,7 +587,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
}));
|
||||
}
|
||||
|
||||
private async addDatabaseSourceArchiveFolder(item: DatabaseItem) {
|
||||
public async addDatabaseSourceArchiveFolder(item: DatabaseItem) {
|
||||
// The folder may already be in workspace state from a previous
|
||||
// session. If not, add it.
|
||||
const index = this.getDatabaseWorkspaceFolderIndex(item);
|
||||
@@ -811,6 +811,9 @@ export class DatabaseManager extends DisposableObject {
|
||||
vscode.workspace.updateWorkspaceFolders(folderIndex, 1);
|
||||
}
|
||||
|
||||
// Remove this database item from the allow-list
|
||||
await this.deregisterDatabase(progress, token, item);
|
||||
|
||||
// Delete folder from file system only if it is controlled by the extension
|
||||
if (this.isExtensionControlledLocation(item.databaseUri)) {
|
||||
void logger.log('Deleting database from filesystem.');
|
||||
@@ -819,9 +822,6 @@ export class DatabaseManager extends DisposableObject {
|
||||
e => void logger.log(`Failed to delete '${item.databaseUri.fsPath}'. Reason: ${e.message}`));
|
||||
}
|
||||
|
||||
// Remove this database item from the allow-list
|
||||
await this.deregisterDatabase(progress, token, item);
|
||||
|
||||
// note that we use undefined as the item in order to reset the entire tree
|
||||
this._onDidChangeDatabaseItem.fire({
|
||||
item: undefined,
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
Uri,
|
||||
window as Window,
|
||||
env,
|
||||
window
|
||||
window,
|
||||
QuickPickItem
|
||||
} from 'vscode';
|
||||
import { LanguageClient } from 'vscode-languageclient';
|
||||
import * as os from 'os';
|
||||
@@ -23,12 +24,13 @@ import { CodeQLCliServer, CliVersionConstraint } from './cli';
|
||||
import {
|
||||
CliConfigListener,
|
||||
DistributionConfigListener,
|
||||
isCanary,
|
||||
MAX_QUERIES,
|
||||
QueryHistoryConfigListener,
|
||||
QueryServerConfigListener
|
||||
} from './config';
|
||||
import * as languageSupport from './languageSupport';
|
||||
import { DatabaseManager } from './databases';
|
||||
import { DatabaseItem, DatabaseManager } from './databases';
|
||||
import { DatabaseUI } from './databases-ui';
|
||||
import {
|
||||
TemplateQueryDefinitionProvider,
|
||||
@@ -70,6 +72,9 @@ import {
|
||||
} from './commandRunner';
|
||||
import { CodeQlStatusBarHandler } from './status-bar';
|
||||
|
||||
import { Credentials } from './authentication';
|
||||
import { runRemoteQuery } from './run-remote-query';
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
* ------------
|
||||
@@ -463,16 +468,18 @@ async function activateWithInstalledDistribution(
|
||||
selectedQuery: Uri | undefined,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
): Promise<void> {
|
||||
if (qs !== undefined) {
|
||||
const dbItem = await databaseUI.getDatabaseItem(progress, token);
|
||||
if (dbItem === undefined) {
|
||||
// If no databaseItem is specified, use the database currently selected in the Databases UI
|
||||
databaseItem = databaseItem || await databaseUI.getDatabaseItem(progress, token);
|
||||
if (databaseItem === undefined) {
|
||||
throw new Error('Can\'t run query without a selected database');
|
||||
}
|
||||
const info = await compileAndRunQueryAgainstDatabase(
|
||||
cliServer,
|
||||
qs,
|
||||
dbItem,
|
||||
databaseItem,
|
||||
quickEval,
|
||||
selectedQuery,
|
||||
progress,
|
||||
@@ -545,13 +552,80 @@ async function activateWithInstalledDistribution(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined
|
||||
) => await compileAndRunQuery(false, uri, progress, token),
|
||||
) => await compileAndRunQuery(false, uri, progress, token, undefined),
|
||||
{
|
||||
title: 'Running query',
|
||||
cancellable: true
|
||||
}
|
||||
)
|
||||
);
|
||||
interface DatabaseQuickPickItem extends QuickPickItem {
|
||||
databaseItem: DatabaseItem;
|
||||
}
|
||||
ctx.subscriptions.push(
|
||||
commandRunnerWithProgress(
|
||||
'codeQL.runQueryOnMultipleDatabases',
|
||||
async (
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined
|
||||
) => {
|
||||
let filteredDBs = dbm.databaseItems;
|
||||
if (filteredDBs.length === 0) {
|
||||
void helpers.showAndLogErrorMessage('No databases found. Please add a suitable database to your workspace.');
|
||||
return;
|
||||
}
|
||||
// If possible, only show databases with the right language (otherwise show all databases).
|
||||
const queryLanguage = await helpers.findLanguage(cliServer, uri);
|
||||
if (queryLanguage) {
|
||||
filteredDBs = dbm.databaseItems.filter(db => db.language === queryLanguage);
|
||||
if (filteredDBs.length === 0) {
|
||||
void helpers.showAndLogErrorMessage(`No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const quickPickItems = filteredDBs.map<DatabaseQuickPickItem>(dbItem => (
|
||||
{
|
||||
databaseItem: dbItem,
|
||||
label: dbItem.name,
|
||||
description: dbItem.language,
|
||||
}
|
||||
));
|
||||
/**
|
||||
* Databases that were selected in the quick pick menu.
|
||||
*/
|
||||
const quickpick = await window.showQuickPick<DatabaseQuickPickItem>(
|
||||
quickPickItems,
|
||||
{ canPickMany: true, ignoreFocusOut: true }
|
||||
);
|
||||
if (quickpick !== undefined) {
|
||||
// Collect all skipped databases and display them at the end (instead of popping up individual errors)
|
||||
const skippedDatabases = [];
|
||||
const errors = [];
|
||||
for (const item of quickpick) {
|
||||
try {
|
||||
await compileAndRunQuery(false, uri, progress, token, item.databaseItem);
|
||||
} catch (error) {
|
||||
skippedDatabases.push(item.label);
|
||||
errors.push(error.message);
|
||||
}
|
||||
}
|
||||
if (skippedDatabases.length > 0) {
|
||||
void logger.log(`Errors:\n${errors.join('\n')}`);
|
||||
void helpers.showAndLogWarningMessage(
|
||||
`The following databases were skipped:\n${skippedDatabases.join('\n')}.\nFor details about the errors, see the logs.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
void helpers.showAndLogErrorMessage('No databases selected.');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Running query on selected databases',
|
||||
cancellable: true
|
||||
}
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commandRunnerWithProgress(
|
||||
'codeQL.runQueries',
|
||||
@@ -607,7 +681,7 @@ async function activateWithInstalledDistribution(
|
||||
});
|
||||
|
||||
await Promise.all(queryUris.map(async uri =>
|
||||
compileAndRunQuery(false, uri, wrappedProgress, token)
|
||||
compileAndRunQuery(false, uri, wrappedProgress, token, undefined)
|
||||
.then(() => queriesRemaining--)
|
||||
));
|
||||
},
|
||||
@@ -623,7 +697,7 @@ async function activateWithInstalledDistribution(
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
uri: Uri | undefined
|
||||
) => await compileAndRunQuery(true, uri, progress, token),
|
||||
) => await compileAndRunQuery(true, uri, progress, token, undefined),
|
||||
{
|
||||
title: 'Running query',
|
||||
cancellable: true
|
||||
@@ -640,6 +714,17 @@ async function activateWithInstalledDistribution(
|
||||
}
|
||||
)
|
||||
);
|
||||
// The "runRemoteQuery" command is internal-only.
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.runRemoteQuery', async (
|
||||
uri: Uri | undefined
|
||||
) => {
|
||||
if (isCanary()) {
|
||||
const credentials = await Credentials.initialize(ctx);
|
||||
await runRemoteQuery(cliServer, credentials, uri || window.activeTextEditor?.document.uri);
|
||||
}
|
||||
})
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commandRunner(
|
||||
'codeQL.openReferencedFile',
|
||||
@@ -686,7 +771,7 @@ async function activateWithInstalledDistribution(
|
||||
) =>
|
||||
databaseUI.handleChooseDatabaseLgtm(progress, token),
|
||||
{
|
||||
title: 'Adding database from LGTM. Choose a language from the dropdown, if requested.',
|
||||
title: 'Adding database from LGTM',
|
||||
})
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
@@ -707,11 +792,37 @@ async function activateWithInstalledDistribution(
|
||||
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.copyVersion', async () => {
|
||||
const text = `CodeQL extension version: ${extension?.packageJSON.version} \nCodeQL CLI version: ${await cliServer.getVersion()} \nPlatform: ${os.platform()} ${os.arch()}`;
|
||||
const text = `CodeQL extension version: ${extension?.packageJSON.version} \nCodeQL CLI version: ${await getCliVersion()} \nPlatform: ${os.platform()} ${os.arch()}`;
|
||||
await env.clipboard.writeText(text);
|
||||
void helpers.showAndLogInformationMessage(text);
|
||||
}));
|
||||
|
||||
const getCliVersion = async () => {
|
||||
try {
|
||||
return await cliServer.getVersion();
|
||||
} catch {
|
||||
return '<missing>';
|
||||
}
|
||||
};
|
||||
|
||||
// The "authenticateToGitHub" command is internal-only.
|
||||
ctx.subscriptions.push(
|
||||
commandRunner('codeQL.authenticateToGitHub', async () => {
|
||||
if (isCanary()) {
|
||||
/**
|
||||
* Credentials for authenticating to GitHub.
|
||||
* These are used when making API calls.
|
||||
*/
|
||||
const credentials = await Credentials.initialize(ctx);
|
||||
const octokit = await credentials.getOctokit();
|
||||
const userInfo = await octokit.users.getAuthenticated();
|
||||
void helpers.showAndLogInformationMessage(`Authenticated to GitHub as user: ${userInfo.data.login}`);
|
||||
}
|
||||
}));
|
||||
|
||||
commands.registerCommand('codeQL.showLogs', () => {
|
||||
logger.show();
|
||||
});
|
||||
|
||||
void logger.log('Starting language server.');
|
||||
ctx.subscriptions.push(client.start());
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
workspace,
|
||||
env
|
||||
} from 'vscode';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { CodeQLCliServer, QlpacksInfo } from './cli';
|
||||
import { logger } from './logging';
|
||||
|
||||
/**
|
||||
@@ -254,9 +254,55 @@ function createRateLimitedResult(): RateLimitedResult {
|
||||
};
|
||||
}
|
||||
|
||||
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<string> {
|
||||
export interface QlPacksForLanguage {
|
||||
/** The name of the pack containing the dbscheme. */
|
||||
dbschemePack: string;
|
||||
/** `true` if `dbschemePack` is a library pack. */
|
||||
dbschemePackIsLibraryPack: boolean;
|
||||
/**
|
||||
* The name of the corresponding standard query pack.
|
||||
* Only defined if `dbschemePack` is a library pack.
|
||||
*/
|
||||
queryPack?: string;
|
||||
}
|
||||
|
||||
interface QlPackWithPath {
|
||||
packName: string;
|
||||
packDir: string | undefined;
|
||||
}
|
||||
|
||||
async function findDbschemePack(packs: QlPackWithPath[], dbschemePath: string): Promise<{ name: string; isLibraryPack: boolean; }> {
|
||||
for (const { packDir, packName } of packs) {
|
||||
if (packDir !== undefined) {
|
||||
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')) as { dbscheme?: string; library?: boolean; };
|
||||
if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) {
|
||||
return {
|
||||
name: packName,
|
||||
isLibraryPack: qlpack.library === true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
|
||||
}
|
||||
|
||||
function findStandardQueryPack(qlpacks: QlpacksInfo, dbschemePackName: string): string | undefined {
|
||||
const matches = dbschemePackName.match(/^codeql\/(?<language>[a-z]+)-all$/);
|
||||
if (matches) {
|
||||
const queryPackName = `codeql/${matches.groups!.language}-queries`;
|
||||
if (qlpacks[queryPackName] !== undefined) {
|
||||
return queryPackName;
|
||||
}
|
||||
}
|
||||
|
||||
// Either the dbscheme pack didn't look like one where the queries might be in the query pack, or
|
||||
// no query pack was found in the search path. Either is OK.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemePath: string): Promise<QlPacksForLanguage> {
|
||||
const qlpacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
|
||||
const packs: { packDir: string | undefined; packName: string }[] =
|
||||
const packs: QlPackWithPath[] =
|
||||
Object.entries(qlpacks).map(([packName, dirs]) => {
|
||||
if (dirs.length < 1) {
|
||||
void logger.log(`In getQlPackFor ${dbschemePath}, qlpack ${packName} has no directories`);
|
||||
@@ -270,15 +316,13 @@ export async function getQlPackForDbscheme(cliServer: CodeQLCliServer, dbschemeP
|
||||
packDir: dirs[0]
|
||||
};
|
||||
});
|
||||
for (const { packDir, packName } of packs) {
|
||||
if (packDir !== undefined) {
|
||||
const qlpack = yaml.safeLoad(await fs.readFile(path.join(packDir, 'qlpack.yml'), 'utf8')) as { dbscheme: string };
|
||||
if (qlpack.dbscheme !== undefined && path.basename(qlpack.dbscheme) === path.basename(dbschemePath)) {
|
||||
return packName;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find qlpack file for dbscheme ${dbschemePath}`);
|
||||
const dbschemePack = await findDbschemePack(packs, dbschemePath);
|
||||
const queryPack = dbschemePack.isLibraryPack ? findStandardQueryPack(qlpacks, dbschemePack.name) : undefined;
|
||||
return {
|
||||
dbschemePack: dbschemePack.name,
|
||||
dbschemePackIsLibraryPack: dbschemePack.isLibraryPack,
|
||||
queryPack
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPrimaryDbscheme(datasetFolder: string): Promise<string> {
|
||||
@@ -379,6 +423,12 @@ const dbSchemeToLanguage = {
|
||||
'go.dbscheme': 'go'
|
||||
};
|
||||
|
||||
export const languageToDbScheme = Object.entries(dbSchemeToLanguage).reduce((acc, [k, v]) => {
|
||||
acc[v] = k;
|
||||
return acc;
|
||||
}, {} as { [k: string]: string });
|
||||
|
||||
|
||||
/**
|
||||
* Returns the initial contents for an empty query, based on the language of the selected
|
||||
* databse.
|
||||
@@ -424,3 +474,34 @@ export async function isLikelyDatabaseRoot(maybeRoot: string) {
|
||||
export function isLikelyDbLanguageFolder(dbPath: string) {
|
||||
return !!path.basename(dbPath).startsWith('db-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the language that a query targets.
|
||||
* If it can't be autodetected, prompt the user to specify the language manually.
|
||||
*/
|
||||
export async function findLanguage(
|
||||
cliServer: CodeQLCliServer,
|
||||
queryUri: Uri | undefined
|
||||
): Promise<string | undefined> {
|
||||
const uri = queryUri || Window.activeTextEditor?.document.uri;
|
||||
if (uri !== undefined) {
|
||||
try {
|
||||
const queryInfo = await cliServer.resolveQueryByLanguage(getOnDiskWorkspaceFolders(), uri);
|
||||
const language = (Object.keys(queryInfo.byLanguage))[0];
|
||||
void logger.log(`Detected query language: ${language}`);
|
||||
return language;
|
||||
} catch (e) {
|
||||
void logger.log('Could not autodetect query language. Select language manually.');
|
||||
}
|
||||
}
|
||||
const availableLanguages = Object.keys(await cliServer.resolveLanguages());
|
||||
const language = await Window.showQuickPick(
|
||||
availableLanguages,
|
||||
{ placeHolder: 'Select target language for your query', ignoreFocusOut: true }
|
||||
);
|
||||
if (!language) {
|
||||
// This only happens if the user cancels the quick pick.
|
||||
void showAndLogErrorMessage('Language not found. Language must be specified manually.');
|
||||
}
|
||||
return language;
|
||||
}
|
||||
|
||||
@@ -312,8 +312,14 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.viewSarifResults',
|
||||
this.handleViewSarifResults.bind(this)
|
||||
'codeQLQueryHistory.viewCsvAlerts',
|
||||
this.handleViewCsvAlerts.bind(this)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
commandRunner(
|
||||
'codeQLQueryHistory.viewSarifAlerts',
|
||||
this.handleViewSarifAlerts.bind(this)
|
||||
)
|
||||
);
|
||||
this.push(
|
||||
@@ -499,13 +505,13 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
if (
|
||||
prevItemClick !== undefined &&
|
||||
now.valueOf() - prevItemClick.time.valueOf() < DOUBLE_CLICK_TIME &&
|
||||
singleItem == prevItemClick.item
|
||||
finalSingleItem == prevItemClick.item
|
||||
) {
|
||||
// show original query file on double click
|
||||
await this.handleOpenQuery(singleItem, [singleItem]);
|
||||
await this.handleOpenQuery(finalSingleItem, [finalSingleItem]);
|
||||
} else {
|
||||
// show results on single click
|
||||
await this.invokeCallbackOn(singleItem);
|
||||
await this.invokeCallbackOn(finalSingleItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,7 +556,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
await vscode.window.showTextDocument(doc, { preview: false });
|
||||
}
|
||||
|
||||
async handleViewSarifResults(
|
||||
async handleViewSarifAlerts(
|
||||
singleItem: CompletedQuery,
|
||||
multiSelect: CompletedQuery[]
|
||||
) {
|
||||
@@ -578,6 +584,24 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
if (!this.assertSingleQuery(multiSelect)) {
|
||||
return;
|
||||
}
|
||||
if (await singleItem.query.hasCsv()) {
|
||||
void this.tryOpenExternalFile(singleItem.query.csvPath);
|
||||
return;
|
||||
}
|
||||
await singleItem.query.exportCsvResults(this.qs, singleItem.query.csvPath, () => {
|
||||
void this.tryOpenExternalFile(
|
||||
singleItem.query.csvPath
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async handleViewCsvAlerts(
|
||||
singleItem: CompletedQuery,
|
||||
multiSelect: CompletedQuery[]
|
||||
) {
|
||||
if (!this.assertSingleQuery(multiSelect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.tryOpenExternalFile(
|
||||
await singleItem.query.ensureCsvProduced(this.qs)
|
||||
|
||||
@@ -62,6 +62,9 @@ export class CompletedQuery implements QueryWithResults {
|
||||
get queryName(): string {
|
||||
return getQueryName(this.query);
|
||||
}
|
||||
get queryFileName(): string {
|
||||
return getQueryFileName(this.query);
|
||||
}
|
||||
|
||||
get statusString(): string {
|
||||
switch (this.result.resultType) {
|
||||
@@ -88,13 +91,14 @@ export class CompletedQuery implements QueryWithResults {
|
||||
}
|
||||
|
||||
interpolate(template: string): string {
|
||||
const { databaseName, queryName, time, resultCount, statusString } = this;
|
||||
const { databaseName, queryName, time, resultCount, statusString, queryFileName } = this;
|
||||
const replacements: { [k: string]: string } = {
|
||||
t: time,
|
||||
q: queryName,
|
||||
d: databaseName,
|
||||
r: resultCount.toString(),
|
||||
s: statusString,
|
||||
f: queryFileName,
|
||||
'%': '%',
|
||||
};
|
||||
return template.replace(/%(.)/g, (match, key) => {
|
||||
@@ -152,17 +156,28 @@ export class CompletedQuery implements QueryWithResults {
|
||||
* Uses metadata if it exists, and defaults to the query file name.
|
||||
*/
|
||||
export function getQueryName(query: QueryInfo) {
|
||||
if (query.quickEvalPosition !== undefined) {
|
||||
return 'Quick evaluation of ' + getQueryFileName(query);
|
||||
} else if (query.metadata?.name) {
|
||||
return query.metadata.name;
|
||||
} else {
|
||||
return getQueryFileName(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file name for an evaluated query.
|
||||
* Defaults to the query file name and may contain position information for quick eval queries.
|
||||
*/
|
||||
export function getQueryFileName(query: QueryInfo) {
|
||||
// Queries run through quick evaluation are not usually the entire query file.
|
||||
// Label them differently and include the line numbers.
|
||||
if (query.quickEvalPosition !== undefined) {
|
||||
const { line, endLine, fileName } = query.quickEvalPosition;
|
||||
const lineInfo = line === endLine ? `${line}` : `${line}-${endLine}`;
|
||||
return `Quick evaluation of ${path.basename(fileName)}:${lineInfo}`;
|
||||
} else if (query.metadata?.name) {
|
||||
return query.metadata.name;
|
||||
} else {
|
||||
return path.basename(query.program.queryPath);
|
||||
return `${path.basename(fileName)}:${lineInfo}`;
|
||||
}
|
||||
return path.basename(query.program.queryPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ export async function displayQuickQuery(
|
||||
|
||||
const datasetFolder = await dbItem.getDatasetFolder(cliServer);
|
||||
const dbscheme = await getPrimaryDbscheme(datasetFolder);
|
||||
const qlpack = await getQlPackForDbscheme(cliServer, dbscheme);
|
||||
const qlpack = (await getQlPackForDbscheme(cliServer, dbscheme)).dbschemePack;
|
||||
const qlPackFile = path.join(queriesDir, 'qlpack.yml');
|
||||
const qlFile = path.join(queriesDir, QUICK_QUERY_QUERY_NAME);
|
||||
const shouldRewrite = await checkShouldRewrite(qlPackFile, qlpack);
|
||||
|
||||
@@ -25,6 +25,8 @@ import * as qsClient from './queryserver-client';
|
||||
import { isQuickQueryPath } from './quick-query';
|
||||
import { compileDatabaseUpgradeSequence, hasNondestructiveUpgradeCapabilities, upgradeDatabaseExplicit } from './upgrades';
|
||||
import { ensureMetadataIsComplete } from './query-results';
|
||||
import { SELECT_QUERY_NAME } from './contextual/locationFinder';
|
||||
import { DecodedBqrsChunk } from './pure/bqrs-cli-types';
|
||||
|
||||
/**
|
||||
* run-queries.ts
|
||||
@@ -216,6 +218,29 @@ export class QueryInfo {
|
||||
return this.dilPath;
|
||||
}
|
||||
|
||||
async exportCsvResults(qs: qsClient.QueryServerClient, csvPath: string, onFinish: () => void): Promise<void> {
|
||||
let stopDecoding = false;
|
||||
const out = fs.createWriteStream(csvPath);
|
||||
out.on('finish', onFinish);
|
||||
out.on('error', () => {
|
||||
if (!stopDecoding) {
|
||||
stopDecoding = true;
|
||||
void showAndLogErrorMessage(`Failed to write CSV results to ${csvPath}`);
|
||||
}
|
||||
});
|
||||
let nextOffset: number | undefined = 0;
|
||||
while (nextOffset !== undefined && !stopDecoding) {
|
||||
const chunk: DecodedBqrsChunk = await qs.cliServer.bqrsDecode(this.resultsPaths.resultsPath, SELECT_QUERY_NAME, {
|
||||
pageSize: 100,
|
||||
offset: nextOffset,
|
||||
});
|
||||
for (const tuple of chunk.tuples)
|
||||
out.write(tuple.join(',') + '\n');
|
||||
nextOffset = chunk.next;
|
||||
}
|
||||
out.end();
|
||||
}
|
||||
|
||||
async ensureCsvProduced(qs: qsClient.QueryServerClient): Promise<string> {
|
||||
if (await this.hasCsv()) {
|
||||
return this.csvPath;
|
||||
@@ -499,7 +524,7 @@ export async function determineSelectedQuery(selectedResourceUri: Uri | undefine
|
||||
// then prompt the user to save it first.
|
||||
if (editor !== undefined && editor.document.uri.fsPath === queryPath) {
|
||||
if (await promptUserToSaveChanges(editor.document)) {
|
||||
void editor.document.save();
|
||||
await editor.document.save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,7 +588,7 @@ export async function compileAndRunQueryAgainstDatabase(
|
||||
const dbSchemaName = path.basename(db.contents.dbSchemeUri.fsPath);
|
||||
if (querySchemaName != dbSchemaName) {
|
||||
void logger.log(`Query schema was ${querySchemaName}, but database schema was ${dbSchemaName}.`);
|
||||
throw new Error(`The query ${path.basename(queryPath)} cannot be run against the selected database: their target languages are different. Please select a different database and try again.`);
|
||||
throw new Error(`The query ${path.basename(queryPath)} cannot be run against the selected database (${db.name}): their target languages are different. Please select a different database and try again.`);
|
||||
}
|
||||
|
||||
const qlProgram: messages.QlProgram = {
|
||||
|
||||
191
extensions/ql-vscode/src/run-remote-query.ts
Normal file
191
extensions/ql-vscode/src/run-remote-query.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { QuickPickItem, Uri, window } from 'vscode';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as fs from 'fs-extra';
|
||||
import { findLanguage, showAndLogErrorMessage, showAndLogInformationMessage, showInformationMessageWithAction } from './helpers';
|
||||
import { Credentials } from './authentication';
|
||||
import * as cli from './cli';
|
||||
import { logger } from './logging';
|
||||
import { getRemoteControllerRepo, getRemoteRepositoryLists, setRemoteControllerRepo } from './config';
|
||||
interface Config {
|
||||
repositories: string[];
|
||||
ref?: string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
interface RepoListQuickPickItem extends QuickPickItem {
|
||||
repoList: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This regex matches strings of the form `owner/repo` where:
|
||||
* - `owner` is made up of alphanumeric characters or single hyphens, starting and ending in an alphanumeric character
|
||||
* - `repo` is made up of alphanumeric characters, hyphens, or underscores
|
||||
*/
|
||||
const REPO_REGEX = /^(?:[a-zA-Z0-9]+-)*[a-zA-Z0-9]+\/[a-zA-Z0-9-_]+$/;
|
||||
|
||||
/**
|
||||
* Gets the repositories to run the query against.
|
||||
*/
|
||||
export async function getRepositories(): Promise<string[] | undefined> {
|
||||
const repoLists = getRemoteRepositoryLists();
|
||||
if (repoLists && Object.keys(repoLists).length) {
|
||||
const quickPickItems = Object.entries(repoLists).map<RepoListQuickPickItem>(([key, value]) => (
|
||||
{
|
||||
label: key, // the name of the repository list
|
||||
repoList: value, // the actual array of repositories
|
||||
}
|
||||
));
|
||||
const quickpick = await window.showQuickPick<RepoListQuickPickItem>(
|
||||
quickPickItems,
|
||||
{
|
||||
placeHolder: 'Select a repository list. You can define repository lists in the `codeQL.remoteQueries.repositoryLists` setting.',
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
if (quickpick?.repoList.length) {
|
||||
void logger.log(`Selected repositories: ${quickpick.repoList.join(', ')}`);
|
||||
return quickpick.repoList;
|
||||
} else {
|
||||
void showAndLogErrorMessage('No repositories selected.');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
void logger.log('No repository lists defined. Displaying text input box.');
|
||||
const remoteRepo = await window.showInputBox({
|
||||
title: 'Enter a GitHub repository in the format <owner>/<repo> (e.g. github/codeql)',
|
||||
placeHolder: '<owner>/<repo>',
|
||||
prompt: 'Tip: you can save frequently used repositories in the `codeQL.remoteQueries.repositoryLists` setting',
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
if (!remoteRepo) {
|
||||
void showAndLogErrorMessage('No repositories entered.');
|
||||
return;
|
||||
} else if (!REPO_REGEX.test(remoteRepo)) { // Check if user entered invalid input
|
||||
void showAndLogErrorMessage('Invalid repository format. Must be in the format <owner>/<repo> (e.g. github/codeql)');
|
||||
return;
|
||||
}
|
||||
void logger.log(`Entered repository: ${remoteRepo}`);
|
||||
return [remoteRepo];
|
||||
}
|
||||
}
|
||||
|
||||
export async function runRemoteQuery(cliServer: cli.CodeQLCliServer, credentials: Credentials, uri?: Uri) {
|
||||
if (!uri?.fsPath.endsWith('.ql')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryFile = uri.fsPath;
|
||||
const query = await fs.readFile(queryFile, 'utf8');
|
||||
|
||||
const repositoriesFile = queryFile.substring(0, queryFile.length - '.ql'.length) + '.repositories';
|
||||
let ref: string | undefined;
|
||||
let language: string | undefined;
|
||||
let repositories: string[] | undefined;
|
||||
|
||||
// If the user has an explicit `.repositories` file, use that.
|
||||
// Otherwise, prompt user to select repositories from the `codeQL.remoteQueries.repositoryLists` setting.
|
||||
if (await fs.pathExists(repositoriesFile)) {
|
||||
void logger.log(`Found '${repositoriesFile}'. Using information from that file to run ${queryFile}.`);
|
||||
|
||||
const config = yaml.safeLoad(await fs.readFile(repositoriesFile, 'utf8')) as Config;
|
||||
|
||||
ref = config.ref || 'main';
|
||||
language = config.language || await findLanguage(cliServer, uri);
|
||||
repositories = config.repositories;
|
||||
} else {
|
||||
ref = 'main';
|
||||
[language, repositories] = await Promise.all([findLanguage(cliServer, uri), getRepositories()]);
|
||||
}
|
||||
|
||||
if (!language) {
|
||||
return; // No error message needed, since `findLanguage` already displays one.
|
||||
}
|
||||
|
||||
if (!repositories || repositories.length === 0) {
|
||||
return; // No error message needed, since `getRepositories` already displays one.
|
||||
}
|
||||
|
||||
// Get the controller repo from the config, if it exists.
|
||||
// If it doesn't exist, prompt the user to enter it, and save that value to the config.
|
||||
let controllerRepo: string | undefined;
|
||||
controllerRepo = getRemoteControllerRepo();
|
||||
if (!controllerRepo || !REPO_REGEX.test(controllerRepo)) {
|
||||
void logger.log(controllerRepo ? 'Invalid controller repository name.' : 'No controller repository defined.');
|
||||
controllerRepo = await window.showInputBox({
|
||||
title: 'Controller repository in which to display progress and results of remote queries',
|
||||
placeHolder: '<owner>/<repo>',
|
||||
prompt: 'Enter the name of a GitHub repository in the format <owner>/<repo>',
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
if (!controllerRepo) {
|
||||
void showAndLogErrorMessage('No controller repository entered.');
|
||||
return;
|
||||
} else if (!REPO_REGEX.test(controllerRepo)) { // Check if user entered invalid input
|
||||
void showAndLogErrorMessage('Invalid repository format. Must be a valid GitHub repository in the format <owner>/<repo>.');
|
||||
return;
|
||||
}
|
||||
void logger.log(`Setting the controller repository as: ${controllerRepo}`);
|
||||
await setRemoteControllerRepo(controllerRepo);
|
||||
}
|
||||
|
||||
void logger.log(`Using controller repository: ${controllerRepo}`);
|
||||
const [owner, repo] = controllerRepo.split('/');
|
||||
|
||||
await runRemoteQueriesApiRequest(credentials, ref, language, repositories, query, owner, repo);
|
||||
}
|
||||
|
||||
async function runRemoteQueriesApiRequest(credentials: Credentials, ref: string, language: string, repositories: string[], query: string, owner: string, repo: string) {
|
||||
const octokit = await credentials.getOctokit();
|
||||
|
||||
try {
|
||||
await octokit.request(
|
||||
'POST /repos/:owner/:repo/code-scanning/codeql/queries',
|
||||
{
|
||||
owner,
|
||||
repo,
|
||||
data: {
|
||||
ref: ref,
|
||||
language: language,
|
||||
repositories: repositories,
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
);
|
||||
void showAndLogInformationMessage(`Successfully scheduled runs. [Click here to see the progress](https://github.com/${owner}/${repo}/actions).`);
|
||||
|
||||
} catch (error) {
|
||||
await attemptRerun(error, credentials, ref, language, repositories, query, owner, repo);
|
||||
}
|
||||
}
|
||||
|
||||
/** Attempts to rerun the query on only the valid repositories */
|
||||
export async function attemptRerun(error: any, credentials: Credentials, ref: string, language: string, repositories: string[], query: string, owner: string, repo: string) {
|
||||
if (typeof error.message === 'string' && error.message.includes('Some repositories were invalid')) {
|
||||
const invalidRepos = error?.response?.data?.invalid_repos || [];
|
||||
const reposWithoutDbUploads = error?.response?.data?.repos_without_db_uploads || [];
|
||||
void logger.log('Unable to run query on some of the specified repositories');
|
||||
if (invalidRepos.length > 0) {
|
||||
void logger.log(`Invalid repos: ${invalidRepos.join(', ')}`);
|
||||
}
|
||||
if (reposWithoutDbUploads.length > 0) {
|
||||
void logger.log(`Repos without DB uploads: ${reposWithoutDbUploads.join(', ')}`);
|
||||
}
|
||||
|
||||
if (invalidRepos.length + reposWithoutDbUploads.length === repositories.length) {
|
||||
// Every repo is invalid in some way
|
||||
void showAndLogErrorMessage('Unable to run query on any of the specified repositories.');
|
||||
return;
|
||||
}
|
||||
|
||||
const popupMessage = 'Unable to run query on some of the specified repositories. [See logs for more details](command:codeQL.showLogs).';
|
||||
const rerunQuery = await showInformationMessageWithAction(popupMessage, 'Rerun on the valid repositories only');
|
||||
if (rerunQuery) {
|
||||
const validRepositories = repositories.filter(r => !invalidRepos.includes(r) && !reposWithoutDbUploads.includes(r));
|
||||
void logger.log(`Rerunning query on set of valid repositories: ${JSON.stringify(validRepositories)}`);
|
||||
await runRemoteQueriesApiRequest(credentials, ref, language, validRepositories, query, owner, repo);
|
||||
}
|
||||
|
||||
} else {
|
||||
void showAndLogErrorMessage(error);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -190,8 +190,15 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
|
||||
this._testStates.fire({ type: 'started', tests: tests } as TestRunStartedEvent);
|
||||
|
||||
const currentDatabaseUri = this.databaseManager.currentDatabaseItem?.databaseUri;
|
||||
const databasesUnderTest = this.databaseManager.databaseItems
|
||||
.filter(database => tests.find(testPath => database.isAffectedByTest(testPath)));
|
||||
const databasesUnderTest: DatabaseItem[] = [];
|
||||
for (const database of this.databaseManager.databaseItems) {
|
||||
for (const test of tests) {
|
||||
if (await database.isAffectedByTest(test)) {
|
||||
databasesUnderTest.push(database);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.removeDatabasesBeforeTests(databasesUnderTest, token);
|
||||
try {
|
||||
@@ -287,7 +294,9 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
|
||||
: 'failed';
|
||||
let message: string | undefined;
|
||||
if (event.failureDescription || event.diff?.length) {
|
||||
message = ['', `${state}: ${event.test}`, event.failureDescription || event.diff?.join('\n'), ''].join('\n');
|
||||
message = event.failureStage === 'RESULT'
|
||||
? ['', `${state}: ${event.test}`, event.failureDescription || event.diff?.join('\n'), ''].join('\n')
|
||||
: ['', `${event.failureStage?.toLowerCase()} error: ${event.test}`, event.failureDescription || `${event.messages[0].severity}: ${event.messages[0].message}`, ''].join('\n');
|
||||
void testLogger.log(message);
|
||||
}
|
||||
this._testStates.fire({
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
display: flex;
|
||||
padding: 0.5em 0;
|
||||
align-items: center;
|
||||
top: 0;
|
||||
background-color: var(--vscode-editorGutter-background);
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vscode-codeql__table-selection-header-item {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import javascript
|
||||
|
||||
select 1
|
||||
@@ -150,7 +150,7 @@ describe('Queries', function() {
|
||||
fs.readFileSync(qlpackFile, 'utf8')
|
||||
);
|
||||
// Should have chosen the js libraries
|
||||
expect(qlpackContents.libraryPathDependencies[0]).to.eq('codeql-javascript');
|
||||
expect(qlpackContents.libraryPathDependencies[0]).to.include('javascript');
|
||||
});
|
||||
|
||||
it('should avoid creating a quick query', async () => {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { expect } from 'chai';
|
||||
import { extensions } from 'vscode';
|
||||
import { extensions, Uri } from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { SemVer } from 'semver';
|
||||
|
||||
import { CodeQLCliServer } from '../../cli';
|
||||
import { CodeQLCliServer, QueryInfoByLanguage } from '../../cli';
|
||||
import { CodeQLExtensionInterface } from '../../extension';
|
||||
import { skipIfNoCodeQL } from '../ensureCli';
|
||||
import { getOnDiskWorkspaceFolders } from '../../helpers';
|
||||
import { getOnDiskWorkspaceFolders, getQlPackForDbscheme, languageToDbScheme } from '../../helpers';
|
||||
import { resolveQueries } from '../../contextual/queryResolver';
|
||||
import { KeyType } from '../../contextual/keyType';
|
||||
|
||||
/**
|
||||
* Perform proper integration tests by running the CLI
|
||||
*/
|
||||
describe('Use cli', function() {
|
||||
const supportedLanguages = ['cpp', 'csharp', 'go', 'java', 'javascript', 'python'];
|
||||
|
||||
this.timeout(60000);
|
||||
|
||||
let cli: CodeQLCliServer;
|
||||
@@ -24,13 +29,15 @@ describe('Use cli', function() {
|
||||
}
|
||||
});
|
||||
|
||||
it('should have the correct version of the cli', async () => {
|
||||
expect(
|
||||
(await cli.getVersion()).toString()
|
||||
).to.eq(
|
||||
new SemVer(process.env.CLI_VERSION || '').toString()
|
||||
);
|
||||
});
|
||||
if (process.env.CLI_VERSION !== 'nightly') {
|
||||
it('should have the correct version of the cli', async () => {
|
||||
expect(
|
||||
(await cli.getVersion()).toString()
|
||||
).to.eq(
|
||||
new SemVer(process.env.CLI_VERSION || '').toString()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
it('should resolve ram', async () => {
|
||||
const result = await (cli as any).resolveRam(8192);
|
||||
@@ -43,11 +50,49 @@ describe('Use cli', function() {
|
||||
it('should resolve query packs', async function() {
|
||||
skipIfNoCodeQL(this);
|
||||
const qlpacks = await cli.resolveQlpacks(getOnDiskWorkspaceFolders());
|
||||
// should have a bunch of qlpacks. just check that a few known ones exist
|
||||
expect(qlpacks['codeql-cpp']).not.to.be.undefined;
|
||||
expect(qlpacks['codeql-csharp']).not.to.be.undefined;
|
||||
expect(qlpacks['codeql-java']).not.to.be.undefined;
|
||||
expect(qlpacks['codeql-javascript']).not.to.be.undefined;
|
||||
expect(qlpacks['codeql-python']).not.to.be.undefined;
|
||||
// Depending on the version of the CLI, the qlpacks may have different names
|
||||
// (e.g. "codeql/javascript-all" vs "codeql-javascript"),
|
||||
// so we just check that the expected languages are included.
|
||||
for (const expectedLanguage of supportedLanguages) {
|
||||
expect((Object.keys(qlpacks)).includes(expectedLanguage));
|
||||
}
|
||||
});
|
||||
|
||||
it('should resolve languages', async function() {
|
||||
skipIfNoCodeQL(this);
|
||||
const languages = await cli.resolveLanguages();
|
||||
for (const expectedLanguage of supportedLanguages) {
|
||||
expect(languages).to.have.property(expectedLanguage).that.is.not.undefined;
|
||||
}
|
||||
});
|
||||
|
||||
it('should resolve query by language', async function() {
|
||||
skipIfNoCodeQL(this);
|
||||
const queryPath = path.join(__dirname, 'data', 'simple-javascript-query.ql');
|
||||
const queryInfo: QueryInfoByLanguage = await cli.resolveQueryByLanguage(getOnDiskWorkspaceFolders(), Uri.file(queryPath));
|
||||
expect((Object.keys(queryInfo.byLanguage))[0]).to.eql('javascript');
|
||||
});
|
||||
|
||||
|
||||
supportedLanguages.forEach(lang => {
|
||||
if (lang === 'go') {
|
||||
// The codeql-go submodule is not available in the integration tests.
|
||||
return;
|
||||
}
|
||||
it(`should resolve printAST queries for ${lang}`, async function() {
|
||||
skipIfNoCodeQL(this);
|
||||
|
||||
const pack = await getQlPackForDbscheme(cli, languageToDbScheme[lang]);
|
||||
expect(pack.dbschemePack).to.contain(lang);
|
||||
if (pack.dbschemePackIsLibraryPack) {
|
||||
expect(pack.queryPack).to.contain(lang);
|
||||
}
|
||||
|
||||
const result = await resolveQueries(cli, pack, KeyType.PrintAstQuery);
|
||||
|
||||
// It doesn't matter what the name or path of the query is, only
|
||||
// that we have found exactly one query.
|
||||
expect(result.length).to.eq(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,10 +6,15 @@ import { workspace } from 'vscode';
|
||||
|
||||
/**
|
||||
* This module ensures that the proper CLI is available for tests of the extension.
|
||||
* There are two environment variables to control this module:
|
||||
* There are three environment variables to control this module:
|
||||
*
|
||||
* - CLI_VERSION: The version of the CLI to install. Defaults to the most recent
|
||||
* version. Note that for now, we must maintain the default version by hand.
|
||||
* This may be set to `nightly`, in which case the `NIGHTLY_URL` variable must
|
||||
* also be set.
|
||||
*
|
||||
* - NIGHTLY_URL: The URL for a nightly release of the CodeQL CLI that will be
|
||||
* used if `CLI_VERSION` is set to `nightly`.
|
||||
*
|
||||
* - CLI_BASE_DIR: The base dir where the CLI will be downloaded and unzipped.
|
||||
* The download location is `${CLI_BASE_DIR}/assets` and the unzip loction is
|
||||
@@ -39,7 +44,7 @@ const _10MB = _1MB * 10;
|
||||
|
||||
// CLI version to test. Hard code the latest as default. And be sure
|
||||
// to update the env if it is not otherwise set.
|
||||
const CLI_VERSION = process.env.CLI_VERSION || 'v2.5.5';
|
||||
const CLI_VERSION = process.env.CLI_VERSION || 'v2.6.3';
|
||||
process.env.CLI_VERSION = CLI_VERSION;
|
||||
|
||||
// Base dir where CLIs will be downloaded into
|
||||
@@ -133,6 +138,11 @@ export function skipIfNoCodeQL(context: Mocha.Context) {
|
||||
* Url to download from
|
||||
*/
|
||||
function getCliDownloadUrl(assetName: string) {
|
||||
if (CLI_VERSION === 'nightly') {
|
||||
if (!process.env.NIGHTLY_URL)
|
||||
throw new Error('Nightly CLI was specified but no URL to download it from was given!');
|
||||
return process.env.NIGHTLY_URL + `/${assetName}`;
|
||||
}
|
||||
return `https://github.com/github/codeql-cli-binaries/releases/download/${CLI_VERSION}/${assetName}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@ describe('config listeners', function() {
|
||||
name: 'codeQL.runningTests.numberOfThreads',
|
||||
property: 'numberTestThreads',
|
||||
values: [1, 0]
|
||||
}, {
|
||||
name: 'codeQL.runningQueries.maxPaths',
|
||||
property: 'maxPaths',
|
||||
values: [0, 1]
|
||||
}]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,28 +18,48 @@ describe('queryResolver', () => {
|
||||
let writeFileSpy: sinon.SinonSpy;
|
||||
let getQlPackForDbschemeSpy: sinon.SinonStub;
|
||||
let getPrimaryDbschemeSpy: sinon.SinonStub;
|
||||
let mockCli: Record<string, sinon.SinonStub>;
|
||||
let mockCli: Record<string, sinon.SinonStub | Record<string, sinon.SinonStub>>;
|
||||
beforeEach(() => {
|
||||
mockCli = {
|
||||
resolveQueriesInSuite: sinon.stub()
|
||||
resolveQueriesInSuite: sinon.stub(),
|
||||
cliConstraints: {
|
||||
supportsAllowLibraryPacksInResolveQueries: sinon.stub().returns(true),
|
||||
}
|
||||
};
|
||||
module = createModule();
|
||||
});
|
||||
|
||||
describe('resolveQueries', () => {
|
||||
|
||||
it('should resolve a query', async () => {
|
||||
mockCli.resolveQueriesInSuite.returns(['a', 'b']);
|
||||
const result = await module.resolveQueries(mockCli, 'my-qlpack', KeyType.DefinitionQuery);
|
||||
const result = await module.resolveQueries(mockCli, { dbschemePack: 'my-qlpack' }, KeyType.DefinitionQuery);
|
||||
expect(result).to.deep.equal(['a', 'b']);
|
||||
expect(writeFileSpy.getCall(0).args[0]).to.match(/.qls$/);
|
||||
expect(yaml.safeLoad(writeFileSpy.getCall(0).args[1])).to.deep.equal({
|
||||
qlpack: 'my-qlpack',
|
||||
expect(yaml.safeLoad(writeFileSpy.getCall(0).args[1])).to.deep.equal([{
|
||||
from: 'my-qlpack',
|
||||
queries: '.',
|
||||
include: {
|
||||
kind: 'definitions',
|
||||
'tags contain': 'ide-contextual-queries/local-definitions'
|
||||
}
|
||||
});
|
||||
}]);
|
||||
});
|
||||
|
||||
it('should resolve a query from the queries pack if this is an old CLI', async () => {
|
||||
// pretend this is an older CLI
|
||||
(mockCli.cliConstraints as any).supportsAllowLibraryPacksInResolveQueries.returns(false);
|
||||
mockCli.resolveQueriesInSuite.returns(['a', 'b']);
|
||||
const result = await module.resolveQueries(mockCli, { dbschemePackIsLibraryPack: true, dbschemePack: 'my-qlpack', queryPack: 'my-qlpack2' }, KeyType.DefinitionQuery);
|
||||
expect(result).to.deep.equal(['a', 'b']);
|
||||
expect(writeFileSpy.getCall(0).args[0]).to.match(/.qls$/);
|
||||
expect(yaml.safeLoad(writeFileSpy.getCall(0).args[1])).to.deep.equal([{
|
||||
from: 'my-qlpack2',
|
||||
queries: '.',
|
||||
include: {
|
||||
kind: 'definitions',
|
||||
'tags contain': 'ide-contextual-queries/local-definitions'
|
||||
}
|
||||
}]);
|
||||
});
|
||||
|
||||
it('should throw an error when there are no queries found', async () => {
|
||||
@@ -48,12 +68,12 @@ describe('queryResolver', () => {
|
||||
// TODO: Figure out why chai-as-promised isn't failing the test on an
|
||||
// unhandled rejection.
|
||||
try {
|
||||
await module.resolveQueries(mockCli, 'my-qlpack', KeyType.DefinitionQuery);
|
||||
await module.resolveQueries(mockCli, { dbschemePack: 'my-qlpack' }, KeyType.DefinitionQuery);
|
||||
// should reject
|
||||
expect(true).to.be.false;
|
||||
} catch (e) {
|
||||
expect(e.message).to.eq(
|
||||
'Couldn\'t find any queries tagged ide-contextual-queries/local-definitions for qlpack my-qlpack'
|
||||
'Couldn\'t find any queries tagged ide-contextual-queries/local-definitions in any of the following packs: my-qlpack.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,19 +13,23 @@ import {
|
||||
looksLikeLgtmUrl,
|
||||
findDirWithFile,
|
||||
} from '../../databaseFetcher';
|
||||
import { ProgressCallback } from '../../commandRunner';
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('databaseFetcher', function() {
|
||||
describe('databaseFetcher', function () {
|
||||
// These tests make API calls and may need extra time to complete.
|
||||
this.timeout(10000);
|
||||
|
||||
describe('convertToDatabaseUrl', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let quickPickSpy: sinon.SinonStub;
|
||||
let progressSpy: ProgressCallback;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
quickPickSpy = sandbox.stub(window, 'showQuickPick');
|
||||
progressSpy = sandbox.spy();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -35,7 +39,7 @@ describe('databaseFetcher', function() {
|
||||
it('should convert a project url to a database url', async () => {
|
||||
quickPickSpy.resolves('javascript');
|
||||
const lgtmUrl = 'https://lgtm.com/projects/g/github/codeql';
|
||||
const dbUrl = await convertToDatabaseUrl(lgtmUrl);
|
||||
const dbUrl = await convertToDatabaseUrl(lgtmUrl, progressSpy);
|
||||
|
||||
expect(dbUrl).to.equal(
|
||||
'https://lgtm.com/api/v1.0/snapshots/1506465042581/javascript'
|
||||
@@ -48,28 +52,31 @@ describe('databaseFetcher', function() {
|
||||
quickPickSpy.resolves('python');
|
||||
const lgtmUrl =
|
||||
'https://lgtm.com/projects/g/github/codeql/subpage/subpage2?query=xxx';
|
||||
const dbUrl = await convertToDatabaseUrl(lgtmUrl);
|
||||
const dbUrl = await convertToDatabaseUrl(lgtmUrl, progressSpy);
|
||||
|
||||
expect(dbUrl).to.equal(
|
||||
'https://lgtm.com/api/v1.0/snapshots/1506465042581/python'
|
||||
);
|
||||
expect(progressSpy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should convert a raw slug to a database url with extra path segments', async () => {
|
||||
quickPickSpy.resolves('python');
|
||||
const lgtmUrl =
|
||||
'g/github/codeql';
|
||||
const dbUrl = await convertToDatabaseUrl(lgtmUrl);
|
||||
const dbUrl = await convertToDatabaseUrl(lgtmUrl, progressSpy);
|
||||
|
||||
expect(dbUrl).to.equal(
|
||||
'https://lgtm.com/api/v1.0/snapshots/1506465042581/python'
|
||||
);
|
||||
expect(progressSpy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should fail on a nonexistent project', async () => {
|
||||
quickPickSpy.resolves('javascript');
|
||||
const lgtmUrl = 'https://lgtm.com/projects/g/github/hucairz';
|
||||
await expect(convertToDatabaseUrl(lgtmUrl)).to.rejectedWith(/Invalid LGTM URL/);
|
||||
await expect(convertToDatabaseUrl(lgtmUrl, progressSpy)).to.rejectedWith(/Invalid LGTM URL/);
|
||||
expect(progressSpy).to.have.callCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -54,6 +54,23 @@ describe('CompletedQuery', () => {
|
||||
expect(completedQuery.queryName).to.eq('Quick evaluation of yz:1');
|
||||
});
|
||||
|
||||
it('should get the query file name', () => {
|
||||
const completedQuery = mockCompletedQuery();
|
||||
|
||||
// from the query path
|
||||
expect(completedQuery.queryFileName).to.eq('stu');
|
||||
|
||||
// from quick eval position
|
||||
(completedQuery.query as any).quickEvalPosition = {
|
||||
line: 1,
|
||||
endLine: 2,
|
||||
fileName: '/home/users/yz'
|
||||
};
|
||||
expect(completedQuery.queryFileName).to.eq('yz:1-2');
|
||||
(completedQuery.query as any).quickEvalPosition.endLine = 1;
|
||||
expect(completedQuery.queryFileName).to.eq('yz:1');
|
||||
});
|
||||
|
||||
it('should get the label', () => {
|
||||
const completedQuery = mockCompletedQuery();
|
||||
expect(completedQuery.getLabel()).to.eq('ghi');
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import 'vscode-test';
|
||||
import 'mocha';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import * as sinon from 'sinon';
|
||||
import * as chai from 'chai';
|
||||
import { window } from 'vscode';
|
||||
import * as pq from 'proxyquire';
|
||||
|
||||
const proxyquire = pq.noPreserveCache();
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('run-remote-query', function() {
|
||||
|
||||
describe('getRepositories', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let quickPickSpy: sinon.SinonStub;
|
||||
let showInputBoxSpy: sinon.SinonStub;
|
||||
let getRemoteRepositoryListsSpy: sinon.SinonStub;
|
||||
let showAndLogErrorMessageSpy: sinon.SinonStub;
|
||||
let mod: any;
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
quickPickSpy = sandbox.stub(window, 'showQuickPick');
|
||||
showInputBoxSpy = sandbox.stub(window, 'showInputBox');
|
||||
getRemoteRepositoryListsSpy = sandbox.stub();
|
||||
showAndLogErrorMessageSpy = sandbox.stub();
|
||||
mod = proxyquire('../../run-remote-query', {
|
||||
'./config': {
|
||||
getRemoteRepositoryLists: getRemoteRepositoryListsSpy
|
||||
},
|
||||
'./helpers': {
|
||||
showAndLogErrorMessage: showAndLogErrorMessageSpy
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should run on a repo list that you chose from your pre-defined config', async () => {
|
||||
// fake return values
|
||||
quickPickSpy.resolves(
|
||||
{ repoList: ['foo/bar', 'foo/baz'] }
|
||||
);
|
||||
getRemoteRepositoryListsSpy.returns(
|
||||
{
|
||||
'list1': ['foo/bar', 'foo/baz'],
|
||||
'list2': [],
|
||||
}
|
||||
);
|
||||
|
||||
// make the function call
|
||||
const repoList = await mod.getRepositories();
|
||||
|
||||
// Check that the return value is correct
|
||||
expect(repoList).to.deep.eq(
|
||||
['foo/bar', 'foo/baz']
|
||||
);
|
||||
});
|
||||
|
||||
// Test the regex in various "good" cases
|
||||
const goodRepos = [
|
||||
'owner/repo',
|
||||
'owner-with-hyphens/repo-with-hyphens_and_underscores',
|
||||
'ownerWithNumbers58/repoWithNumbers37'
|
||||
];
|
||||
goodRepos.forEach(repo => {
|
||||
it(`should run on a valid repo that you enter in the text box: ${repo}`, async () => {
|
||||
// fake return values
|
||||
getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists
|
||||
showInputBoxSpy.resolves(repo);
|
||||
|
||||
// make the function call
|
||||
const repoList = await mod.getRepositories();
|
||||
|
||||
// Check that the return value is correct
|
||||
expect(repoList).to.deep.equal(
|
||||
[repo]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Test the regex in various "bad" cases
|
||||
const badRepos = [
|
||||
'invalid_owner/repo',
|
||||
'owner/repo+some&invalid&stuff',
|
||||
'owner-with-no-repo/',
|
||||
'/repo-with-no-owner'
|
||||
];
|
||||
badRepos.forEach(repo => {
|
||||
it(`should show an error message if you enter an invalid repo in the text box: ${repo}`, async () => {
|
||||
// fake return values
|
||||
getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists
|
||||
showInputBoxSpy.resolves(repo);
|
||||
|
||||
// make the function call
|
||||
await mod.getRepositories();
|
||||
|
||||
// check that we get the right error message
|
||||
expect(showAndLogErrorMessageSpy.firstCall.args[0]).to.contain('Invalid repository format');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('attemptRerun', () => {
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
let showAndLogErrorMessageSpy: sinon.SinonStub;
|
||||
let showInformationMessageWithActionSpy: sinon.SinonStub;
|
||||
let mockRequest: sinon.SinonStub;
|
||||
let logSpy: sinon.SinonStub;
|
||||
let mod: any;
|
||||
|
||||
const error = {
|
||||
message: 'Unable to run query on the specified repositories. Some repositories were invalid or don\'t have database uploads enabled.',
|
||||
response: {
|
||||
data: {
|
||||
invalid_repos: ['abc/def', 'ghi/jkl'],
|
||||
repos_without_db_uploads: ['mno/pqr', 'stu/vwx']
|
||||
}
|
||||
}
|
||||
};
|
||||
const ref = 'main';
|
||||
const language = 'javascript';
|
||||
const credentials = getMockCredentials(0);
|
||||
const query = 'select 1';
|
||||
const owner = 'owner';
|
||||
const repo = 'repo';
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
logSpy = sandbox.stub();
|
||||
showAndLogErrorMessageSpy = sandbox.stub();
|
||||
showInformationMessageWithActionSpy = sandbox.stub();
|
||||
mod = proxyquire('../../run-remote-query', {
|
||||
'./helpers': {
|
||||
showAndLogErrorMessage: showAndLogErrorMessageSpy,
|
||||
showInformationMessageWithAction: showInformationMessageWithActionSpy
|
||||
},
|
||||
'./logging': {
|
||||
'logger': {
|
||||
log: logSpy
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should return and log error if it can\'t run on any repos', async () => {
|
||||
const repositories = ['abc/def', 'ghi/jkl', 'mno/pqr', 'stu/vwx'];
|
||||
|
||||
// make the function call
|
||||
await mod.attemptRerun(error, credentials, ref, language, repositories, query, owner, repo);
|
||||
|
||||
// check logging output
|
||||
expect(logSpy.firstCall.args[0]).to.contain('Unable to run query');
|
||||
expect(logSpy.secondCall.args[0]).to.contain('Invalid repos: abc/def, ghi/jkl');
|
||||
expect(logSpy.thirdCall.args[0]).to.contain('Repos without DB uploads: mno/pqr, stu/vwx');
|
||||
expect(showAndLogErrorMessageSpy.firstCall.args[0]).to.contain('Unable to run query on any');
|
||||
});
|
||||
|
||||
it('should list invalid repos and repos without DB uploads, and rerun on valid ones', async () => {
|
||||
const repositories = ['foo/bar', 'abc/def', 'ghi/jkl', 'mno/pqr', 'foo/baz'];
|
||||
|
||||
// fake return values
|
||||
showInformationMessageWithActionSpy.resolves(true);
|
||||
|
||||
// make the function call
|
||||
await mod.attemptRerun(error, credentials, ref, language, repositories, query, owner, repo);
|
||||
|
||||
// check logging output
|
||||
expect(logSpy.firstCall.args[0]).to.contain('Unable to run query');
|
||||
expect(logSpy.secondCall.args[0]).to.contain('Invalid repos: abc/def, ghi/jkl');
|
||||
expect(logSpy.thirdCall.args[0]).to.contain('Repos without DB uploads: mno/pqr');
|
||||
|
||||
// check that the correct information message is displayed
|
||||
expect(showInformationMessageWithActionSpy.firstCall.args[0]).to.contain('Unable to run query on some');
|
||||
expect(showInformationMessageWithActionSpy.firstCall.args[1]).to.contain('Rerun');
|
||||
|
||||
// check that API request is made again, with only valid repos
|
||||
expect(logSpy.lastCall.args[0]).to.contain('valid repositories: ["foo/bar","foo/baz"]');
|
||||
// test a few values in the octokit request
|
||||
expect(mockRequest.firstCall.args[1].data.language).to.eq('javascript');
|
||||
expect(mockRequest.firstCall.args[1].data.repositories).to.deep.eq(['foo/bar', 'foo/baz']);
|
||||
|
||||
});
|
||||
|
||||
function getMockCredentials(response: any) {
|
||||
mockRequest = sinon.stub().resolves(response);
|
||||
return {
|
||||
getOctokit: () => ({
|
||||
request: mockRequest
|
||||
})
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
describe('runRemoteQuery', () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
@@ -96,7 +96,7 @@ describe('test-adapter', () => {
|
||||
type: 'test',
|
||||
state: 'errored',
|
||||
test: gPath,
|
||||
message: `\nerrored: ${gPath}\npqr\nxyz\n`,
|
||||
message: `\ncompilation error: ${gPath}\nERROR: abc\n`,
|
||||
decorations: [
|
||||
{ line: 1, message: 'abc' }
|
||||
]
|
||||
@@ -149,14 +149,16 @@ describe('test-adapter', () => {
|
||||
pass: false,
|
||||
diff: ['pqr', 'xyz'],
|
||||
// a compile error
|
||||
failureStage: 'COMPILATION',
|
||||
messages: [
|
||||
{ position: { line: 1 }, message: 'abc' }
|
||||
{ position: { line: 1 }, message: 'abc', severity: 'ERROR' }
|
||||
]
|
||||
});
|
||||
yield Promise.resolve({
|
||||
test: Uri.parse('file:/ab/c/e/f/h.ql').fsPath,
|
||||
pass: false,
|
||||
diff: ['jkh', 'tuv'],
|
||||
failureStage: 'RESULT',
|
||||
messages: []
|
||||
});
|
||||
})()
|
||||
|
||||
Reference in New Issue
Block a user