Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a5514c696 | ||
|
|
29f92575ee | ||
|
|
5d63431b8c | ||
|
|
17eee86765 | ||
|
|
95d5274fd4 | ||
|
|
959552544a | ||
|
|
16fab7f45d | ||
|
|
cb03da3716 | ||
|
|
f968f8e2f5 | ||
|
|
c247292181 | ||
|
|
518e6c14cc | ||
|
|
37cf525c8e | ||
|
|
1f4e69940d | ||
|
|
72878fb6fd | ||
|
|
6b343b4581 | ||
|
|
b191f68599 | ||
|
|
ef84d8d362 | ||
|
|
ef55d9d4e0 | ||
|
|
ff841950ae | ||
|
|
aaf9e1fb9c | ||
|
|
7f885755c2 | ||
|
|
8c55e3ef2d | ||
|
|
039343efa2 | ||
|
|
d0982f34a4 | ||
|
|
890821b273 | ||
|
|
84e2cf7986 | ||
|
|
648bf4b629 | ||
|
|
8ccb7c4fa4 | ||
|
|
73fc37d370 | ||
|
|
0a3d4095b7 | ||
|
|
32d4deb575 | ||
|
|
d2409054e2 | ||
|
|
6ae5cd3ac3 | ||
|
|
0dfc64c7e8 | ||
|
|
6a9c9a1eb4 | ||
|
|
f62cce32da | ||
|
|
a36ff8ca1e | ||
|
|
0d1199bb64 | ||
|
|
3edd8ec1d1 | ||
|
|
4a030dc2f4 | ||
|
|
a4f19c9b5d | ||
|
|
353a87de12 | ||
|
|
a2cda79ceb | ||
|
|
bc73712987 | ||
|
|
09c4e7e99b | ||
|
|
d0e0ad619b | ||
|
|
e4ff8d1fa8 | ||
|
|
9052851f9a | ||
|
|
a946965331 | ||
|
|
10177412f6 | ||
|
|
4519e0f951 | ||
|
|
0d2b44cdba | ||
|
|
0045891f9d | ||
|
|
2b712827df | ||
|
|
65b5b68df6 | ||
|
|
f21296e4f6 | ||
|
|
762edd137c | ||
|
|
b3dc7d75a8 | ||
|
|
9ad0bf6f43 | ||
|
|
f8804f946c | ||
|
|
3c07be5f74 | ||
|
|
cd329eeaeb | ||
|
|
2671414f32 | ||
|
|
b6bd534857 | ||
|
|
8093d9a529 | ||
|
|
aebab082c2 | ||
|
|
36d612e5b0 | ||
|
|
8459edb57c | ||
|
|
af965c941a | ||
|
|
eaa26e5ef7 | ||
|
|
546ec2eb1c | ||
|
|
565ea0d8a0 | ||
|
|
258f43132c | ||
|
|
b7a72b9d21 | ||
|
|
d2138907b9 | ||
|
|
bce3413158 | ||
|
|
2b53396146 | ||
|
|
19a76dcbee | ||
|
|
56b62ff758 | ||
|
|
9083c5d649 | ||
|
|
49c0d39a50 | ||
|
|
57ea215639 | ||
|
|
528cbc8d49 | ||
|
|
2c5b672c81 | ||
|
|
f0055910c1 | ||
|
|
657df5e07d | ||
|
|
53d5c2438a | ||
|
|
ac941eb9dd | ||
|
|
e5e854822d | ||
|
|
868b356588 | ||
|
|
2dd841e667 | ||
|
|
609fea404d | ||
|
|
24da63fbfa | ||
|
|
10156b1f49 | ||
|
|
3694fdaecb | ||
|
|
4c30374dc3 | ||
|
|
26d83b5cef | ||
|
|
3639dcb806 | ||
|
|
4aa752135d | ||
|
|
80c6ea6eac | ||
|
|
2243c21afc | ||
|
|
46bddcd8fa | ||
|
|
df5dccc3f6 | ||
|
|
3207c594e7 | ||
|
|
70de59eabd | ||
|
|
27dd804731 | ||
|
|
240e0fbd4e | ||
|
|
f65caa0d85 | ||
|
|
e7192eb423 | ||
|
|
06b51326a3 | ||
|
|
82a6ef4844 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -9,4 +9,4 @@ Replace this with a description of the changes your pull request makes.
|
||||
|
||||
- [ ] [CHANGELOG.md](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/CHANGELOG.md) has been updated to incorporate all user visible changes made by this pull request.
|
||||
- [ ] Issues have been created for any UI or other user-facing changes made by this pull request.
|
||||
- [ ] `@github/product-docs-dsp` has been cc'd in all issues for UI or other user-facing changes made by this pull request.
|
||||
- [ ] `@github/docs-content-dsp` has been cc'd in all issues for UI or other user-facing changes made by this pull request.
|
||||
|
||||
1
.github/workflows/codeql.yml
vendored
1
.github/workflows/codeql.yml
vendored
@@ -2,6 +2,7 @@ name: "Code Scanning - CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
|
||||
15
.github/workflows/label-issue.yml
vendored
Normal file
15
.github/workflows/label-issue.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: Label issue
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
label:
|
||||
name: Label issue
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Label issue
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo '{"labels": ["VSCode"]}' | gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels --input -
|
||||
18
.github/workflows/main.yml
vendored
18
.github/workflows/main.yml
vendored
@@ -19,11 +19,15 @@ jobs:
|
||||
node-version: '10.18.1'
|
||||
|
||||
- name: Install dependencies
|
||||
run: node common/scripts/install-run-rush.js install
|
||||
run: |
|
||||
cd extensions/ql-vscode
|
||||
npm install
|
||||
shell: bash
|
||||
|
||||
- name: Build
|
||||
run: node common/scripts/install-run-rush.js build
|
||||
run: |
|
||||
cd extensions/ql-vscode
|
||||
npm run build
|
||||
shell: bash
|
||||
|
||||
- name: Prepare artifacts
|
||||
@@ -33,7 +37,7 @@ jobs:
|
||||
cp dist/*.vsix artifacts
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
uses: actions/upload-artifact@v2
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
with:
|
||||
name: vscode-codeql-extension
|
||||
@@ -57,11 +61,15 @@ jobs:
|
||||
|
||||
# We have to build the dependencies in `lib` before running any tests.
|
||||
- name: Install dependencies
|
||||
run: node common/scripts/install-run-rush.js install
|
||||
run: |
|
||||
cd extensions/ql-vscode
|
||||
npm install
|
||||
shell: bash
|
||||
|
||||
- name: Build
|
||||
run: node common/scripts/install-run-rush.js build
|
||||
run: |
|
||||
cd extensions/ql-vscode
|
||||
npm run build
|
||||
shell: bash
|
||||
|
||||
- name: Lint
|
||||
|
||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -34,11 +34,15 @@ jobs:
|
||||
node-version: '10.18.1'
|
||||
|
||||
- name: Install dependencies
|
||||
run: node common/scripts/install-run-rush.js install
|
||||
run: |
|
||||
cd extensions/ql-vscode
|
||||
npm install
|
||||
shell: bash
|
||||
|
||||
- name: Build
|
||||
run: node common/scripts/install-run-rush.js build --release
|
||||
run: |
|
||||
cd extensions/ql-vscode
|
||||
npm run build -- --release
|
||||
shell: bash
|
||||
|
||||
- name: Prepare artifacts
|
||||
@@ -59,7 +63,7 @@ jobs:
|
||||
# This is just in case the release itself fails and we want to access the built artifacts from Actions.
|
||||
# TODO Remove if not useful.
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@master
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: vscode-codeql-extension
|
||||
path: artifacts
|
||||
@@ -110,6 +114,12 @@ jobs:
|
||||
NEXT_VERSION="$(npm version patch)"
|
||||
echo "::set-output name=next_version::$NEXT_VERSION"
|
||||
|
||||
- name: Add changelog for next release
|
||||
if: success()
|
||||
run: |
|
||||
cd extensions/ql-vscode
|
||||
perl -i -pe 's/^/## \[UNRELEASED\]\n\n/ if($.==3)' CHANGELOG.md
|
||||
|
||||
- name: Create version bump PR
|
||||
uses: peter-evans/create-pull-request@c7b64af0a489eae91f7890f2c1b63d13cc2d8ab7 # v2.4.2
|
||||
if: success()
|
||||
|
||||
24
.vscode/launch.json
vendored
24
.vscode/launch.json
vendored
@@ -8,18 +8,18 @@
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql"
|
||||
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode"
|
||||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/dist/vscode-codeql/out/**/*.js",
|
||||
"${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-bqrs/out/**/*.js",
|
||||
"${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-io/out/**/*.js",
|
||||
"${workspaceRoot}/dist/vscode-codeql/node_modules/semmle-io-node/out/**/*.js",
|
||||
"${workspaceRoot}/dist/vscode-codeql/node_modules/@github/codeql-vscode-utils/out/**/*.js"
|
||||
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
|
||||
],
|
||||
"preLaunchTask": "Build"
|
||||
"preLaunchTask": "Build",
|
||||
"env": {
|
||||
// uncomment to allow debugging the language server Java process from a remote java debugger
|
||||
// "DEBUG_LANGUAGE_SERVER": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Launch Unit Tests (vscode-codeql)",
|
||||
@@ -54,14 +54,13 @@
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
|
||||
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
|
||||
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/no-workspace/index"
|
||||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/dist/vscode-codeql/out/**/*.js",
|
||||
"${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/**/*.js"
|
||||
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
|
||||
],
|
||||
"preLaunchTask": "Build"
|
||||
},
|
||||
@@ -71,15 +70,14 @@
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceRoot}/dist/vscode-codeql",
|
||||
"--extensionDevelopmentPath=${workspaceRoot}/extensions/ql-vscode",
|
||||
"--extensionTestsPath=${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/minimal-workspace/index",
|
||||
"${workspaceRoot}/extensions/ql-vscode/test/data"
|
||||
],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/dist/vscode-codeql/out/**/*.js",
|
||||
"${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/**/*.js"
|
||||
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
|
||||
],
|
||||
"preLaunchTask": "Build"
|
||||
}
|
||||
|
||||
66
.vscode/tasks.json
vendored
66
.vscode/tasks.json
vendored
@@ -10,7 +10,10 @@
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"command": "node common/scripts/install-run-rush.js build --verbose",
|
||||
"command": "npm run build",
|
||||
"options": {
|
||||
"cwd": "extensions/ql-vscode/"
|
||||
},
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
@@ -33,64 +36,13 @@
|
||||
"$ts-webpack"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Rebuild",
|
||||
"type": "shell",
|
||||
"group": "build",
|
||||
"command": "node common/scripts/install-run-rush.js rebuild --verbose",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "typescript",
|
||||
"fileLocation": "absolute",
|
||||
"pattern": {
|
||||
"regexp": "^\\[gulp-typescript\\] ([^(]+)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\): error TS\\d+: (.*)$",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Update",
|
||||
"type": "shell",
|
||||
"command": "node common/scripts/install-run-rush.js update",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Update (full)",
|
||||
"type": "shell",
|
||||
"command": "node common/scripts/install-run-rush.js update --full",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Format",
|
||||
"type": "shell",
|
||||
"command": "node common/scripts/install-run-rush.js format",
|
||||
"command": "npm run format",
|
||||
"options": {
|
||||
"cwd": "extensions/ql-vscode/"
|
||||
},
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
@@ -111,4 +63,4 @@
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,87 +32,25 @@ Here are a few things you can do that will increase the likelihood of your pull
|
||||
Make sure you have a fairly recent version of vscode (>1.32) and are using nodejs
|
||||
version >=v10.13.0. (Tested on v10.15.1 and v10.16.0).
|
||||
|
||||
This repo uses [Rush](https://rushjs.io) to handle package management, building, and other
|
||||
operations across multiple projects. See the Rush "[Getting started as a developer](https://rushjs.io/pages/developer/new_developer/)" docs
|
||||
for more details.
|
||||
### Installing all packages
|
||||
|
||||
If you plan on building from the command line, it's easiest if Rush is installed globally:
|
||||
From the command line, go to the directory `extensions/ql-vscode` and run
|
||||
|
||||
```shell
|
||||
npm install -g @microsoft/rush
|
||||
npm install
|
||||
```
|
||||
|
||||
To get started, run:
|
||||
### Building the extension
|
||||
|
||||
From the command line, go to the directory `extensions/ql-vscode` and run
|
||||
|
||||
```shell
|
||||
rush update && rush build
|
||||
npm run build
|
||||
```
|
||||
|
||||
Note that when you run the `rush` command from the globally installed version, it will examine the
|
||||
`rushVersion` property in the repo's `rush.json`, and if it differs from the globally installed
|
||||
version, it will download, cache, and run the version of Rush specified in the `rushVersion`
|
||||
property.
|
||||
Alternatively, you can build the extension within VS Code via `Terminal > Run Build Task...` (or `Ctrl+Shift+B` with the default key bindings).
|
||||
|
||||
A few more things to know about using rush:
|
||||
|
||||
* Avoid running `npm` for any commands that install/link dependencies
|
||||
* Instead use the *rush* equivalent: `rush add <package>`, `rush update`, etc.
|
||||
* If you plan on only building via VS Code tasks, you don't need Rush installed at all, since those
|
||||
tasks run `common/scripts/install-run-rush.js` to bootstrap a locally installed and cached copy of
|
||||
Rush.
|
||||
|
||||
### Building
|
||||
|
||||
#### Installing all packages (instead of `npm install`)
|
||||
|
||||
After updating any `package.json` file, or after checking or pulling a new branch, you need to
|
||||
make sure all the right npm packages are installed, which you would normally do via `npm install` in
|
||||
a single-project repo. With Rush, you need to do an "update" instead:
|
||||
|
||||
##### From VS Code
|
||||
|
||||
`Terminal > Run Task... > Update`
|
||||
|
||||
##### From the command line
|
||||
|
||||
```shell
|
||||
rush update
|
||||
```
|
||||
|
||||
#### Building all projects (instead of `gulp`)
|
||||
|
||||
Rush builds all projects in the repo, in dependency order, building multiple projects in parallel
|
||||
where possible. By default, the build also packages the extension itself into a .vsix file in the
|
||||
`dist` directory. To build:
|
||||
|
||||
##### From VS Code
|
||||
|
||||
`Terminal > Run Build Task...` (or just `Ctrl+Shift+B` with the default key bindings)
|
||||
|
||||
##### From the command line
|
||||
|
||||
```shell
|
||||
rush build --verbose
|
||||
```
|
||||
|
||||
#### Forcing a clean build
|
||||
|
||||
Rush does a reasonable job of detecting on its own which projects need to be rebuilt, but if you need to
|
||||
force a full rebuild of all projects:
|
||||
|
||||
##### From VS Code
|
||||
|
||||
`Terminal > Run Task... > Rebuild`
|
||||
|
||||
##### From the command line
|
||||
|
||||
```shell
|
||||
rush rebuild --verbose
|
||||
```
|
||||
|
||||
Note that `rush rebuild` performs a complete rebuild, whereas `rush build` performs an incremental build and in many cases will not need to do anything at all.
|
||||
|
||||
### Installing
|
||||
### Installing the extension
|
||||
|
||||
You can install the `.vsix` file from within VS Code itself, from the Extensions container in the sidebar:
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
This directory contains content from https://github.com/microsoft/rushstack,
|
||||
used under the MIT license as follows.
|
||||
See https://github.com/microsoft/rushstack/blob/master/stack/rush-stack/LICENSE.
|
||||
|
||||
@microsoft/rush-stack
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,12 +0,0 @@
|
||||
# Rush uses this file to configure the package registry, regardless of whether the
|
||||
# package manager is PNPM, NPM, or Yarn. Prior to invoking the package manager,
|
||||
# Rush will always copy this file to the folder where installation is performed.
|
||||
# When NPM is the package manager, Rush works around NPM's processing of
|
||||
# undefined environment variables by deleting any lines that reference undefined
|
||||
# environment variables.
|
||||
#
|
||||
# DO NOT SPECIFY AUTHENTICATION CREDENTIALS IN THIS FILE. It should only be used
|
||||
# to configure registry sources.
|
||||
|
||||
registry=https://registry.npmjs.org/
|
||||
always-auth=false
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* This configuration file defines custom commands for the "rush" command-line.
|
||||
* For full documentation, please see https://rushjs.io/pages/configs/command_line_json/
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json",
|
||||
"commands": [
|
||||
{
|
||||
"commandKind": "bulk",
|
||||
"name": "format",
|
||||
"summary": "Reformat source code in all projects",
|
||||
"description": "Runs the `format` npm task in each project, if present.",
|
||||
"safeForSimultaneousRushProcesses": false,
|
||||
"enableParallelism": true,
|
||||
"ignoreDependencyOrder": true,
|
||||
"ignoreMissingScript": true,
|
||||
"allowWarningsInSuccessfulBuild": false
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"parameterKind": "flag",
|
||||
"longName": "--release",
|
||||
"shortName": "-r",
|
||||
"description": "Perform a release build",
|
||||
"associatedCommands": [
|
||||
"build",
|
||||
"rebuild"
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* This configuration file specifies NPM dependency version selections that affect all projects
|
||||
* in a Rush repo. For full documentation, please see https://rushjs.io
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json",
|
||||
|
||||
/**
|
||||
* A table that specifies a "preferred version" for a dependency package. The "preferred version"
|
||||
* is typically used to hold an indirect dependency back to a specific version, however generally
|
||||
* it can be any SemVer range specifier (e.g. "~1.2.3"), and it will narrow any (compatible)
|
||||
* SemVer range specifier. See the Rush documentation for details about this feature.
|
||||
*/
|
||||
"preferredVersions": {
|
||||
|
||||
/**
|
||||
* When someone asks for "^1.0.0" make sure they get "1.2.3" when working in this repo,
|
||||
* instead of the latest version.
|
||||
*/
|
||||
// "some-library": "1.2.3"
|
||||
},
|
||||
|
||||
/**
|
||||
* The "rush check" command can be used to enforce that every project in the repo must specify
|
||||
* the same SemVer range for a given dependency. However, sometimes exceptions are needed.
|
||||
* The allowedAlternativeVersions table allows you to list other SemVer ranges that will be
|
||||
* accepted by "rush check" for a given dependency.
|
||||
*
|
||||
* IMPORTANT: THIS TABLE IS FOR *ADDITIONAL* VERSION RANGES THAT ARE ALTERNATIVES TO THE
|
||||
* USUAL VERSION (WHICH IS INFERRED BY LOOKING AT ALL PROJECTS IN THE REPO).
|
||||
* This design avoids unnecessary churn in this file.
|
||||
*/
|
||||
"allowedAlternativeVersions": {
|
||||
|
||||
/**
|
||||
* For example, allow some projects to use an older TypeScript compiler
|
||||
* (in addition to whatever "usual" version is being used by other projects in the repo):
|
||||
*/
|
||||
// "typescript": [
|
||||
// "~2.4.0"
|
||||
// ]
|
||||
}
|
||||
}
|
||||
8257
common/config/rush/pnpm-lock.yaml
generated
8257
common/config/rush/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,32 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* When using the PNPM package manager, you can use pnpmfile.js to workaround
|
||||
* dependencies that have mistakes in their package.json file. (This feature is
|
||||
* functionally similar to Yarn's "resolutions".)
|
||||
*
|
||||
* For details, see the PNPM documentation:
|
||||
* https://pnpm.js.org/docs/en/hooks.html
|
||||
*
|
||||
* IMPORTANT: SINCE THIS FILE CONTAINS EXECUTABLE CODE, MODIFYING IT IS LIKELY
|
||||
* TO INVALIDATE ANY CACHED DEPENDENCY ANALYSIS. We recommend to run "rush update --full"
|
||||
* after any modification to pnpmfile.js.
|
||||
*
|
||||
*/
|
||||
module.exports = {
|
||||
hooks: {
|
||||
readPackage
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This hook is invoked during installation before a package's dependencies
|
||||
* are selected.
|
||||
* The `packageJson` parameter is the deserialized package.json
|
||||
* contents for the package that is about to be installed.
|
||||
* The `context` parameter provides a log() function.
|
||||
* The return value is the updated object.
|
||||
*/
|
||||
function readPackage(packageJson, context) {
|
||||
return packageJson;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* This is configuration file is used for advanced publishing configurations with Rush.
|
||||
* For full documentation, please see https://rushjs.io/pages/configs/version_policies_json/
|
||||
*/
|
||||
[
|
||||
{
|
||||
"definitionName": "individualVersion",
|
||||
"policyName": "utilities"
|
||||
}
|
||||
]
|
||||
@@ -1,67 +0,0 @@
|
||||
"use strict";
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||||
// See the @microsoft/rush package's LICENSE file for license information.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
|
||||
//
|
||||
// This script is intended for usage in an automated build environment where the Rush command may not have
|
||||
// been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
|
||||
// specified in the rush.json configuration file (if not already installed), and then pass a command-line to it.
|
||||
// An example usage would be:
|
||||
//
|
||||
// node common/scripts/install-run-rush.js install
|
||||
//
|
||||
// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const install_run_1 = require("./install-run");
|
||||
const PACKAGE_NAME = '@microsoft/rush';
|
||||
const RUSH_PREVIEW_VERSION = 'RUSH_PREVIEW_VERSION';
|
||||
function _getRushVersion() {
|
||||
const rushPreviewVersion = process.env[RUSH_PREVIEW_VERSION];
|
||||
if (rushPreviewVersion !== undefined) {
|
||||
console.log(`Using Rush version from environment variable ${RUSH_PREVIEW_VERSION}=${rushPreviewVersion}`);
|
||||
return rushPreviewVersion;
|
||||
}
|
||||
const rushJsonFolder = install_run_1.findRushJsonFolder();
|
||||
const rushJsonPath = path.join(rushJsonFolder, install_run_1.RUSH_JSON_FILENAME);
|
||||
try {
|
||||
const rushJsonContents = fs.readFileSync(rushJsonPath, 'utf-8');
|
||||
// Use a regular expression to parse out the rushVersion value because rush.json supports comments,
|
||||
// but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script.
|
||||
const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/);
|
||||
return rushJsonMatches[1];
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` +
|
||||
'The \'rushVersion\' field is either not assigned in rush.json or was specified ' +
|
||||
'using an unexpected syntax.');
|
||||
}
|
||||
}
|
||||
function _run() {
|
||||
const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ ...packageBinArgs /* [build, --to, myproject] */] = process.argv;
|
||||
// Detect if this script was directly invoked, or if the install-run-rushx script was invokved to select the
|
||||
// appropriate binary inside the rush package to run
|
||||
const scriptName = path.basename(scriptPath);
|
||||
const bin = scriptName.toLowerCase() === 'install-run-rushx.js' ? 'rushx' : 'rush';
|
||||
if (!nodePath || !scriptPath) {
|
||||
throw new Error('Unexpected exception: could not detect node path or script path');
|
||||
}
|
||||
if (process.argv.length < 3) {
|
||||
console.log(`Usage: ${scriptName} <command> [args...]`);
|
||||
if (scriptName === 'install-run-rush.js') {
|
||||
console.log(`Example: ${scriptName} build --to myproject`);
|
||||
}
|
||||
else {
|
||||
console.log(`Example: ${scriptName} custom-command`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
install_run_1.runWithErrorAndStatusCode(() => {
|
||||
const version = _getRushVersion();
|
||||
console.log(`The rush.json configuration requests Rush version ${version}`);
|
||||
return install_run_1.installAndRun(PACKAGE_NAME, version, bin, packageBinArgs);
|
||||
});
|
||||
}
|
||||
_run();
|
||||
//# sourceMappingURL=install-run-rush.js.map
|
||||
@@ -1,18 +0,0 @@
|
||||
"use strict";
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||||
// See the @microsoft/rush package's LICENSE file for license information.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
|
||||
//
|
||||
// This script is intended for usage in an automated build environment where the Rush command may not have
|
||||
// been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush
|
||||
// specified in the rush.json configuration file (if not already installed), and then pass a command-line to the
|
||||
// rushx command.
|
||||
//
|
||||
// An example usage would be:
|
||||
//
|
||||
// node common/scripts/install-run-rushx.js custom-command
|
||||
//
|
||||
// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
|
||||
require("./install-run-rush");
|
||||
//# sourceMappingURL=install-run-rushx.js.map
|
||||
@@ -1,433 +0,0 @@
|
||||
"use strict";
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||||
// See the @microsoft/rush package's LICENSE file for license information.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED.
|
||||
//
|
||||
// This script is intended for usage in an automated build environment where a Node tool may not have
|
||||
// been preinstalled, or may have an unpredictable version. This script will automatically install the specified
|
||||
// version of the specified tool (if not already installed), and then pass a command-line to it.
|
||||
// An example usage would be:
|
||||
//
|
||||
// node common/scripts/install-run.js qrcode@1.2.2 qrcode https://rushjs.io
|
||||
//
|
||||
// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/
|
||||
const childProcess = require("child_process");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
exports.RUSH_JSON_FILENAME = 'rush.json';
|
||||
const RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME = 'RUSH_TEMP_FOLDER';
|
||||
const INSTALLED_FLAG_FILENAME = 'installed.flag';
|
||||
const NODE_MODULES_FOLDER_NAME = 'node_modules';
|
||||
const PACKAGE_JSON_FILENAME = 'package.json';
|
||||
/**
|
||||
* Parse a package specifier (in the form of name\@version) into name and version parts.
|
||||
*/
|
||||
function _parsePackageSpecifier(rawPackageSpecifier) {
|
||||
rawPackageSpecifier = (rawPackageSpecifier || '').trim();
|
||||
const separatorIndex = rawPackageSpecifier.lastIndexOf('@');
|
||||
let name;
|
||||
let version = undefined;
|
||||
if (separatorIndex === 0) {
|
||||
// The specifier starts with a scope and doesn't have a version specified
|
||||
name = rawPackageSpecifier;
|
||||
}
|
||||
else if (separatorIndex === -1) {
|
||||
// The specifier doesn't have a version
|
||||
name = rawPackageSpecifier;
|
||||
}
|
||||
else {
|
||||
name = rawPackageSpecifier.substring(0, separatorIndex);
|
||||
version = rawPackageSpecifier.substring(separatorIndex + 1);
|
||||
}
|
||||
if (!name) {
|
||||
throw new Error(`Invalid package specifier: ${rawPackageSpecifier}`);
|
||||
}
|
||||
return { name, version };
|
||||
}
|
||||
/**
|
||||
* As a workaround, copyAndTrimNpmrcFile() copies the .npmrc file to the target folder, and also trims
|
||||
* unusable lines from the .npmrc file.
|
||||
*
|
||||
* Why are we trimming the .npmrc lines? NPM allows environment variables to be specified in
|
||||
* the .npmrc file to provide different authentication tokens for different registry.
|
||||
* However, if the environment variable is undefined, it expands to an empty string, which
|
||||
* produces a valid-looking mapping with an invalid URL that causes an error. Instead,
|
||||
* we'd prefer to skip that line and continue looking in other places such as the user's
|
||||
* home directory.
|
||||
*
|
||||
* IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._copyNpmrcFile()
|
||||
*/
|
||||
function _copyAndTrimNpmrcFile(sourceNpmrcPath, targetNpmrcPath) {
|
||||
console.log(`Copying ${sourceNpmrcPath} --> ${targetNpmrcPath}`); // Verbose
|
||||
let npmrcFileLines = fs.readFileSync(sourceNpmrcPath).toString().split('\n');
|
||||
npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim());
|
||||
const resultLines = [];
|
||||
// Trim out lines that reference environment variables that aren't defined
|
||||
for (const line of npmrcFileLines) {
|
||||
// This finds environment variable tokens that look like "${VAR_NAME}"
|
||||
const regex = /\$\{([^\}]+)\}/g;
|
||||
const environmentVariables = line.match(regex);
|
||||
let lineShouldBeTrimmed = false;
|
||||
if (environmentVariables) {
|
||||
for (const token of environmentVariables) {
|
||||
// Remove the leading "${" and the trailing "}" from the token
|
||||
const environmentVariableName = token.substring(2, token.length - 1);
|
||||
if (!process.env[environmentVariableName]) {
|
||||
lineShouldBeTrimmed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lineShouldBeTrimmed) {
|
||||
// Example output:
|
||||
// "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}"
|
||||
resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line);
|
||||
}
|
||||
else {
|
||||
resultLines.push(line);
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(targetNpmrcPath, resultLines.join(os.EOL));
|
||||
}
|
||||
/**
|
||||
* syncNpmrc() copies the .npmrc file to the target folder, and also trims unusable lines from the .npmrc file.
|
||||
* If the source .npmrc file not exist, then syncNpmrc() will delete an .npmrc that is found in the target folder.
|
||||
*
|
||||
* IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._syncNpmrc()
|
||||
*/
|
||||
function _syncNpmrc(sourceNpmrcFolder, targetNpmrcFolder, useNpmrcPublish) {
|
||||
const sourceNpmrcPath = path.join(sourceNpmrcFolder, !useNpmrcPublish ? '.npmrc' : '.npmrc-publish');
|
||||
const targetNpmrcPath = path.join(targetNpmrcFolder, '.npmrc');
|
||||
try {
|
||||
if (fs.existsSync(sourceNpmrcPath)) {
|
||||
_copyAndTrimNpmrcFile(sourceNpmrcPath, targetNpmrcPath);
|
||||
}
|
||||
else if (fs.existsSync(targetNpmrcPath)) {
|
||||
// If the source .npmrc doesn't exist and there is one in the target, delete the one in the target
|
||||
console.log(`Deleting ${targetNpmrcPath}`); // Verbose
|
||||
fs.unlinkSync(targetNpmrcPath);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Error syncing .npmrc file: ${e}`);
|
||||
}
|
||||
}
|
||||
let _npmPath = undefined;
|
||||
/**
|
||||
* Get the absolute path to the npm executable
|
||||
*/
|
||||
function getNpmPath() {
|
||||
if (!_npmPath) {
|
||||
try {
|
||||
if (os.platform() === 'win32') {
|
||||
// We're on Windows
|
||||
const whereOutput = childProcess.execSync('where npm', { stdio: [] }).toString();
|
||||
const lines = whereOutput.split(os.EOL).filter((line) => !!line);
|
||||
// take the last result, we are looking for a .cmd command
|
||||
// see https://github.com/microsoft/rushstack/issues/759
|
||||
_npmPath = lines[lines.length - 1];
|
||||
}
|
||||
else {
|
||||
// We aren't on Windows - assume we're on *NIX or Darwin
|
||||
_npmPath = childProcess.execSync('which npm', { stdio: [] }).toString();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unable to determine the path to the NPM tool: ${e}`);
|
||||
}
|
||||
_npmPath = _npmPath.trim();
|
||||
if (!fs.existsSync(_npmPath)) {
|
||||
throw new Error('The NPM executable does not exist');
|
||||
}
|
||||
}
|
||||
return _npmPath;
|
||||
}
|
||||
exports.getNpmPath = getNpmPath;
|
||||
function _ensureFolder(folderPath) {
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
const parentDir = path.dirname(folderPath);
|
||||
_ensureFolder(parentDir);
|
||||
fs.mkdirSync(folderPath);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create missing directories under the specified base directory, and return the resolved directory.
|
||||
*
|
||||
* Does not support "." or ".." path segments.
|
||||
* Assumes the baseFolder exists.
|
||||
*/
|
||||
function _ensureAndJoinPath(baseFolder, ...pathSegments) {
|
||||
let joinedPath = baseFolder;
|
||||
try {
|
||||
for (let pathSegment of pathSegments) {
|
||||
pathSegment = pathSegment.replace(/[\\\/]/g, '+');
|
||||
joinedPath = path.join(joinedPath, pathSegment);
|
||||
if (!fs.existsSync(joinedPath)) {
|
||||
fs.mkdirSync(joinedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Error building local installation folder (${path.join(baseFolder, ...pathSegments)}): ${e}`);
|
||||
}
|
||||
return joinedPath;
|
||||
}
|
||||
function _getRushTempFolder(rushCommonFolder) {
|
||||
const rushTempFolder = process.env[RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME];
|
||||
if (rushTempFolder !== undefined) {
|
||||
_ensureFolder(rushTempFolder);
|
||||
return rushTempFolder;
|
||||
}
|
||||
else {
|
||||
return _ensureAndJoinPath(rushCommonFolder, 'temp');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Resolve a package specifier to a static version
|
||||
*/
|
||||
function _resolvePackageVersion(rushCommonFolder, { name, version }) {
|
||||
if (!version) {
|
||||
version = '*'; // If no version is specified, use the latest version
|
||||
}
|
||||
if (version.match(/^[a-zA-Z0-9\-\+\.]+$/)) {
|
||||
// If the version contains only characters that we recognize to be used in static version specifiers,
|
||||
// pass the version through
|
||||
return version;
|
||||
}
|
||||
else {
|
||||
// version resolves to
|
||||
try {
|
||||
const rushTempFolder = _getRushTempFolder(rushCommonFolder);
|
||||
const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
|
||||
_syncNpmrc(sourceNpmrcFolder, rushTempFolder);
|
||||
const npmPath = getNpmPath();
|
||||
// This returns something that looks like:
|
||||
// @microsoft/rush@3.0.0 '3.0.0'
|
||||
// @microsoft/rush@3.0.1 '3.0.1'
|
||||
// ...
|
||||
// @microsoft/rush@3.0.20 '3.0.20'
|
||||
// <blank line>
|
||||
const npmVersionSpawnResult = childProcess.spawnSync(npmPath, ['view', `${name}@${version}`, 'version', '--no-update-notifier'], {
|
||||
cwd: rushTempFolder,
|
||||
stdio: []
|
||||
});
|
||||
if (npmVersionSpawnResult.status !== 0) {
|
||||
throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`);
|
||||
}
|
||||
const npmViewVersionOutput = npmVersionSpawnResult.stdout.toString();
|
||||
const versionLines = npmViewVersionOutput.split('\n').filter((line) => !!line);
|
||||
const latestVersion = versionLines[versionLines.length - 1];
|
||||
if (!latestVersion) {
|
||||
throw new Error('No versions found for the specified version range.');
|
||||
}
|
||||
const versionMatches = latestVersion.match(/^.+\s\'(.+)\'$/);
|
||||
if (!versionMatches) {
|
||||
throw new Error(`Invalid npm output ${latestVersion}`);
|
||||
}
|
||||
return versionMatches[1];
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unable to resolve version ${version} of package ${name}: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
let _rushJsonFolder;
|
||||
/**
|
||||
* Find the absolute path to the folder containing rush.json
|
||||
*/
|
||||
function findRushJsonFolder() {
|
||||
if (!_rushJsonFolder) {
|
||||
let basePath = __dirname;
|
||||
let tempPath = __dirname;
|
||||
do {
|
||||
const testRushJsonPath = path.join(basePath, exports.RUSH_JSON_FILENAME);
|
||||
if (fs.existsSync(testRushJsonPath)) {
|
||||
_rushJsonFolder = basePath;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
basePath = tempPath;
|
||||
}
|
||||
} while (basePath !== (tempPath = path.dirname(basePath))); // Exit the loop when we hit the disk root
|
||||
if (!_rushJsonFolder) {
|
||||
throw new Error('Unable to find rush.json.');
|
||||
}
|
||||
}
|
||||
return _rushJsonFolder;
|
||||
}
|
||||
exports.findRushJsonFolder = findRushJsonFolder;
|
||||
/**
|
||||
* Detects if the package in the specified directory is installed
|
||||
*/
|
||||
function _isPackageAlreadyInstalled(packageInstallFolder) {
|
||||
try {
|
||||
const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
|
||||
if (!fs.existsSync(flagFilePath)) {
|
||||
return false;
|
||||
}
|
||||
const fileContents = fs.readFileSync(flagFilePath).toString();
|
||||
return fileContents.trim() === process.version;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Removes the following files and directories under the specified folder path:
|
||||
* - installed.flag
|
||||
* -
|
||||
* - node_modules
|
||||
*/
|
||||
function _cleanInstallFolder(rushTempFolder, packageInstallFolder) {
|
||||
try {
|
||||
const flagFile = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME);
|
||||
if (fs.existsSync(flagFile)) {
|
||||
fs.unlinkSync(flagFile);
|
||||
}
|
||||
const packageLockFile = path.resolve(packageInstallFolder, 'package-lock.json');
|
||||
if (fs.existsSync(packageLockFile)) {
|
||||
fs.unlinkSync(packageLockFile);
|
||||
}
|
||||
const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
|
||||
if (fs.existsSync(nodeModulesFolder)) {
|
||||
const rushRecyclerFolder = _ensureAndJoinPath(rushTempFolder, 'rush-recycler', `install-run-${Date.now().toString()}`);
|
||||
fs.renameSync(nodeModulesFolder, rushRecyclerFolder);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Error cleaning the package install folder (${packageInstallFolder}): ${e}`);
|
||||
}
|
||||
}
|
||||
function _createPackageJson(packageInstallFolder, name, version) {
|
||||
try {
|
||||
const packageJsonContents = {
|
||||
'name': 'ci-rush',
|
||||
'version': '0.0.0',
|
||||
'dependencies': {
|
||||
[name]: version
|
||||
},
|
||||
'description': 'DON\'T WARN',
|
||||
'repository': 'DON\'T WARN',
|
||||
'license': 'MIT'
|
||||
};
|
||||
const packageJsonPath = path.join(packageInstallFolder, PACKAGE_JSON_FILENAME);
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2));
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unable to create package.json: ${e}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Run "npm install" in the package install folder.
|
||||
*/
|
||||
function _installPackage(packageInstallFolder, name, version) {
|
||||
try {
|
||||
console.log(`Installing ${name}...`);
|
||||
const npmPath = getNpmPath();
|
||||
const result = childProcess.spawnSync(npmPath, ['install'], {
|
||||
stdio: 'inherit',
|
||||
cwd: packageInstallFolder,
|
||||
env: process.env
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error('"npm install" encountered an error');
|
||||
}
|
||||
console.log(`Successfully installed ${name}@${version}`);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unable to install package: ${e}`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the ".bin" path for the package.
|
||||
*/
|
||||
function _getBinPath(packageInstallFolder, binName) {
|
||||
const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin');
|
||||
const resolvedBinName = (os.platform() === 'win32') ? `${binName}.cmd` : binName;
|
||||
return path.resolve(binFolderPath, resolvedBinName);
|
||||
}
|
||||
/**
|
||||
* Write a flag file to the package's install directory, signifying that the install was successful.
|
||||
*/
|
||||
function _writeFlagFile(packageInstallFolder) {
|
||||
try {
|
||||
const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME);
|
||||
fs.writeFileSync(flagFilePath, process.version);
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`);
|
||||
}
|
||||
}
|
||||
function installAndRun(packageName, packageVersion, packageBinName, packageBinArgs) {
|
||||
const rushJsonFolder = findRushJsonFolder();
|
||||
const rushCommonFolder = path.join(rushJsonFolder, 'common');
|
||||
const rushTempFolder = _getRushTempFolder(rushCommonFolder);
|
||||
const packageInstallFolder = _ensureAndJoinPath(rushTempFolder, 'install-run', `${packageName}@${packageVersion}`);
|
||||
if (!_isPackageAlreadyInstalled(packageInstallFolder)) {
|
||||
// The package isn't already installed
|
||||
_cleanInstallFolder(rushTempFolder, packageInstallFolder);
|
||||
const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
|
||||
_syncNpmrc(sourceNpmrcFolder, packageInstallFolder);
|
||||
_createPackageJson(packageInstallFolder, packageName, packageVersion);
|
||||
_installPackage(packageInstallFolder, packageName, packageVersion);
|
||||
_writeFlagFile(packageInstallFolder);
|
||||
}
|
||||
const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`;
|
||||
const statusMessageLine = new Array(statusMessage.length + 1).join('-');
|
||||
console.log(os.EOL + statusMessage + os.EOL + statusMessageLine + os.EOL);
|
||||
const binPath = _getBinPath(packageInstallFolder, packageBinName);
|
||||
const result = childProcess.spawnSync(binPath, packageBinArgs, {
|
||||
stdio: 'inherit',
|
||||
cwd: process.cwd(),
|
||||
env: process.env
|
||||
});
|
||||
if (result.status !== null) {
|
||||
return result.status;
|
||||
}
|
||||
else {
|
||||
throw result.error || new Error('An unknown error occurred.');
|
||||
}
|
||||
}
|
||||
exports.installAndRun = installAndRun;
|
||||
function runWithErrorAndStatusCode(fn) {
|
||||
process.exitCode = 1;
|
||||
try {
|
||||
const exitCode = fn();
|
||||
process.exitCode = exitCode;
|
||||
}
|
||||
catch (e) {
|
||||
console.error(os.EOL + os.EOL + e.toString() + os.EOL + os.EOL);
|
||||
}
|
||||
}
|
||||
exports.runWithErrorAndStatusCode = runWithErrorAndStatusCode;
|
||||
function _run() {
|
||||
const [nodePath, /* Ex: /bin/node */ scriptPath, /* /repo/common/scripts/install-run-rush.js */ rawPackageSpecifier, /* qrcode@^1.2.0 */ packageBinName, /* qrcode */ ...packageBinArgs /* [-f, myproject/lib] */] = process.argv;
|
||||
if (!nodePath) {
|
||||
throw new Error('Unexpected exception: could not detect node path');
|
||||
}
|
||||
if (path.basename(scriptPath).toLowerCase() !== 'install-run.js') {
|
||||
// If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control
|
||||
// to the script that (presumably) imported this file
|
||||
return;
|
||||
}
|
||||
if (process.argv.length < 4) {
|
||||
console.log('Usage: install-run.js <package>@<version> <command> [args...]');
|
||||
console.log('Example: install-run.js qrcode@1.2.2 qrcode https://rushjs.io');
|
||||
process.exit(1);
|
||||
}
|
||||
runWithErrorAndStatusCode(() => {
|
||||
const rushJsonFolder = findRushJsonFolder();
|
||||
const rushCommonFolder = _ensureAndJoinPath(rushJsonFolder, 'common');
|
||||
const packageSpecifier = _parsePackageSpecifier(rawPackageSpecifier);
|
||||
const name = packageSpecifier.name;
|
||||
const version = _resolvePackageVersion(rushCommonFolder, packageSpecifier);
|
||||
if (packageSpecifier.version !== version) {
|
||||
console.log(`Resolved to ${name}@${version}`);
|
||||
}
|
||||
return installAndRun(name, version, packageBinName, packageBinArgs);
|
||||
});
|
||||
}
|
||||
_run();
|
||||
//# sourceMappingURL=install-run.js.map
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"extends": "./common.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"extends": "./common.tsconfig.json"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "typescript-config",
|
||||
"description": "TypeScript configurations",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"publisher": "GitHub",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/github/vscode-codeql"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "",
|
||||
"format": ""
|
||||
},
|
||||
"devDependencies": {},
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: "module",
|
||||
project: ["tsconfig.json", "./src/**/tsconfig.json"],
|
||||
project: ["tsconfig.json", "./src/**/tsconfig.json", "./gulpfile.ts/tsconfig.json"],
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
env: {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## 1.3.3 - 16 September 2020
|
||||
|
||||
- Fix display of raw results entities with label but no url.
|
||||
- Fix bug where sort order is forgotten when changing raw results page.
|
||||
- Avoid showing a location link in results view when a result item has an empty location.
|
||||
|
||||
## 1.3.2 - 12 August 2020
|
||||
|
||||
- Fix error with choosing qlpack search path.
|
||||
- Fix pagination when there are no results.
|
||||
- Suppress database downloaded from URL message when action canceled.
|
||||
- Fix QL test discovery to avoid showing duplicate tests in the test explorer.
|
||||
- Enable pagination of query results
|
||||
- Add experimental AST Viewer for Go and C++. To enable, add `"codeQL.experimentalAstViewer": true` to the user settings file.
|
||||
|
||||
## 1.3.1 - 7 July 2020
|
||||
|
||||
- Fix unzipping of large files.
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
require('ts-node').register({});
|
||||
const gulp = require('gulp');
|
||||
const {
|
||||
compileTypeScript,
|
||||
watchTypeScript,
|
||||
packageExtension,
|
||||
compileTextMateGrammar,
|
||||
copyTestData,
|
||||
copyViewCss
|
||||
} = require('@github/codeql-gulp-tasks');
|
||||
const { compileView } = require('./webpack');
|
||||
|
||||
exports.buildWithoutPackage = gulp.parallel(compileTypeScript, compileTextMateGrammar, compileView, copyTestData, copyViewCss);
|
||||
exports.compileTextMateGrammar = compileTextMateGrammar;
|
||||
exports.default = gulp.series(exports.buildWithoutPackage, packageExtension);
|
||||
exports.watchTypeScript = watchTypeScript;
|
||||
exports.compileTypeScript = compileTypeScript;
|
||||
72
extensions/ql-vscode/gulpfile.ts/deploy.ts
Normal file
72
extensions/ql-vscode/gulpfile.ts/deploy.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as jsonc from 'jsonc-parser';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface DeployedPackage {
|
||||
distPath: string;
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const packageFiles = [
|
||||
'.vscodeignore',
|
||||
'CHANGELOG.md',
|
||||
'README.md',
|
||||
'language-configuration.json',
|
||||
'media',
|
||||
'node_modules',
|
||||
'out'
|
||||
];
|
||||
|
||||
async function copyPackage(sourcePath: string, destPath: string): Promise<void> {
|
||||
for (const file of packageFiles) {
|
||||
console.log(`copying ${path.resolve(sourcePath, file)} to ${path.resolve(destPath, file)}`);
|
||||
await fs.copy(path.resolve(sourcePath, file), path.resolve(destPath, file));
|
||||
}
|
||||
}
|
||||
|
||||
export async function deployPackage(packageJsonPath: string): Promise<DeployedPackage> {
|
||||
try {
|
||||
const packageJson: any = jsonc.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
||||
|
||||
// Default to development build; use flag --release to indicate release build.
|
||||
const isDevBuild = !process.argv.includes('--release');
|
||||
const distDir = path.join(__dirname, '../../../dist');
|
||||
await fs.mkdirs(distDir);
|
||||
|
||||
if (isDevBuild) {
|
||||
// NOTE: rootPackage.name had better not have any regex metacharacters
|
||||
const oldDevBuildPattern = new RegExp('^' + packageJson.name + '[^/]+-dev[0-9.]+\\.vsix$');
|
||||
// Dev package filenames are of the form
|
||||
// vscode-codeql-0.0.1-dev.2019.9.27.19.55.20.vsix
|
||||
(await fs.readdir(distDir)).filter(name => name.match(oldDevBuildPattern)).map(build => {
|
||||
console.log(`Deleting old dev build ${build}...`);
|
||||
fs.unlinkSync(path.join(distDir, build));
|
||||
});
|
||||
const now = new Date();
|
||||
packageJson.version = packageJson.version +
|
||||
`-dev.${now.getUTCFullYear()}.${now.getUTCMonth() + 1}.${now.getUTCDate()}` +
|
||||
`.${now.getUTCHours()}.${now.getUTCMinutes()}.${now.getUTCSeconds()}`;
|
||||
}
|
||||
|
||||
const distPath = path.join(distDir, packageJson.name);
|
||||
await fs.remove(distPath);
|
||||
await fs.mkdirs(distPath);
|
||||
|
||||
await fs.writeFile(path.join(distPath, 'package.json'), JSON.stringify(packageJson, null, 2));
|
||||
|
||||
const sourcePath = path.join(__dirname, '..');
|
||||
console.log(`Copying package '${packageJson.name}' and its dependencies to '${distPath}'...`);
|
||||
await copyPackage(sourcePath, distPath);
|
||||
|
||||
return {
|
||||
distPath: distPath,
|
||||
name: packageJson.name,
|
||||
version: packageJson.version
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
10
extensions/ql-vscode/gulpfile.ts/index.ts
Normal file
10
extensions/ql-vscode/gulpfile.ts/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import * as gulp from 'gulp';
|
||||
import { compileTypeScript, watchTypeScript, copyViewCss } from './typescript';
|
||||
import { compileTextMateGrammar } from './textmate';
|
||||
import { copyTestData } from './tests';
|
||||
import { compileView } from './webpack';
|
||||
import { packageExtension } from './package';
|
||||
|
||||
export const buildWithoutPackage = gulp.parallel(compileTypeScript, compileTextMateGrammar, compileView, copyTestData, copyViewCss);
|
||||
export { compileTextMateGrammar, watchTypeScript, compileTypeScript };
|
||||
exports.default = gulp.series(exports.buildWithoutPackage, packageExtension);
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from 'path';
|
||||
import { deployPackage } from './deploy';
|
||||
import * as child_process from 'child-process-promise';
|
||||
import * as childProcess from 'child-process-promise';
|
||||
|
||||
export async function packageExtension(): Promise<void> {
|
||||
const deployedPackage = await deployPackage(path.resolve('package.json'));
|
||||
@@ -9,7 +9,7 @@ export async function packageExtension(): Promise<void> {
|
||||
'package',
|
||||
'--out', path.resolve(deployedPackage.distPath, '..', `${deployedPackage.name}-${deployedPackage.version}.vsix`)
|
||||
];
|
||||
const proc = child_process.spawn('vsce', args, {
|
||||
const proc = childProcess.spawn('./node_modules/.bin/vsce', args, {
|
||||
cwd: deployedPackage.distPath
|
||||
});
|
||||
proc.childProcess.stdout!.on('data', (data) => {
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as gulp from 'gulp';
|
||||
import * as js_yaml from 'js-yaml';
|
||||
import * as jsYaml from 'js-yaml';
|
||||
import * as through from 'through2';
|
||||
import * as PluginError from 'plugin-error';
|
||||
import * as Vinyl from 'vinyl';
|
||||
@@ -13,9 +13,10 @@ import * as Vinyl from 'vinyl';
|
||||
*/
|
||||
function replaceReferencesWithStrings(value: string, replacements: Map<string, string>): string {
|
||||
let result = value;
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const original = result;
|
||||
for (const key of replacements.keys()) {
|
||||
for (const key of Array.from(replacements.keys())) {
|
||||
result = result.replace(`(?#${key})`, `(?:${replacements.get(key)})`);
|
||||
}
|
||||
if (result === original) {
|
||||
@@ -32,7 +33,7 @@ function replaceReferencesWithStrings(value: string, replacements: Map<string, s
|
||||
*/
|
||||
function gatherMacros(yaml: any): Map<string, string> {
|
||||
const macros = new Map<string, string>();
|
||||
for (var key in yaml.macros) {
|
||||
for (const key in yaml.macros) {
|
||||
macros.set(key, yaml.macros[key]);
|
||||
}
|
||||
|
||||
@@ -55,7 +56,7 @@ function getNodeMatchText(rule: any): string {
|
||||
else if (rule.patterns !== undefined) {
|
||||
const patterns: string[] = [];
|
||||
// For a list of patterns, use the disjunction of those patterns.
|
||||
for (var patternIndex in rule.patterns) {
|
||||
for (const patternIndex in rule.patterns) {
|
||||
const pattern = rule.patterns[patternIndex];
|
||||
if (pattern.include !== null) {
|
||||
patterns.push('(?' + pattern.include + ')');
|
||||
@@ -65,7 +66,7 @@ function getNodeMatchText(rule: any): string {
|
||||
return '(?:' + patterns.join('|') + ')';
|
||||
}
|
||||
else {
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +79,7 @@ function getNodeMatchText(rule: any): string {
|
||||
*/
|
||||
function gatherMatchTextForRules(yaml: any): Map<string, string> {
|
||||
const replacements = new Map<string, string>();
|
||||
for (var key in yaml.repository) {
|
||||
for (const key in yaml.repository) {
|
||||
const node = yaml.repository[key];
|
||||
replacements.set(key, getNodeMatchText(node));
|
||||
}
|
||||
@@ -106,7 +107,7 @@ function visitAllRulesInFile(yaml: any, action: (rule: any) => void) {
|
||||
* @param action Callback to invoke on each rule.
|
||||
*/
|
||||
function visitAllRulesInRuleMap(ruleMap: any, action: (rule: any) => void) {
|
||||
for (var key in ruleMap) {
|
||||
for (const key in ruleMap) {
|
||||
const rule = ruleMap[key];
|
||||
if ((typeof rule) === 'object') {
|
||||
action(rule);
|
||||
@@ -124,7 +125,7 @@ function visitAllRulesInRuleMap(ruleMap: any, action: (rule: any) => void) {
|
||||
* @param action The transformation to make on each match pattern.
|
||||
*/
|
||||
function visitAllMatchesInRule(rule: any, action: (match: any) => any) {
|
||||
for (var key in rule) {
|
||||
for (const key in rule) {
|
||||
switch (key) {
|
||||
case 'begin':
|
||||
case 'end':
|
||||
@@ -184,10 +185,10 @@ function transformFile(yaml: any) {
|
||||
visitAllRulesInFile(yaml, (rule) => {
|
||||
visitAllMatchesInRule(rule, (match) => {
|
||||
if ((typeof match) === 'object') {
|
||||
for (var key in match) {
|
||||
for (const key in match) {
|
||||
return macros.get(key)!.replace('(?#)', `(?:${match[key]})`);
|
||||
}
|
||||
throw new Error("No key in macro map.")
|
||||
throw new Error('No key in macro map.');
|
||||
}
|
||||
else {
|
||||
return match;
|
||||
@@ -225,7 +226,7 @@ export function transpileTextMateGrammar() {
|
||||
else if (file.isBuffer()) {
|
||||
const buf: Buffer = file.contents;
|
||||
const yamlText: string = buf.toString('utf8');
|
||||
const jsonData: any = js_yaml.safeLoad(yamlText);
|
||||
const jsonData: any = jsYaml.safeLoad(yamlText);
|
||||
transformFile(jsonData);
|
||||
|
||||
file.contents = Buffer.from(JSON.stringify(jsonData, null, 2), 'utf8');
|
||||
@@ -1,15 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"lib": ["es6"],
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"rootDir": "../../src",
|
||||
"rootDir": ".",
|
||||
"strictNullChecks": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"preserveWatchOutput": true,
|
||||
@@ -19,12 +18,5 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"include": [
|
||||
"../../src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../../node_modules",
|
||||
"../../test",
|
||||
"../../**/view"
|
||||
]
|
||||
"include": ["*.ts"]
|
||||
}
|
||||
42
extensions/ql-vscode/gulpfile.ts/typescript.ts
Normal file
42
extensions/ql-vscode/gulpfile.ts/typescript.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as colors from 'ansi-colors';
|
||||
import * as gulp from 'gulp';
|
||||
import * as sourcemaps from 'gulp-sourcemaps';
|
||||
import * as ts from 'gulp-typescript';
|
||||
|
||||
function goodReporter(): ts.reporter.Reporter {
|
||||
return {
|
||||
error: (error, typescript) => {
|
||||
if (error.tsFile) {
|
||||
console.log('[' + colors.gray('gulp-typescript') + '] ' + colors.red(error.fullFilename
|
||||
+ '(' + (error.startPosition!.line + 1) + ',' + error.startPosition!.character + '): ')
|
||||
+ 'error TS' + error.diagnostic.code + ': ' + typescript.flattenDiagnosticMessageText(error.diagnostic.messageText, '\n'));
|
||||
}
|
||||
else {
|
||||
console.log(error.message);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const tsProject = ts.createProject('tsconfig.json');
|
||||
|
||||
export function compileTypeScript() {
|
||||
return tsProject.src()
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsProject(goodReporter()))
|
||||
.pipe(sourcemaps.write('.', {
|
||||
includeContent: false,
|
||||
sourceRoot: '.',
|
||||
}))
|
||||
.pipe(gulp.dest('out'));
|
||||
}
|
||||
|
||||
export function watchTypeScript() {
|
||||
gulp.watch('src/**/*.ts', compileTypeScript);
|
||||
}
|
||||
|
||||
/** Copy CSS files for the results view into the output directory. */
|
||||
export function copyViewCss() {
|
||||
return gulp.src('src/view/*.css')
|
||||
.pipe(gulp.dest('out'));
|
||||
}
|
||||
@@ -9,9 +9,9 @@ export const config: webpack.Configuration = {
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '..', 'out'),
|
||||
filename: "[name].js"
|
||||
filename: '[name].js'
|
||||
},
|
||||
devtool: "inline-source-map",
|
||||
devtool: 'inline-source-map',
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.tsx', '.json']
|
||||
},
|
||||
@@ -20,6 +20,9 @@ export const config: webpack.Configuration = {
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFile: 'src/view/tsconfig.json',
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
7
extensions/ql-vscode/media/dark/clear-all.svg
Normal file
7
extensions/ql-vscode/media/dark/clear-all.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 12.6L10.7 13.3L12.3 11.7L13.9 13.3L14.7 12.6L13 11L14.7 9.40005L13.9 8.60005L12.3 10.3L10.7 8.60005L10 9.40005L11.6 11L10 12.6Z" fill="#C5C5C5"/>
|
||||
<path d="M1 4L15 4L15 3L1 3L1 4Z" fill="#C5C5C5"/>
|
||||
<path d="M1 7L15 7L15 6L1 6L1 7Z" fill="#C5C5C5"/>
|
||||
<path d="M9 9.5L9 9L1 9L1 10L9 10L9 9.5Z" fill="#C5C5C5"/>
|
||||
<path d="M9 13L9 12.5L9 12L1 12L1 13L9 13Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 483 B |
7
extensions/ql-vscode/media/light/clear-all.svg
Normal file
7
extensions/ql-vscode/media/light/clear-all.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0001 12.6L10.7001 13.3L12.3001 11.7L13.9001 13.3L14.7001 12.6L13.0001 11L14.7001 9.40005L13.9001 8.60005L12.3001 10.3L10.7001 8.60005L10.0001 9.40005L11.6001 11L10.0001 12.6Z" fill="#424242"/>
|
||||
<path d="M1.00006 4L15.0001 4L15.0001 3L1.00006 3L1.00006 4Z" fill="#424242"/>
|
||||
<path d="M1.00006 7L15.0001 7L15.0001 6L1.00006 6L1.00006 7Z" fill="#424242"/>
|
||||
<path d="M9.00006 9.5L9.00006 9L1.00006 9L1.00006 10L9.00006 10L9.00006 9.5Z" fill="#424242"/>
|
||||
<path d="M9.00006 13L9.00006 12.5L9.00006 12L1.00006 12L1.00006 13L9.00006 13Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 658 B |
10031
extensions/ql-vscode/package-lock.json
generated
Normal file
10031
extensions/ql-vscode/package-lock.json
generated
Normal file
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.3.1",
|
||||
"version": "1.3.3",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -13,7 +13,7 @@
|
||||
"url": "https://github.com/github/vscode-codeql"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.39.0"
|
||||
"vscode": "^1.43.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
@@ -25,6 +25,7 @@
|
||||
"onLanguage:ql",
|
||||
"onView:codeQLDatabases",
|
||||
"onView:codeQLQueryHistory",
|
||||
"onView:codeQLAstViewer",
|
||||
"onView:test-explorer",
|
||||
"onCommand:codeQL.checkForUpdatesToCLI",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseFolder",
|
||||
@@ -32,6 +33,7 @@
|
||||
"onCommand:codeQLDatabases.chooseDatabaseInternet",
|
||||
"onCommand:codeQLDatabases.chooseDatabaseLgtm",
|
||||
"onCommand:codeQL.setCurrentDatabase",
|
||||
"onCommand:codeQL.viewAst",
|
||||
"onCommand:codeQL.chooseDatabaseFolder",
|
||||
"onCommand:codeQL.chooseDatabaseArchive",
|
||||
"onCommand:codeQL.chooseDatabaseInternet",
|
||||
@@ -218,6 +220,10 @@
|
||||
"command": "codeQL.setCurrentDatabase",
|
||||
"title": "CodeQL: Set Current Database"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAst",
|
||||
"title": "CodeQL: View AST"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.upgradeCurrentDatabase",
|
||||
"title": "CodeQL: Upgrade Current Database"
|
||||
@@ -333,6 +339,18 @@
|
||||
{
|
||||
"command": "codeQLTests.acceptOutput",
|
||||
"title": "CodeQL: Accept Test Output"
|
||||
},
|
||||
{
|
||||
"command": "codeQLAstViewer.gotoCode",
|
||||
"title": "Go To Code"
|
||||
},
|
||||
{
|
||||
"command": "codeQLAstViewer.clear",
|
||||
"title": "Clear AST",
|
||||
"icon": {
|
||||
"light": "media/light/clear-all.svg",
|
||||
"dark": "media/dark/clear-all.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
@@ -366,6 +384,11 @@
|
||||
"command": "codeQLDatabases.chooseDatabaseLgtm",
|
||||
"when": "view == codeQLDatabases",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "codeQLAstViewer.clear",
|
||||
"when": "view == codeQLAstViewer && config.codeQL.experimentalAstViewer == true",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
@@ -446,6 +469,11 @@
|
||||
"group": "9_qlCommands",
|
||||
"when": "resourceScheme == codeql-zip-archive || explorerResourceIsFolder || resourceExtname == .zip"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAst",
|
||||
"group": "9_qlCommands",
|
||||
"when": "resourceScheme == codeql-zip-archive && config.codeQL.experimentalAstViewer == true"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueries",
|
||||
"group": "9_qlCommands"
|
||||
@@ -468,6 +496,10 @@
|
||||
"command": "codeQL.setCurrentDatabase",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.viewAst",
|
||||
"when": "resourceScheme == codeql-zip-archive"
|
||||
},
|
||||
{
|
||||
"command": "codeQLDatabases.setCurrentDatabase",
|
||||
"when": "false"
|
||||
@@ -543,6 +575,14 @@
|
||||
{
|
||||
"command": "codeQLQueryHistory.compareWith",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLAstViewer.gotoCode",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQLAstViewer.clear",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
@@ -574,9 +614,25 @@
|
||||
{
|
||||
"id": "codeQLQueryHistory",
|
||||
"name": "Query History"
|
||||
},
|
||||
{
|
||||
"id": "codeQLAstViewer",
|
||||
"name": "AST Viewer",
|
||||
"when": "config.codeQL.experimentalAstViewer == true"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"viewsWelcome": [
|
||||
{
|
||||
"view": "codeQLAstViewer",
|
||||
"contents": "Run the 'CodeQL: View AST' command on an open source file from a Code QL database.\n[View AST](command:codeQL.viewAst)",
|
||||
"when": "config.codeQL.experimentalAstViewer == true"
|
||||
},
|
||||
{
|
||||
"view": "codeQLQueryHistory",
|
||||
"contents": "Run the 'CodeQL: Run Query' command on a QL query.\n[Run Query](command:codeQL.runQuery)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
@@ -586,7 +642,6 @@
|
||||
"preintegration": "rm -rf ./out/vscode-tests && gulp",
|
||||
"integration": "node ./out/vscode-tests/run-integration-tests.js",
|
||||
"update-vscode": "node ./node_modules/vscode/bin/install",
|
||||
"postinstall": "npm rebuild && node ./node_modules/vscode/bin/install",
|
||||
"format": "tsfmt -r && eslint src test --ext .ts,.tsx --fix",
|
||||
"lint": "eslint src test --ext .ts,.tsx --max-warnings=0",
|
||||
"format-staged": "lint-staged"
|
||||
@@ -596,84 +651,83 @@
|
||||
"classnames": "~2.2.6",
|
||||
"fs-extra": "^8.1.0",
|
||||
"glob-promise": "^3.4.0",
|
||||
"js-yaml": "^3.12.0",
|
||||
"js-yaml": "^3.14.0",
|
||||
"minimist": "~1.2.5",
|
||||
"node-fetch": "~2.6.0",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"semmle-bqrs": "^0.0.1",
|
||||
"semmle-io-node": "^0.0.1",
|
||||
"@github/codeql-vscode-utils": "^0.0.4",
|
||||
"semver": "~7.3.2",
|
||||
"tmp": "^0.1.0",
|
||||
"tmp-promise": "~3.0.2",
|
||||
"tree-kill": "~1.2.2",
|
||||
"unzipper": "~0.10.5",
|
||||
"vscode-jsonrpc": "^5.0.1",
|
||||
"vscode-languageclient": "^6.1.3",
|
||||
"vscode-test-adapter-api": "~1.7.0",
|
||||
"vscode-test-adapter-util": "~0.7.0",
|
||||
"minimist": "~1.2.5",
|
||||
"semver": "~7.3.2",
|
||||
"@types/semver": "~7.2.0",
|
||||
"tmp-promise": "~3.0.2",
|
||||
"zip-a-folder": "~0.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/semver": "~7.2.0",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chai-as-promised": "~7.1.2",
|
||||
"@types/child-process-promise": "^2.2.1",
|
||||
"@types/classnames": "~2.2.9",
|
||||
"@types/fs-extra": "^8.0.0",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/google-protobuf": "^3.2.7",
|
||||
"@types/gulp": "^4.0.6",
|
||||
"@types/js-yaml": "~3.12.1",
|
||||
"@types/gulp-sourcemaps": "0.0.32",
|
||||
"@types/js-yaml": "~3.12.2",
|
||||
"@types/jszip": "~3.1.6",
|
||||
"@types/mocha": "~5.2.7",
|
||||
"@types/node": "^12.0.8",
|
||||
"@types/node-fetch": "~2.5.2",
|
||||
"@types/proxyquire": "~1.3.28",
|
||||
"@types/react": "^16.8.17",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/sarif": "~2.1.2",
|
||||
"@types/sinon": "~7.5.2",
|
||||
"@types/sinon-chai": "~3.2.3",
|
||||
"@types/through2": "^2.0.36",
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/unzipper": "~0.10.1",
|
||||
"@types/vscode": "^1.39.0",
|
||||
"@types/vscode": "^1.43.0",
|
||||
"@types/webpack": "^4.32.1",
|
||||
"@types/xml2js": "~0.4.4",
|
||||
"@github/codeql-gulp-tasks": "^0.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "~2.23.0",
|
||||
"@typescript-eslint/parser": "~2.23.0",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "~7.1.1",
|
||||
"css-loader": "~3.1.0",
|
||||
"eslint": "~6.8.0",
|
||||
"eslint-plugin-react": "~7.19.0",
|
||||
"glob": "^7.1.4",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"husky": "~4.2.5",
|
||||
"jsonc-parser": "^2.3.0",
|
||||
"lint-staged": "~10.2.2",
|
||||
"mocha": "~6.2.1",
|
||||
"mocha-sinon": "~2.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "~2.0.5",
|
||||
"proxyquire": "~2.1.3",
|
||||
"sinon": "~9.0.0",
|
||||
"sinon-chai": "~3.5.0",
|
||||
"style-loader": "~0.23.1",
|
||||
"through2": "^3.0.1",
|
||||
"ts-loader": "^5.4.5",
|
||||
"ts-node": "^8.3.0",
|
||||
"ts-protoc-gen": "^0.9.0",
|
||||
"typescript": "^3.7.2",
|
||||
"typescript-config": "^0.0.1",
|
||||
"typescript": "~3.8.3",
|
||||
"typescript-formatter": "^7.2.2",
|
||||
"vsce": "^1.65.0",
|
||||
"vscode-test": "^1.4.0",
|
||||
"webpack": "^4.38.0",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"eslint": "~6.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "~2.23.0",
|
||||
"@typescript-eslint/parser": "~2.23.0",
|
||||
"chai-as-promised": "~7.1.1",
|
||||
"@types/chai-as-promised": "~7.1.2",
|
||||
"@types/sinon": "~7.5.2",
|
||||
"sinon-chai": "~3.5.0",
|
||||
"@types/sinon-chai": "~3.2.3",
|
||||
"proxyquire": "~2.1.3",
|
||||
"@types/proxyquire": "~1.3.28",
|
||||
"eslint-plugin-react": "~7.19.0",
|
||||
"husky": "~4.2.5",
|
||||
"lint-staged": "~10.2.2",
|
||||
"prettier": "~2.0.5"
|
||||
"webpack-cli": "^3.3.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DecodedBqrsChunk, ResultSetSchema, ColumnKind, Column, ColumnValue } from './bqrs-cli-types';
|
||||
import { LocationValue, ResultSetSchema as AdaptedSchema, ColumnSchema, ColumnType, LocationStyle } from 'semmle-bqrs';
|
||||
import { LocationValue, ResultSetSchema as AdaptedSchema, ColumnSchema, ColumnType, LocationStyle } from './bqrs-types';
|
||||
import { ResultSet } from './interface-types';
|
||||
|
||||
// FIXME: This is a temporary bit of impedance matching to convert
|
||||
@@ -73,7 +73,7 @@ export function adaptValue(val: ColumnValue): ResultValue {
|
||||
}
|
||||
|
||||
if (url === undefined) {
|
||||
return 'none';
|
||||
return val.label || '';
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -103,30 +103,7 @@ export function adaptBqrs(schema: AdaptedSchema, page: DecodedBqrsChunk): RawRes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This type has two branches; we are in the process of changing from
|
||||
* one to the other. The old way is to parse them inside the webview,
|
||||
* the new way is to parse them in the extension. The main motivation
|
||||
* for this transition is to make pagination possible in such a way
|
||||
* that only one page needs to be sent from the extension to the webview.
|
||||
*/
|
||||
export type ParsedResultSets = ExtensionParsedResultSets | WebviewParsedResultSets;
|
||||
|
||||
/**
|
||||
* The old method doesn't require any nontrivial information to be included here,
|
||||
* just a tag to indicate that it is being used.
|
||||
*/
|
||||
export interface WebviewParsedResultSets {
|
||||
t: 'WebviewParsed';
|
||||
selectedTable?: string; // when undefined, means 'show default table'
|
||||
}
|
||||
|
||||
/**
|
||||
* The new method includes which bqrs page is being sent, and the
|
||||
* actual results parsed on the extension side.
|
||||
*/
|
||||
export interface ExtensionParsedResultSets {
|
||||
t: 'ExtensionParsed';
|
||||
export interface ParsedResultSets {
|
||||
pageNumber: number;
|
||||
numPages: number;
|
||||
numInterpretedPages: number;
|
||||
|
||||
102
extensions/ql-vscode/src/astViewer.ts
Normal file
102
extensions/ql-vscode/src/astViewer.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { DatabaseItem } from './databases';
|
||||
import { UrlValue, BqrsId } from './bqrs-cli-types';
|
||||
import fileRangeFromURI from './contextual/fileRangeFromURI';
|
||||
import { showLocation } from './interface-utils';
|
||||
|
||||
export interface AstItem {
|
||||
id: BqrsId;
|
||||
label?: string;
|
||||
location?: UrlValue;
|
||||
parent: AstItem | RootAstItem;
|
||||
children: AstItem[];
|
||||
order: number;
|
||||
}
|
||||
|
||||
export type RootAstItem = Omit<AstItem, 'parent'>;
|
||||
|
||||
class AstViewerDataProvider implements vscode.TreeDataProvider<AstItem | RootAstItem> {
|
||||
|
||||
public roots: RootAstItem[] = [];
|
||||
public db: DatabaseItem | undefined;
|
||||
|
||||
private _onDidChangeTreeData =
|
||||
new vscode.EventEmitter<AstItem | undefined>();
|
||||
readonly onDidChangeTreeData: vscode.Event<AstItem | undefined> =
|
||||
this._onDidChangeTreeData.event;
|
||||
|
||||
constructor() {
|
||||
vscode.commands.registerCommand('codeQLAstViewer.gotoCode',
|
||||
async (location: UrlValue, db: DatabaseItem) => {
|
||||
if (location) {
|
||||
await showLocation(fileRangeFromURI(location, db));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
getChildren(item?: AstItem): vscode.ProviderResult<(AstItem | RootAstItem)[]> {
|
||||
const children = item ? item.children : this.roots;
|
||||
return children.sort((c1, c2) => (c1.order - c2.order));
|
||||
}
|
||||
|
||||
getParent(item: AstItem): vscode.ProviderResult<AstItem> {
|
||||
return item.parent as AstItem;
|
||||
}
|
||||
|
||||
getTreeItem(item: AstItem): vscode.TreeItem {
|
||||
const line = typeof item.location === 'string'
|
||||
? item.location
|
||||
: item.location?.startLine;
|
||||
|
||||
const state = item.children.length
|
||||
? vscode.TreeItemCollapsibleState.Collapsed
|
||||
: vscode.TreeItemCollapsibleState.None;
|
||||
const treeItem = new vscode.TreeItem(item.label || '', state);
|
||||
treeItem.description = line ? `Line ${line}` : '';
|
||||
treeItem.id = String(item.id);
|
||||
treeItem.tooltip = `${treeItem.description} ${treeItem.label}`;
|
||||
treeItem.command = {
|
||||
command: 'codeQLAstViewer.gotoCode',
|
||||
title: 'Go To Code',
|
||||
tooltip: `Go To ${item.location}`,
|
||||
arguments: [item.location, this.db]
|
||||
};
|
||||
return treeItem;
|
||||
}
|
||||
}
|
||||
|
||||
export class AstViewer {
|
||||
private treeView: vscode.TreeView<AstItem | RootAstItem>;
|
||||
private treeDataProvider: AstViewerDataProvider;
|
||||
|
||||
constructor() {
|
||||
this.treeDataProvider = new AstViewerDataProvider();
|
||||
this.treeView = vscode.window.createTreeView('codeQLAstViewer', {
|
||||
treeDataProvider: this.treeDataProvider,
|
||||
showCollapseAll: true
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('codeQLAstViewer.clear', () => {
|
||||
this.clear();
|
||||
});
|
||||
}
|
||||
|
||||
updateRoots(roots: RootAstItem[], db: DatabaseItem, fileName: string) {
|
||||
this.treeDataProvider.roots = roots;
|
||||
this.treeDataProvider.db = db;
|
||||
this.treeDataProvider.refresh();
|
||||
this.treeView.message = `AST for ${fileName}`;
|
||||
this.treeView.reveal(roots[0], { focus: true });
|
||||
}
|
||||
|
||||
private clear() {
|
||||
this.treeDataProvider.roots = [];
|
||||
this.treeDataProvider.db = undefined;
|
||||
this.treeDataProvider.refresh();
|
||||
this.treeView.message = undefined;
|
||||
}
|
||||
}
|
||||
@@ -53,9 +53,12 @@ export interface BQRSInfo {
|
||||
'result-sets': ResultSetSchema[];
|
||||
}
|
||||
|
||||
export type BqrsId = number;
|
||||
|
||||
export interface EntityValue {
|
||||
url?: UrlValue;
|
||||
label?: string;
|
||||
id?: BqrsId;
|
||||
}
|
||||
|
||||
export interface LineColumnLocation {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* TODO: Types in this file are deprecated, and uses of them should be
|
||||
* migrated to the analogous types in bqrs-cli-types.
|
||||
*/
|
||||
|
||||
export enum LocationStyle {
|
||||
None = 0,
|
||||
String,
|
||||
@@ -60,7 +65,38 @@ export interface ResultSetSchema {
|
||||
* The schema describing the contents of a BQRS file.
|
||||
*/
|
||||
export interface ResultSetsSchema {
|
||||
readonly version: number,
|
||||
readonly stringPoolSize: number,
|
||||
readonly resultSets: readonly ResultSetSchema[]
|
||||
readonly version: number;
|
||||
readonly stringPoolSize: number;
|
||||
readonly resultSets: readonly ResultSetSchema[];
|
||||
}
|
||||
|
||||
// See https://help.semmle.com/QL/learn-ql/ql/locations.html for how these are used.
|
||||
export interface FivePartLocation {
|
||||
t: LocationStyle.FivePart;
|
||||
file: string;
|
||||
lineStart: number;
|
||||
colStart: number;
|
||||
lineEnd: number;
|
||||
colEnd: number;
|
||||
}
|
||||
|
||||
export interface StringLocation {
|
||||
t: LocationStyle.String;
|
||||
loc: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A location representing an entire filesystem resource.
|
||||
* This is usually derived from a `StringLocation` with the entire filesystem URL.
|
||||
*/
|
||||
export interface WholeFileLocation {
|
||||
t: LocationStyle.WholeFile;
|
||||
file: string;
|
||||
}
|
||||
|
||||
export type RawLocationValue = FivePartLocation | StringLocation;
|
||||
|
||||
export type LocationValue = RawLocationValue | WholeFileLocation;
|
||||
|
||||
/** A location that may be resolved to a source code element. */
|
||||
export type ResolvableLocationValue = FivePartLocation | WholeFileLocation;
|
||||
@@ -1,35 +1,4 @@
|
||||
import { LocationStyle } from "./bqrs-schema";
|
||||
|
||||
// See https://help.semmle.com/QL/learn-ql/ql/locations.html for how these are used.
|
||||
export interface FivePartLocation {
|
||||
t: LocationStyle.FivePart;
|
||||
file: string;
|
||||
lineStart: number;
|
||||
colStart: number;
|
||||
lineEnd: number;
|
||||
colEnd: number;
|
||||
}
|
||||
|
||||
export interface StringLocation {
|
||||
t: LocationStyle.String;
|
||||
loc: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A location representing an entire filesystem resource.
|
||||
* This is usually derived from a `StringLocation` with the entire filesystem URL.
|
||||
*/
|
||||
export interface WholeFileLocation {
|
||||
t: LocationStyle.WholeFile;
|
||||
file: string;
|
||||
}
|
||||
|
||||
export type RawLocationValue = FivePartLocation | StringLocation;
|
||||
|
||||
export type LocationValue = RawLocationValue | WholeFileLocation;
|
||||
|
||||
/** A location that may be resolved to a source code element. */
|
||||
export type ResolvableLocationValue = FivePartLocation | WholeFileLocation;
|
||||
import { StringLocation, LocationValue, LocationStyle, ResolvableLocationValue } from './bqrs-types';
|
||||
|
||||
/**
|
||||
* The CodeQL filesystem libraries use this pattern in `getURL()` predicates
|
||||
@@ -47,17 +16,24 @@ const FILE_LOCATION_REGEX = /file:\/\/(.+):([0-9]+):([0-9]+):([0-9]+):([0-9]+)/;
|
||||
export function tryGetResolvableLocation(
|
||||
loc: LocationValue | undefined
|
||||
): ResolvableLocationValue | undefined {
|
||||
let resolvedLoc;
|
||||
if (loc === undefined) {
|
||||
return undefined;
|
||||
resolvedLoc = undefined;
|
||||
} else if (loc.t === LocationStyle.FivePart && loc.file) {
|
||||
return loc;
|
||||
resolvedLoc = loc;
|
||||
} else if (loc.t === LocationStyle.WholeFile && loc.file) {
|
||||
return loc;
|
||||
resolvedLoc = loc;
|
||||
} else if (loc.t === LocationStyle.String && loc.loc) {
|
||||
return tryGetLocationFromString(loc);
|
||||
resolvedLoc = tryGetLocationFromString(loc);
|
||||
} else {
|
||||
return undefined;
|
||||
resolvedLoc = undefined;
|
||||
}
|
||||
|
||||
if (resolvedLoc && isEmptyPath(resolvedLoc.file)) {
|
||||
resolvedLoc = undefined;
|
||||
}
|
||||
|
||||
return resolvedLoc;
|
||||
}
|
||||
|
||||
export function tryGetLocationFromString(
|
||||
@@ -78,7 +54,7 @@ export function tryGetLocationFromString(
|
||||
colStart: Number(matches[3]),
|
||||
lineEnd: Number(matches[4]),
|
||||
colEnd: Number(matches[5]),
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
@@ -87,28 +63,20 @@ export function tryGetLocationFromString(
|
||||
|
||||
function isWholeFileMatch(matches: RegExpExecArray): boolean {
|
||||
return (
|
||||
matches[2] === "0" &&
|
||||
matches[3] === "0" &&
|
||||
matches[4] === "0" &&
|
||||
matches[5] === "0"
|
||||
matches[2] === '0' &&
|
||||
matches[3] === '0' &&
|
||||
matches[4] === '0' &&
|
||||
matches[5] === '0'
|
||||
);
|
||||
}
|
||||
|
||||
export interface ElementBase {
|
||||
id: PrimitiveColumnValue;
|
||||
label?: string;
|
||||
location?: LocationValue;
|
||||
/**
|
||||
* Checks whether the file path is empty. For now, just check whether
|
||||
* the file path is empty. If so, we do not want to render this location
|
||||
* as a link.
|
||||
*
|
||||
* @param path A file path
|
||||
*/
|
||||
function isEmptyPath(path: string) {
|
||||
return !path || path === '/';
|
||||
}
|
||||
|
||||
export interface ElementWithLabel extends ElementBase {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface ElementWithLocation extends ElementBase {
|
||||
location: LocationValue;
|
||||
}
|
||||
|
||||
export interface Element extends Required<ElementBase> {}
|
||||
|
||||
export type PrimitiveColumnValue = string | boolean | number | Date;
|
||||
export type ColumnValue = PrimitiveColumnValue | ElementBase;
|
||||
@@ -93,6 +93,18 @@ export interface TestCompleted {
|
||||
expected: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional arguments for the `bqrsDecode` function
|
||||
*/
|
||||
interface BqrsDecodeOptions {
|
||||
/** How many results to get. */
|
||||
pageSize?: number;
|
||||
/** The 0-based index of the first result to get. */
|
||||
offset?: number;
|
||||
/** The entity names to retrieve from the bqrs file. Default is url, string */
|
||||
entities?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This class manages a cli server started by `codeql execute cli-server` to
|
||||
* run commands without the overhead of starting a new java
|
||||
@@ -494,12 +506,16 @@ export class CodeQLCliServer implements Disposable {
|
||||
* Gets the results from a bqrs.
|
||||
* @param bqrsPath The path to the bqrs.
|
||||
* @param resultSet The result set to get.
|
||||
* @param pageSize How many results to get.
|
||||
* @param offset The 0-based index of the first result to get.
|
||||
* @param options Optional BqrsDecodeOptions arguments
|
||||
*/
|
||||
async bqrsDecode(bqrsPath: string, resultSet: string, pageSize?: number, offset?: number): Promise<DecodedBqrsChunk> {
|
||||
async bqrsDecode(
|
||||
bqrsPath: string,
|
||||
resultSet: string,
|
||||
{ pageSize, offset, entities = ['url', 'string'] }: BqrsDecodeOptions = {}
|
||||
): Promise<DecodedBqrsChunk> {
|
||||
|
||||
const subcommandArgs = [
|
||||
'--entities=url,string',
|
||||
`--entities=${entities.join(',')}`,
|
||||
'--result-set', resultSet,
|
||||
].concat(
|
||||
pageSize ? ['--rows', pageSize.toString()] : []
|
||||
@@ -604,7 +620,7 @@ export class CodeQLCliServer implements Disposable {
|
||||
*/
|
||||
resolveQlpacks(additionalPacks: string[], searchPath?: string[]): Promise<QlpacksInfo> {
|
||||
const args = ['--additional-packs', additionalPacks.join(path.delimiter)];
|
||||
if (searchPath !== undefined) {
|
||||
if (searchPath?.length) {
|
||||
args.push('--search-path', path.join(...searchPath));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from '../vscode-utils/disposable-object';
|
||||
import {
|
||||
WebviewPanel,
|
||||
ExtensionContext,
|
||||
|
||||
@@ -31,10 +31,14 @@ export function Compare(_: {}): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', (evt: MessageEvent) => {
|
||||
const msg: ToCompareViewMessage = evt.data;
|
||||
switch (msg.t) {
|
||||
case 'setComparisons':
|
||||
setComparison(msg);
|
||||
if (evt.origin === window.origin) {
|
||||
const msg: ToCompareViewMessage = evt.data;
|
||||
switch (msg.t) {
|
||||
case 'setComparisons':
|
||||
setComparison(msg);
|
||||
}
|
||||
} else {
|
||||
console.error(`Invalid event origin ${evt.origin}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -60,8 +64,8 @@ export function Compare(_: {}): JSX.Element {
|
||||
{hasRows ? (
|
||||
<CompareTable comparison={comparison}></CompareTable>
|
||||
) : (
|
||||
<div className="vscode-codeql__compare-message">{message}</div>
|
||||
)}
|
||||
<div className="vscode-codeql__compare-message">{message}</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} catch (err) {
|
||||
|
||||
@@ -10,15 +10,14 @@
|
||||
],
|
||||
"jsx": "react",
|
||||
"sourceMap": true,
|
||||
"rootDir": "../..",
|
||||
"rootDir": "..",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"experimentalDecorators": true,
|
||||
"typeRoots" : ["./typings"]
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { workspace, Event, EventEmitter, ConfigurationChangeEvent, ConfigurationTarget } from 'vscode';
|
||||
import { DistributionManager } from './distribution';
|
||||
import { logger } from './logging';
|
||||
@@ -39,18 +39,6 @@ class Setting {
|
||||
|
||||
const ROOT_SETTING = new Setting('codeQL');
|
||||
|
||||
// Enable experimental features
|
||||
|
||||
/**
|
||||
* Any settings below are deliberately not in package.json so that
|
||||
* they do not appear in the settings ui in vscode itself. If users
|
||||
* want to enable experimental features, they can add them directly in
|
||||
* their vscode settings json file.
|
||||
*/
|
||||
|
||||
/* Advanced setting: used to enable bqrs parsing in the cli instead of in the webview. */
|
||||
export const EXPERIMENTAL_BQRS_SETTING = new Setting('experimentalBqrsParsing', ROOT_SETTING);
|
||||
|
||||
// Distribution configuration
|
||||
|
||||
const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING);
|
||||
@@ -221,3 +209,15 @@ export class QueryHistoryConfigListener extends ConfigListener implements QueryH
|
||||
return QUERY_HISTORY_FORMAT_SETTING.getValue<string>();
|
||||
}
|
||||
}
|
||||
|
||||
// Enable experimental features
|
||||
|
||||
/**
|
||||
* Any settings below are deliberately not in package.json so that
|
||||
* they do not appear in the settings ui in vscode itself. If users
|
||||
* want to enable experimental features, they can add them directly in
|
||||
* their vscode settings json file.
|
||||
*/
|
||||
|
||||
/* Advanced setting: used to enable the AST Viewer. */
|
||||
export const EXPERIMENTAL_AST_VIEWER = new Setting('experimentalAstViewer', ROOT_SETTING);
|
||||
|
||||
135
extensions/ql-vscode/src/contextual/astBuilder.ts
Normal file
135
extensions/ql-vscode/src/contextual/astBuilder.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { QueryWithResults } from '../run-queries';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { DecodedBqrsChunk, BqrsId, EntityValue } from '../bqrs-cli-types';
|
||||
import { DatabaseItem } from '../databases';
|
||||
import { AstItem, RootAstItem } from '../astViewer';
|
||||
|
||||
/**
|
||||
* A class that wraps a tree of QL results from a query that
|
||||
* has an @kind of graph
|
||||
*/
|
||||
export default class AstBuilder {
|
||||
|
||||
private roots: RootAstItem[] | undefined;
|
||||
private bqrsPath: string;
|
||||
constructor(
|
||||
queryResults: QueryWithResults,
|
||||
private cli: CodeQLCliServer,
|
||||
public db: DatabaseItem,
|
||||
public fileName: string
|
||||
) {
|
||||
this.bqrsPath = queryResults.query.resultsPaths.resultsPath;
|
||||
}
|
||||
|
||||
async getRoots(): Promise<RootAstItem[]> {
|
||||
if (!this.roots) {
|
||||
this.roots = await this.parseRoots();
|
||||
}
|
||||
return this.roots;
|
||||
}
|
||||
|
||||
private async parseRoots(): Promise<RootAstItem[]> {
|
||||
const options = { entities: ['id', 'url', 'string'] };
|
||||
const [nodeTuples, edgeTuples, graphProperties] = await Promise.all([
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'nodes', options),
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'edges', options),
|
||||
await this.cli.bqrsDecode(this.bqrsPath, 'graphProperties', options),
|
||||
]);
|
||||
|
||||
if (!this.isValidGraph(graphProperties)) {
|
||||
throw new Error('AST is invalid');
|
||||
}
|
||||
|
||||
const idToItem = new Map<BqrsId, AstItem | RootAstItem>();
|
||||
const parentToChildren = new Map<BqrsId, BqrsId[]>();
|
||||
const childToParent = new Map<BqrsId, BqrsId>();
|
||||
const astOrder = new Map<BqrsId, number>();
|
||||
const roots = [];
|
||||
|
||||
// Build up the parent-child relationships
|
||||
edgeTuples.tuples.forEach(tuple => {
|
||||
const [source, target, tupleType, orderValue] = tuple as [EntityValue, EntityValue, string, string];
|
||||
const sourceId = source.id!;
|
||||
const targetId = target.id!;
|
||||
|
||||
switch (tupleType) {
|
||||
case 'semmle.order':
|
||||
astOrder.set(targetId, Number(orderValue));
|
||||
break;
|
||||
|
||||
case 'semmle.label': {
|
||||
childToParent.set(targetId, sourceId);
|
||||
let children = parentToChildren.get(sourceId);
|
||||
if (!children) {
|
||||
parentToChildren.set(sourceId, children = []);
|
||||
}
|
||||
children.push(targetId);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// ignore other tupleTypes since they are not needed by the ast viewer
|
||||
}
|
||||
});
|
||||
|
||||
// populate parents and children
|
||||
nodeTuples.tuples.forEach(tuple => {
|
||||
const [entity, tupleType, orderValue] = tuple as [EntityValue, string, string];
|
||||
const id = entity.id!;
|
||||
|
||||
switch (tupleType) {
|
||||
case 'semmle.order':
|
||||
astOrder.set(id, Number(orderValue));
|
||||
break;
|
||||
|
||||
case 'semmle.label': {
|
||||
const item = {
|
||||
id,
|
||||
label: entity.label,
|
||||
location: entity.url,
|
||||
children: [] as AstItem[],
|
||||
order: Number.MAX_SAFE_INTEGER
|
||||
};
|
||||
|
||||
idToItem.set(id, item as RootAstItem);
|
||||
const parent = idToItem.get(childToParent.has(id) ? childToParent.get(id)! : -1);
|
||||
|
||||
if (parent) {
|
||||
const astItem = item as AstItem;
|
||||
astItem.parent = parent;
|
||||
parent.children.push(astItem);
|
||||
}
|
||||
const children = parentToChildren.has(id) ? parentToChildren.get(id)! : [];
|
||||
children.forEach(childId => {
|
||||
const child = idToItem.get(childId) as AstItem | undefined;
|
||||
if (child) {
|
||||
child.parent = item;
|
||||
item.children.push(child);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// ignore other tupleTypes since they are not needed by the ast viewer
|
||||
}
|
||||
});
|
||||
|
||||
// find the roots and add the order
|
||||
for (const [, item] of idToItem) {
|
||||
item.order = astOrder.has(item.id)
|
||||
? astOrder.get(item.id)!
|
||||
: Number.MAX_SAFE_INTEGER;
|
||||
|
||||
if (!('parent' in item)) {
|
||||
roots.push(item);
|
||||
}
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
private isValidGraph(graphProperties: DecodedBqrsChunk) {
|
||||
const tuple = graphProperties?.tuples?.find(t => t[0] === 'semmle.graphKind');
|
||||
return tuple?.[1] === 'tree';
|
||||
}
|
||||
}
|
||||
28
extensions/ql-vscode/src/contextual/fileRangeFromURI.ts
Normal file
28
extensions/ql-vscode/src/contextual/fileRangeFromURI.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { UrlValue, LineColumnLocation } from '../bqrs-cli-types';
|
||||
import { DatabaseItem } from '../databases';
|
||||
|
||||
|
||||
export default function fileRangeFromURI(uri: UrlValue, db: DatabaseItem): vscode.Location | undefined {
|
||||
if (typeof uri === 'string') {
|
||||
return undefined;
|
||||
} else if ('startOffset' in uri) {
|
||||
return undefined;
|
||||
} else {
|
||||
const loc = uri as LineColumnLocation;
|
||||
const range = new vscode.Range(Math.max(0, (loc.startLine || 0) - 1),
|
||||
Math.max(0, (loc.startColumn || 0) - 1),
|
||||
Math.max(0, (loc.endLine || 0) - 1),
|
||||
Math.max(0, (loc.endColumn || 0)));
|
||||
try {
|
||||
const parsed = vscode.Uri.parse(uri.uri, true);
|
||||
if (parsed.scheme === 'file') {
|
||||
return new vscode.Location(db.resolveSourceFile(parsed.fsPath), range);
|
||||
}
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
extensions/ql-vscode/src/contextual/keyType.ts
Normal file
37
extensions/ql-vscode/src/contextual/keyType.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export enum KeyType {
|
||||
DefinitionQuery = 'DefinitionQuery',
|
||||
ReferenceQuery = 'ReferenceQuery',
|
||||
PrintAstQuery = 'PrintAstQuery',
|
||||
}
|
||||
|
||||
export function tagOfKeyType(keyType: KeyType): string {
|
||||
switch (keyType) {
|
||||
case KeyType.DefinitionQuery:
|
||||
return 'ide-contextual-queries/local-definitions';
|
||||
case KeyType.ReferenceQuery:
|
||||
return 'ide-contextual-queries/local-references';
|
||||
case KeyType.PrintAstQuery:
|
||||
return 'ide-contextual-queries/print-ast';
|
||||
}
|
||||
}
|
||||
|
||||
export function nameOfKeyType(keyType: KeyType): string {
|
||||
switch (keyType) {
|
||||
case KeyType.DefinitionQuery:
|
||||
return 'definitions';
|
||||
case KeyType.ReferenceQuery:
|
||||
return 'references';
|
||||
case KeyType.PrintAstQuery:
|
||||
return 'print AST';
|
||||
}
|
||||
}
|
||||
|
||||
export function kindOfKeyType(keyType: KeyType): string {
|
||||
switch (keyType) {
|
||||
case KeyType.DefinitionQuery:
|
||||
case KeyType.ReferenceQuery:
|
||||
return 'definitions';
|
||||
case KeyType.PrintAstQuery:
|
||||
return 'graph';
|
||||
}
|
||||
}
|
||||
103
extensions/ql-vscode/src/contextual/locationFinder.ts
Normal file
103
extensions/ql-vscode/src/contextual/locationFinder.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { decodeSourceArchiveUri, zipArchiveScheme } from '../archive-filesystem-provider';
|
||||
import { ColumnKindCode, EntityValue, getResultSetSchema } from '../bqrs-cli-types';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { DatabaseManager, DatabaseItem } from '../databases';
|
||||
import fileRangeFromURI from './fileRangeFromURI';
|
||||
import * as messages from '../messages';
|
||||
import { QueryServerClient } from '../queryserver-client';
|
||||
import { QueryWithResults, compileAndRunQueryAgainstDatabase } from '../run-queries';
|
||||
import { KeyType } from './keyType';
|
||||
import { qlpackOfDatabase, resolveQueries } from './queryResolver';
|
||||
|
||||
const SELECT_QUERY_NAME = '#select';
|
||||
export const TEMPLATE_NAME = 'selectedSourceFile';
|
||||
|
||||
export interface FullLocationLink extends vscode.LocationLink {
|
||||
originUri: vscode.Uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function executes a contextual query inside a given database, filters, and converts
|
||||
* the results into source locations. This function is the workhorse for all search-based
|
||||
* contextual queries like find references and find definitions.
|
||||
*
|
||||
* @param cli The cli server
|
||||
* @param qs The query server client
|
||||
* @param dbm The database manager
|
||||
* @param uriString The selected source file and location
|
||||
* @param keyType The contextual query type to run
|
||||
* @param filter A function that will filter extraneous results
|
||||
*/
|
||||
export async function getLocationsForUriString(
|
||||
cli: CodeQLCliServer,
|
||||
qs: QueryServerClient,
|
||||
dbm: DatabaseManager,
|
||||
uriString: string,
|
||||
keyType: KeyType,
|
||||
filter: (src: string, dest: string) => boolean
|
||||
): Promise<FullLocationLink[]> {
|
||||
const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString));
|
||||
const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme });
|
||||
|
||||
const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
|
||||
if (db) {
|
||||
const qlpack = await qlpackOfDatabase(cli, db);
|
||||
if (qlpack === undefined) {
|
||||
throw new Error('Can\'t infer qlpack from database source archive');
|
||||
}
|
||||
const links: FullLocationLink[] = [];
|
||||
for (const query of await resolveQueries(cli, qlpack, keyType)) {
|
||||
const templates: messages.TemplateDefinitions = {
|
||||
[TEMPLATE_NAME]: {
|
||||
values: {
|
||||
tuples: [[{
|
||||
stringValue: uri.pathWithinSourceArchive
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates);
|
||||
if (results.result.resultType == messages.QueryResultType.SUCCESS) {
|
||||
links.push(...await getLinksFromResults(results, cli, db, filter));
|
||||
}
|
||||
}
|
||||
return links;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function getLinksFromResults(
|
||||
results: QueryWithResults,
|
||||
cli: CodeQLCliServer,
|
||||
db: DatabaseItem,
|
||||
filter: (srcFile: string, destFile: string) => boolean
|
||||
): Promise<FullLocationLink[]> {
|
||||
const localLinks: FullLocationLink[] = [];
|
||||
const bqrsPath = results.query.resultsPaths.resultsPath;
|
||||
const info = await cli.bqrsInfo(bqrsPath);
|
||||
const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info);
|
||||
if (selectInfo && selectInfo.columns.length == 3
|
||||
&& selectInfo.columns[0].kind == ColumnKindCode.ENTITY
|
||||
&& selectInfo.columns[1].kind == ColumnKindCode.ENTITY
|
||||
&& selectInfo.columns[2].kind == ColumnKindCode.STRING) {
|
||||
// TODO: Page this
|
||||
const allTuples = await cli.bqrsDecode(bqrsPath, SELECT_QUERY_NAME);
|
||||
for (const tuple of allTuples.tuples) {
|
||||
const [src, dest] = tuple as [EntityValue, EntityValue];
|
||||
const srcFile = src.url && fileRangeFromURI(src.url, db);
|
||||
const destFile = dest.url && fileRangeFromURI(dest.url, db);
|
||||
if (srcFile && destFile && filter(srcFile.uri.toString(), destFile.uri.toString())) {
|
||||
localLinks.push({
|
||||
targetRange: destFile.range,
|
||||
targetUri: destFile.uri,
|
||||
originSelectionRange: srcFile.range,
|
||||
originUri: srcFile.uri
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return localLinks;
|
||||
}
|
||||
45
extensions/ql-vscode/src/contextual/queryResolver.ts
Normal file
45
extensions/ql-vscode/src/contextual/queryResolver.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as tmp from 'tmp-promise';
|
||||
|
||||
import * as helpers from '../helpers';
|
||||
import {
|
||||
KeyType,
|
||||
kindOfKeyType,
|
||||
nameOfKeyType,
|
||||
tagOfKeyType
|
||||
} from './keyType';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { DatabaseItem } from '../databases';
|
||||
|
||||
export async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<string | undefined> {
|
||||
if (db.contents === undefined)
|
||||
return undefined;
|
||||
const datasetPath = db.contents.datasetUri.fsPath;
|
||||
const { qlpack } = await helpers.resolveDatasetFolder(cli, datasetPath);
|
||||
return qlpack;
|
||||
}
|
||||
|
||||
|
||||
export async function resolveQueries(cli: CodeQLCliServer, qlpack: string, keyType: KeyType): Promise<string[]> {
|
||||
const suiteFile = (await tmp.file({
|
||||
postfix: '.qls'
|
||||
})).path;
|
||||
const suiteYaml = {
|
||||
qlpack,
|
||||
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) {
|
||||
helpers.showAndLogErrorMessage(
|
||||
`No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(keyType)}") could be found in the current library path. It might be necessary to upgrade the CodeQL libraries.`
|
||||
);
|
||||
throw new Error(`Couldn't find any queries tagged ${tagOfKeyType(keyType)} for qlpack ${qlpack}`);
|
||||
}
|
||||
return queries;
|
||||
}
|
||||
170
extensions/ql-vscode/src/contextual/templateProvider.ts
Normal file
170
extensions/ql-vscode/src/contextual/templateProvider.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
|
||||
import { decodeSourceArchiveUri, zipArchiveScheme } from '../archive-filesystem-provider';
|
||||
import { CodeQLCliServer } from '../cli';
|
||||
import { DatabaseManager } from '../databases';
|
||||
import { CachedOperation } from '../helpers';
|
||||
import * as messages from '../messages';
|
||||
import { QueryServerClient } from '../queryserver-client';
|
||||
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from '../run-queries';
|
||||
import AstBuilder from './astBuilder';
|
||||
import {
|
||||
KeyType,
|
||||
} from './keyType';
|
||||
import { FullLocationLink, getLocationsForUriString, TEMPLATE_NAME } from './locationFinder';
|
||||
import { qlpackOfDatabase, resolveQueries } from './queryResolver';
|
||||
|
||||
/**
|
||||
* Run templated CodeQL queries to find definitions and references in
|
||||
* source-language files. We may eventually want to find a way to
|
||||
* generalize this to other custom queries, e.g. showing dataflow to
|
||||
* or from a selected identifier.
|
||||
*/
|
||||
|
||||
export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvider {
|
||||
private cache: CachedOperation<vscode.LocationLink[]>;
|
||||
|
||||
constructor(
|
||||
private cli: CodeQLCliServer,
|
||||
private qs: QueryServerClient,
|
||||
private dbm: DatabaseManager,
|
||||
) {
|
||||
this.cache = new CachedOperation<vscode.LocationLink[]>(this.getDefinitions.bind(this));
|
||||
}
|
||||
|
||||
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.LocationLink[]> {
|
||||
const fileLinks = await this.cache.get(document.uri.toString());
|
||||
const locLinks: vscode.LocationLink[] = [];
|
||||
for (const link of fileLinks) {
|
||||
if (link.originSelectionRange!.contains(position)) {
|
||||
locLinks.push(link);
|
||||
}
|
||||
}
|
||||
return locLinks;
|
||||
}
|
||||
|
||||
private async getDefinitions(uriString: string): Promise<vscode.LocationLink[]> {
|
||||
return getLocationsForUriString(
|
||||
this.cli,
|
||||
this.qs,
|
||||
this.dbm,
|
||||
uriString,
|
||||
KeyType.DefinitionQuery,
|
||||
(src, _dest) => src === uriString
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider {
|
||||
private cache: CachedOperation<FullLocationLink[]>;
|
||||
|
||||
constructor(
|
||||
private cli: CodeQLCliServer,
|
||||
private qs: QueryServerClient,
|
||||
private dbm: DatabaseManager,
|
||||
) {
|
||||
this.cache = new CachedOperation<FullLocationLink[]>(this.getReferences.bind(this));
|
||||
}
|
||||
|
||||
async provideReferences(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
_context: vscode.ReferenceContext,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.Location[]> {
|
||||
const fileLinks = await this.cache.get(document.uri.toString());
|
||||
const locLinks: vscode.Location[] = [];
|
||||
for (const link of fileLinks) {
|
||||
if (link.targetRange!.contains(position)) {
|
||||
locLinks.push({ range: link.originSelectionRange!, uri: link.originUri });
|
||||
}
|
||||
}
|
||||
return locLinks;
|
||||
}
|
||||
|
||||
private async getReferences(uriString: string): Promise<FullLocationLink[]> {
|
||||
return getLocationsForUriString(
|
||||
this.cli,
|
||||
this.qs,
|
||||
this.dbm,
|
||||
uriString,
|
||||
KeyType.ReferenceQuery,
|
||||
(_src, dest) => dest === uriString
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class TemplatePrintAstProvider {
|
||||
private cache: CachedOperation<QueryWithResults | undefined>;
|
||||
|
||||
constructor(
|
||||
private cli: CodeQLCliServer,
|
||||
private qs: QueryServerClient,
|
||||
private dbm: DatabaseManager,
|
||||
) {
|
||||
this.cache = new CachedOperation<QueryWithResults | undefined>(this.getAst.bind(this));
|
||||
}
|
||||
|
||||
async provideAst(document?: vscode.TextDocument): Promise<AstBuilder | undefined> {
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
const queryResults = await this.cache.get(document.uri.toString());
|
||||
if (!queryResults) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new AstBuilder(
|
||||
queryResults, this.cli,
|
||||
this.dbm.findDatabaseItem(vscode.Uri.parse(queryResults.database.databaseUri!))!,
|
||||
path.basename(document.fileName)
|
||||
);
|
||||
}
|
||||
|
||||
private async getAst(uriString: string): Promise<QueryWithResults> {
|
||||
const uri = vscode.Uri.parse(uriString, true);
|
||||
if (uri.scheme !== zipArchiveScheme) {
|
||||
throw new Error('AST Viewing is only available for databases with zipped source archives.');
|
||||
}
|
||||
|
||||
const zippedArchive = decodeSourceArchiveUri(uri);
|
||||
const sourceArchiveUri = vscode.Uri.file(zippedArchive.sourceArchiveZipPath).with({ scheme: zipArchiveScheme });
|
||||
const db = this.dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
|
||||
|
||||
if (!db) {
|
||||
throw new Error('Can\'t infer database from the provided source.');
|
||||
}
|
||||
|
||||
const qlpack = await qlpackOfDatabase(this.cli, db);
|
||||
if (!qlpack) {
|
||||
throw new Error('Can\'t infer qlpack from database source archive');
|
||||
}
|
||||
const queries = await resolveQueries(this.cli, qlpack, KeyType.PrintAstQuery);
|
||||
if (queries.length > 1) {
|
||||
throw new Error('Found multiple Print AST queries. Can\'t continue');
|
||||
}
|
||||
if (queries.length === 0) {
|
||||
throw new Error('Did not find any Print AST queries. Can\'t continue');
|
||||
}
|
||||
|
||||
const query = queries[0];
|
||||
const templates: messages.TemplateDefinitions = {
|
||||
[TEMPLATE_NAME]: {
|
||||
values: {
|
||||
tuples: [[{
|
||||
stringValue: zippedArchive.pathWithinSourceArchive
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
return await compileAndRunQueryAgainstDatabase(
|
||||
this.cli,
|
||||
this.qs,
|
||||
db,
|
||||
false,
|
||||
vscode.Uri.file(query),
|
||||
templates
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,11 +54,11 @@ export async function promptImportInternetDatabase(
|
||||
progress
|
||||
))
|
||||
);
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
if (item) {
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
showAndLogInformationMessage('Database downloaded and imported successfully.');
|
||||
}
|
||||
}
|
||||
showAndLogInformationMessage(
|
||||
'Database downloaded and imported successfully.'
|
||||
);
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
}
|
||||
@@ -106,16 +106,14 @@ export async function promptImportLgtmDatabase(
|
||||
progress
|
||||
))
|
||||
);
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
if (item) {
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
showAndLogInformationMessage('Database downloaded and imported successfully.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Invalid LGTM URL: ${lgtmUrl}`);
|
||||
}
|
||||
if (item) {
|
||||
showAndLogInformationMessage(
|
||||
'Database downloaded and imported successfully.'
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
showAndLogErrorMessage(e.message);
|
||||
}
|
||||
@@ -152,12 +150,9 @@ export async function importArchiveDatabase(
|
||||
progress
|
||||
))
|
||||
);
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
|
||||
if (item) {
|
||||
showAndLogInformationMessage(
|
||||
'Database unzipped and imported successfully.'
|
||||
);
|
||||
commands.executeCommand('codeQLDatabases.focus');
|
||||
showAndLogInformationMessage('Database unzipped and imported successfully.');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message.includes('unexpected end of file')) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import {
|
||||
commands,
|
||||
Event,
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as cli from './cli';
|
||||
import { ExtensionContext } from 'vscode';
|
||||
import { showAndLogErrorMessage, showAndLogWarningMessage, showAndLogInformationMessage } from './helpers';
|
||||
import { zipArchiveScheme, encodeSourceArchiveUri, decodeSourceArchiveUri } from './archive-filesystem-provider';
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { QueryServerConfig } from './config';
|
||||
import { Logger, logger } from './logging';
|
||||
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as tmp from 'tmp-promise';
|
||||
import * as vscode from 'vscode';
|
||||
import { decodeSourceArchiveUri, zipArchiveScheme } from './archive-filesystem-provider';
|
||||
import { ColumnKindCode, EntityValue, getResultSetSchema, LineColumnLocation, UrlValue } from './bqrs-cli-types';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { DatabaseItem, DatabaseManager } from './databases';
|
||||
import * as helpers from './helpers';
|
||||
import { CachedOperation } from './helpers';
|
||||
import * as messages from './messages';
|
||||
import { QueryServerClient } from './queryserver-client';
|
||||
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from './run-queries';
|
||||
|
||||
/**
|
||||
* Run templated CodeQL queries to find definitions and references in
|
||||
* source-language files. We may eventually want to find a way to
|
||||
* generalize this to other custom queries, e.g. showing dataflow to
|
||||
* or from a selected identifier.
|
||||
*/
|
||||
|
||||
const TEMPLATE_NAME = 'selectedSourceFile';
|
||||
const SELECT_QUERY_NAME = '#select';
|
||||
|
||||
enum KeyType {
|
||||
DefinitionQuery = 'DefinitionQuery',
|
||||
ReferenceQuery = 'ReferenceQuery',
|
||||
}
|
||||
|
||||
function tagOfKeyType(keyType: KeyType): string {
|
||||
switch (keyType) {
|
||||
case KeyType.DefinitionQuery: return 'ide-contextual-queries/local-definitions';
|
||||
case KeyType.ReferenceQuery: return 'ide-contextual-queries/local-references';
|
||||
}
|
||||
}
|
||||
|
||||
function nameOfKeyType(keyType: KeyType): string {
|
||||
switch (keyType) {
|
||||
case KeyType.DefinitionQuery: return 'definitions';
|
||||
case KeyType.ReferenceQuery: return 'references';
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveQueries(cli: CodeQLCliServer, qlpack: string, keyType: KeyType): Promise<string[]> {
|
||||
const suiteFile = (await tmp.file({
|
||||
postfix: '.qls'
|
||||
})).path;
|
||||
const suiteYaml = { qlpack, include: { kind: 'definitions', 'tags contain': tagOfKeyType(keyType) } };
|
||||
await fs.writeFile(suiteFile, yaml.safeDump(suiteYaml), 'utf8');
|
||||
|
||||
const queries = await cli.resolveQueriesInSuite(suiteFile, helpers.getOnDiskWorkspaceFolders());
|
||||
if (queries.length === 0) {
|
||||
vscode.window.showErrorMessage(
|
||||
`No ${nameOfKeyType(keyType)} queries (tagged "${tagOfKeyType(keyType)}") could be found in the current library path. It might be necessary to upgrade the CodeQL libraries.`
|
||||
);
|
||||
throw new Error(`Couldn't find any queries tagged ${tagOfKeyType(keyType)} for qlpack ${qlpack}`);
|
||||
}
|
||||
return queries;
|
||||
}
|
||||
|
||||
async function qlpackOfDatabase(cli: CodeQLCliServer, db: DatabaseItem): Promise<string | undefined> {
|
||||
if (db.contents === undefined)
|
||||
return undefined;
|
||||
const datasetPath = db.contents.datasetUri.fsPath;
|
||||
const { qlpack } = await helpers.resolveDatasetFolder(cli, datasetPath);
|
||||
return qlpack;
|
||||
}
|
||||
|
||||
interface FullLocationLink extends vscode.LocationLink {
|
||||
originUri: vscode.Uri;
|
||||
}
|
||||
|
||||
export class TemplateQueryDefinitionProvider implements vscode.DefinitionProvider {
|
||||
private cache: CachedOperation<vscode.LocationLink[]>;
|
||||
|
||||
constructor(
|
||||
private cli: CodeQLCliServer,
|
||||
private qs: QueryServerClient,
|
||||
private dbm: DatabaseManager,
|
||||
) {
|
||||
this.cache = new CachedOperation<vscode.LocationLink[]>(this.getDefinitions.bind(this));
|
||||
}
|
||||
|
||||
async getDefinitions(uriString: string): Promise<vscode.LocationLink[]> {
|
||||
return getLinksForUriString(this.cli, this.qs, this.dbm, uriString, KeyType.DefinitionQuery, (src, _dest) => src === uriString);
|
||||
}
|
||||
|
||||
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.LocationLink[]> {
|
||||
const fileLinks = await this.cache.get(document.uri.toString());
|
||||
const locLinks: vscode.LocationLink[] = [];
|
||||
for (const link of fileLinks) {
|
||||
if (link.originSelectionRange!.contains(position)) {
|
||||
locLinks.push(link);
|
||||
}
|
||||
}
|
||||
return locLinks;
|
||||
}
|
||||
}
|
||||
|
||||
export class TemplateQueryReferenceProvider implements vscode.ReferenceProvider {
|
||||
private cache: CachedOperation<FullLocationLink[]>;
|
||||
|
||||
constructor(
|
||||
private cli: CodeQLCliServer,
|
||||
private qs: QueryServerClient,
|
||||
private dbm: DatabaseManager,
|
||||
) {
|
||||
this.cache = new CachedOperation<FullLocationLink[]>(this.getReferences.bind(this));
|
||||
}
|
||||
|
||||
async getReferences(uriString: string): Promise<FullLocationLink[]> {
|
||||
return getLinksForUriString(this.cli, this.qs, this.dbm, uriString, KeyType.ReferenceQuery, (_src, dest) => dest === uriString);
|
||||
}
|
||||
|
||||
async provideReferences(document: vscode.TextDocument, position: vscode.Position, _context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise<vscode.Location[]> {
|
||||
const fileLinks = await this.cache.get(document.uri.toString());
|
||||
const locLinks: vscode.Location[] = [];
|
||||
for (const link of fileLinks) {
|
||||
if (link.targetRange!.contains(position)) {
|
||||
locLinks.push({ range: link.originSelectionRange!, uri: link.originUri });
|
||||
}
|
||||
}
|
||||
return locLinks;
|
||||
}
|
||||
}
|
||||
|
||||
interface FileRange {
|
||||
file: vscode.Uri;
|
||||
range: vscode.Range;
|
||||
}
|
||||
|
||||
async function getLinksFromResults(results: QueryWithResults, cli: CodeQLCliServer, db: DatabaseItem, filter: (srcFile: string, destFile: string) => boolean): Promise<FullLocationLink[]> {
|
||||
const localLinks: FullLocationLink[] = [];
|
||||
const bqrsPath = results.query.resultsPaths.resultsPath;
|
||||
const info = await cli.bqrsInfo(bqrsPath);
|
||||
const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info);
|
||||
if (selectInfo && selectInfo.columns.length == 3
|
||||
&& selectInfo.columns[0].kind == ColumnKindCode.ENTITY
|
||||
&& selectInfo.columns[1].kind == ColumnKindCode.ENTITY
|
||||
&& selectInfo.columns[2].kind == ColumnKindCode.STRING) {
|
||||
// TODO: Page this
|
||||
const allTuples = await cli.bqrsDecode(bqrsPath, SELECT_QUERY_NAME);
|
||||
for (const tuple of allTuples.tuples) {
|
||||
const src = tuple[0] as EntityValue;
|
||||
const dest = tuple[1] as EntityValue;
|
||||
const srcFile = src.url && fileRangeFromURI(src.url, db);
|
||||
const destFile = dest.url && fileRangeFromURI(dest.url, db);
|
||||
if (srcFile && destFile && filter(srcFile.file.toString(), destFile.file.toString())) {
|
||||
localLinks.push({ targetRange: destFile.range, targetUri: destFile.file, originSelectionRange: srcFile.range, originUri: srcFile.file });
|
||||
}
|
||||
}
|
||||
}
|
||||
return localLinks;
|
||||
}
|
||||
|
||||
async function getLinksForUriString(
|
||||
cli: CodeQLCliServer,
|
||||
qs: QueryServerClient,
|
||||
dbm: DatabaseManager,
|
||||
uriString: string,
|
||||
keyType: KeyType,
|
||||
filter: (src: string, dest: string) => boolean
|
||||
) {
|
||||
const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString));
|
||||
const sourceArchiveUri = vscode.Uri.file(uri.sourceArchiveZipPath).with({ scheme: zipArchiveScheme });
|
||||
|
||||
const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);
|
||||
if (db) {
|
||||
const qlpack = await qlpackOfDatabase(cli, db);
|
||||
if (qlpack === undefined) {
|
||||
throw new Error('Can\'t infer qlpack from database source archive');
|
||||
}
|
||||
const links: FullLocationLink[] = [];
|
||||
for (const query of await resolveQueries(cli, qlpack, keyType)) {
|
||||
const templates: messages.TemplateDefinitions = {
|
||||
[TEMPLATE_NAME]: {
|
||||
values: {
|
||||
tuples: [[{
|
||||
stringValue: uri.pathWithinSourceArchive
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
const results = await compileAndRunQueryAgainstDatabase(cli, qs, db, false, vscode.Uri.file(query), templates);
|
||||
if (results.result.resultType == messages.QueryResultType.SUCCESS) {
|
||||
links.push(...await getLinksFromResults(results, cli, db, filter));
|
||||
}
|
||||
}
|
||||
return links;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function fileRangeFromURI(uri: UrlValue, db: DatabaseItem): FileRange | undefined {
|
||||
if (typeof uri === 'string') {
|
||||
return undefined;
|
||||
} else if ('startOffset' in uri) {
|
||||
return undefined;
|
||||
} else {
|
||||
const loc = uri as LineColumnLocation;
|
||||
const range = new vscode.Range(Math.max(0, loc.startLine - 1),
|
||||
Math.max(0, loc.startColumn - 1),
|
||||
Math.max(0, loc.endLine - 1),
|
||||
Math.max(0, loc.endColumn));
|
||||
try {
|
||||
const parsed = vscode.Uri.parse(uri.uri, true);
|
||||
if (parsed.scheme === 'file') {
|
||||
return { file: db.resolveSourceFile(parsed.fsPath), range };
|
||||
}
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { showAndLogErrorMessage } from './helpers';
|
||||
|
||||
/**
|
||||
* Base class for "discovery" operations, which scan the file system to find specific kinds of
|
||||
@@ -9,7 +10,7 @@ export abstract class Discovery<T> extends DisposableObject {
|
||||
private retry = false;
|
||||
private discoveryInProgress = false;
|
||||
|
||||
constructor() {
|
||||
constructor(private readonly name: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -59,6 +60,11 @@ export abstract class Discovery<T> extends DisposableObject {
|
||||
this.update(results);
|
||||
}
|
||||
});
|
||||
|
||||
discoveryPromise.catch(err => {
|
||||
showAndLogErrorMessage(`${this.name} failed. Reason: ${err.message}`);
|
||||
});
|
||||
|
||||
discoveryPromise.finally(() => {
|
||||
if (this.retry) {
|
||||
// Another refresh request came in while we were still running a previous discovery
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
import { commands, Disposable, ExtensionContext, extensions, languages, ProgressLocation, ProgressOptions, Uri, window as Window, env } from 'vscode';
|
||||
import {
|
||||
commands,
|
||||
Disposable,
|
||||
ExtensionContext,
|
||||
extensions,
|
||||
languages,
|
||||
ProgressLocation,
|
||||
ProgressOptions,
|
||||
Uri,
|
||||
window as Window,
|
||||
env,
|
||||
window
|
||||
} from 'vscode';
|
||||
import { LanguageClient } from 'vscode-languageclient';
|
||||
import * as path from 'path';
|
||||
import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
|
||||
|
||||
import { AstViewer } from './astViewer';
|
||||
import * as archiveFilesystemProvider from './archive-filesystem-provider';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { DistributionConfigListener, QueryHistoryConfigListener, QueryServerConfigListener } from './config';
|
||||
import * as languageSupport from './languageSupport';
|
||||
import { DatabaseManager } from './databases';
|
||||
import { DatabaseUI } from './databases-ui';
|
||||
import { TemplateQueryDefinitionProvider, TemplateQueryReferenceProvider } from './definitions';
|
||||
import {
|
||||
TemplateQueryDefinitionProvider,
|
||||
TemplateQueryReferenceProvider,
|
||||
TemplatePrintAstProvider
|
||||
} from './contextual/templateProvider';
|
||||
import {
|
||||
DEFAULT_DISTRIBUTION_VERSION_RANGE,
|
||||
DistributionKind,
|
||||
@@ -390,7 +408,11 @@ async function activateWithInstalledDistribution(
|
||||
await showResultsForCompletedQuery(item, WebviewReveal.NotForced);
|
||||
} catch (e) {
|
||||
if (e instanceof UserCancellationException) {
|
||||
helpers.showAndLogWarningMessage(e.message);
|
||||
if (e.silent) {
|
||||
logger.log(e.message);
|
||||
} else {
|
||||
helpers.showAndLogWarningMessage(e.message);
|
||||
}
|
||||
} else if (e instanceof Error) {
|
||||
helpers.showAndLogErrorMessage(e.message);
|
||||
} else {
|
||||
@@ -523,6 +545,15 @@ async function activateWithInstalledDistribution(
|
||||
new TemplateQueryReferenceProvider(cliServer, qs, dbm)
|
||||
);
|
||||
|
||||
const astViewer = new AstViewer();
|
||||
ctx.subscriptions.push(commands.registerCommand('codeQL.viewAst', async () => {
|
||||
const ast = await new TemplatePrintAstProvider(cliServer, qs, dbm)
|
||||
.provideAst(window.activeTextEditor?.document);
|
||||
if (ast) {
|
||||
astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName);
|
||||
}
|
||||
}));
|
||||
|
||||
logger.log('Successfully finished extension initialization.');
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,15 @@ import { ideServerLogger } from './logging';
|
||||
/** Starts a new CodeQL language server process, sending progress messages to the status bar. */
|
||||
export async function spawnIdeServer(config: QueryServerConfig): Promise<StreamInfo> {
|
||||
return window.withProgress({ title: 'CodeQL language server', location: ProgressLocation.Window }, async (progressReporter, _) => {
|
||||
const args = ['--check-errors', 'ON_CHANGE'];
|
||||
if (shouldDebug()) {
|
||||
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9009,server=y,suspend=n,quiet=y');
|
||||
}
|
||||
const child = cli.spawnServer(
|
||||
config.codeQlPath,
|
||||
'CodeQL language server',
|
||||
['execute', 'language-server'],
|
||||
['--check-errors', 'ON_CHANGE'],
|
||||
args,
|
||||
ideServerLogger,
|
||||
data => ideServerLogger.log(data.toString(), { trailingNewline: false }),
|
||||
data => ideServerLogger.log(data.toString(), { trailingNewline: false }),
|
||||
@@ -24,3 +28,9 @@ export async function spawnIdeServer(config: QueryServerConfig): Promise<StreamI
|
||||
return { writer: child.stdin!, reader: child.stdout! };
|
||||
});
|
||||
}
|
||||
|
||||
function shouldDebug() {
|
||||
return 'DEBUG_LANGUAGE_SERVER' in process.env
|
||||
&& process.env.DEBUG_LANGUAGE_SERVER !== '0'
|
||||
&& process.env.DEBUG_LANGUAGE_SERVER?.toLocaleLowerCase() !== 'false';
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
ResolvableLocationValue,
|
||||
ColumnSchema,
|
||||
ResultSetSchema,
|
||||
} from 'semmle-bqrs';
|
||||
} from './bqrs-types';
|
||||
import { ResultRow, ParsedResultSets, RawResultSet } from './adapt';
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,10 +16,12 @@ import {
|
||||
FivePartLocation,
|
||||
LocationStyle,
|
||||
LocationValue,
|
||||
tryGetResolvableLocation,
|
||||
WholeFileLocation,
|
||||
ResolvableLocationValue,
|
||||
} from 'semmle-bqrs';
|
||||
} from './bqrs-types';
|
||||
import {
|
||||
tryGetResolvableLocation,
|
||||
} from './bqrs-utils';
|
||||
import { DatabaseItem, DatabaseManager } from './databases';
|
||||
import { ViewSourceFileMsg } from './interface-types';
|
||||
import { Logger } from './logging';
|
||||
@@ -42,7 +44,10 @@ export enum WebviewReveal {
|
||||
NotForced,
|
||||
}
|
||||
|
||||
/** Converts a filesystem URI into a webview URI string that the given panel can use to read the file. */
|
||||
/**
|
||||
* Converts a filesystem URI into a webview URI string that the given panel
|
||||
* can use to read the file.
|
||||
*/
|
||||
export function fileUriToWebviewUri(
|
||||
panel: WebviewPanel,
|
||||
fileUriOnDisk: Uri
|
||||
@@ -50,14 +55,6 @@ export function fileUriToWebviewUri(
|
||||
return panel.webview.asWebviewUri(fileUriOnDisk).toString();
|
||||
}
|
||||
|
||||
/** Converts a URI string received from a webview into a local filesystem URI for the same resource. */
|
||||
export function webviewUriToFileUri(webviewUri: string): Uri {
|
||||
// Webview URIs used the vscode-resource scheme. The filesystem path of the resource can be obtained from the path component of the webview URI.
|
||||
const path = Uri.parse(webviewUri).path;
|
||||
// For this path to be interpreted on the filesystem, we need to parse it as a filesystem URI for the current platform.
|
||||
return Uri.file(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the specified CodeQL location to a URI into the source archive.
|
||||
* @param loc CodeQL location to resolve. Must have a non-empty value for `loc.file`.
|
||||
@@ -155,13 +152,16 @@ export function getHtmlForWebview(
|
||||
</html>`;
|
||||
}
|
||||
|
||||
export async function showLocation(
|
||||
export async function showResolvableLocation(
|
||||
loc: ResolvableLocationValue,
|
||||
databaseItem: DatabaseItem
|
||||
): Promise<void> {
|
||||
const resolvedLocation = tryResolveLocation(loc, databaseItem);
|
||||
if (resolvedLocation) {
|
||||
const doc = await workspace.openTextDocument(resolvedLocation.uri);
|
||||
await showLocation(tryResolveLocation(loc, databaseItem));
|
||||
}
|
||||
|
||||
export async function showLocation(location?: Location) {
|
||||
if (location) {
|
||||
const doc = await workspace.openTextDocument(location.uri);
|
||||
const editorsWithDoc = Window.visibleTextEditors.filter(
|
||||
(e) => e.document === doc
|
||||
);
|
||||
@@ -169,7 +169,7 @@ export async function showLocation(
|
||||
editorsWithDoc.length > 0
|
||||
? editorsWithDoc[0]
|
||||
: await Window.showTextDocument(doc, ViewColumn.One);
|
||||
const range = resolvedLocation.range;
|
||||
const range = location.range;
|
||||
// When highlighting the range, vscode's occurrence-match and bracket-match highlighting will
|
||||
// trigger based on where we place the cursor/selection, and will compete for the user's attention.
|
||||
// For reference:
|
||||
@@ -194,6 +194,7 @@ const findRangeHighlightBackground = new ThemeColor(
|
||||
'editor.findRangeHighlightBackground'
|
||||
);
|
||||
|
||||
|
||||
export const shownLocationDecoration = Window.createTextEditorDecorationType({
|
||||
backgroundColor: findMatchBackground,
|
||||
});
|
||||
@@ -215,7 +216,7 @@ export async function jumpToLocation(
|
||||
);
|
||||
if (databaseItem !== undefined) {
|
||||
try {
|
||||
await showLocation(msg.loc, databaseItem);
|
||||
await showResolvableLocation(msg.loc, databaseItem);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e.message.match(/File not found/)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from 'path';
|
||||
import * as Sarif from 'sarif';
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
Diagnostic,
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
RAW_RESULTS_PAGE_SIZE,
|
||||
INTERPRETED_RESULTS_PAGE_SIZE,
|
||||
ALERTS_TABLE_NAME,
|
||||
RawResultsSortState,
|
||||
} from './interface-types';
|
||||
import { Logger } from './logging';
|
||||
import * as messages from './messages';
|
||||
@@ -41,7 +42,6 @@ import {
|
||||
ParsedResultSets,
|
||||
RawResultSet,
|
||||
} from './adapt';
|
||||
import { EXPERIMENTAL_BQRS_SETTING } from './config';
|
||||
import {
|
||||
WebviewReveal,
|
||||
fileUriToWebviewUri,
|
||||
@@ -191,8 +191,8 @@ export class InterfaceManager extends DisposableObject {
|
||||
return this._panel;
|
||||
}
|
||||
|
||||
private async changeSortState(
|
||||
update: (query: CompletedQuery) => Promise<void>
|
||||
private async changeInterpretedSortState(
|
||||
sortState: InterpretedResultsSortState | undefined
|
||||
): Promise<void> {
|
||||
if (this._displayedQuery === undefined) {
|
||||
showAndLogErrorMessage(
|
||||
@@ -202,10 +202,34 @@ export class InterfaceManager extends DisposableObject {
|
||||
}
|
||||
// Notify the webview that it should expect new results.
|
||||
await this.postMessage({ t: 'resultsUpdating' });
|
||||
await update(this._displayedQuery);
|
||||
this._displayedQuery.updateInterpretedSortState(sortState);
|
||||
await this.showResults(this._displayedQuery, WebviewReveal.NotForced, true);
|
||||
}
|
||||
|
||||
private async changeRawSortState(
|
||||
resultSetName: string,
|
||||
sortState: RawResultsSortState | undefined
|
||||
): Promise<void> {
|
||||
if (this._displayedQuery === undefined) {
|
||||
showAndLogErrorMessage(
|
||||
'Failed to sort results since evaluation info was unknown.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Notify the webview that it should expect new results.
|
||||
await this.postMessage({ t: 'resultsUpdating' });
|
||||
await this._displayedQuery.updateSortState(
|
||||
this.cliServer,
|
||||
resultSetName,
|
||||
sortState
|
||||
);
|
||||
// Sorting resets to first page, as there is arguably no particular
|
||||
// correlation between the results on the nth page that the user
|
||||
// was previously viewing and the contents of the nth page in a
|
||||
// new sorted order.
|
||||
await this.showPageOfRawResults(resultSetName, 0, true);
|
||||
}
|
||||
|
||||
private async handleMsgFromView(msg: FromResultsViewMsg): Promise<void> {
|
||||
switch (msg.t) {
|
||||
case 'viewSourceFile': {
|
||||
@@ -236,25 +260,25 @@ export class InterfaceManager extends DisposableObject {
|
||||
this._panelLoadedCallBacks = [];
|
||||
break;
|
||||
case 'changeSort':
|
||||
await this.changeSortState(query =>
|
||||
query.updateSortState(
|
||||
this.cliServer,
|
||||
msg.resultSetName,
|
||||
msg.sortState
|
||||
)
|
||||
);
|
||||
await this.changeRawSortState(msg.resultSetName, msg.sortState);
|
||||
break;
|
||||
case 'changeInterpretedSort':
|
||||
await this.changeSortState(query =>
|
||||
query.updateInterpretedSortState(this.cliServer, msg.sortState)
|
||||
);
|
||||
await this.changeInterpretedSortState(msg.sortState);
|
||||
break;
|
||||
case 'changePage':
|
||||
if (msg.selectedTable === ALERTS_TABLE_NAME) {
|
||||
await this.showPageOfInterpretedResults(msg.pageNumber);
|
||||
}
|
||||
else {
|
||||
await this.showPageOfRawResults(msg.selectedTable, msg.pageNumber);
|
||||
await this.showPageOfRawResults(
|
||||
msg.selectedTable,
|
||||
msg.pageNumber,
|
||||
// When we are in an unsorted state, we guarantee that
|
||||
// sortedResultsInfo doesn't have an entry for the current
|
||||
// result set. Use this to determine whether or not we use
|
||||
// the sorted bqrs file.
|
||||
this._displayedQuery?.sortedResultsInfo.has(msg.selectedTable) || false
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -335,40 +359,35 @@ export class InterfaceManager extends DisposableObject {
|
||||
}
|
||||
|
||||
const getParsedResultSets = async (): Promise<ParsedResultSets> => {
|
||||
if (EXPERIMENTAL_BQRS_SETTING.getValue()) {
|
||||
const resultSetSchemas = await this.getResultSetSchemas(results);
|
||||
const resultSetNames = resultSetSchemas.map(schema => schema.name);
|
||||
|
||||
// This may not wind up being the page we actually show, if there are interpreted results,
|
||||
// but speculatively send it anyway.
|
||||
const selectedTable = getDefaultResultSetName(resultSetNames);
|
||||
const schema = resultSetSchemas.find(
|
||||
(resultSet) => resultSet.name == selectedTable
|
||||
)!;
|
||||
if (schema === undefined) {
|
||||
return { t: 'WebviewParsed' };
|
||||
const resultSetSchemas = await this.getResultSetSchemas(results);
|
||||
const resultSetNames = resultSetSchemas.map(schema => schema.name);
|
||||
|
||||
// This may not wind up being the page we actually show, if there are interpreted results,
|
||||
// but speculatively send it anyway.
|
||||
const selectedTable = getDefaultResultSetName(resultSetNames);
|
||||
const schema = resultSetSchemas.find(
|
||||
(resultSet) => resultSet.name == selectedTable
|
||||
)!;
|
||||
|
||||
const chunk = await this.cliServer.bqrsDecode(
|
||||
results.query.resultsPaths.resultsPath,
|
||||
schema.name,
|
||||
{
|
||||
offset: schema.pagination?.offsets[0],
|
||||
pageSize: RAW_RESULTS_PAGE_SIZE
|
||||
}
|
||||
|
||||
const chunk = await this.cliServer.bqrsDecode(
|
||||
results.query.resultsPaths.resultsPath,
|
||||
schema.name,
|
||||
RAW_RESULTS_PAGE_SIZE,
|
||||
schema.pagination?.offsets[0]
|
||||
);
|
||||
const adaptedSchema = adaptSchema(schema);
|
||||
const resultSet = adaptBqrs(adaptedSchema, chunk);
|
||||
return {
|
||||
t: 'ExtensionParsed',
|
||||
pageNumber: 0,
|
||||
numPages: numPagesOfResultSet(resultSet),
|
||||
numInterpretedPages: numInterpretedPages(this._interpretation),
|
||||
resultSet: { t: 'RawResultSet', ...resultSet },
|
||||
selectedTable: undefined,
|
||||
resultSetNames,
|
||||
};
|
||||
} else {
|
||||
return { t: 'WebviewParsed' };
|
||||
}
|
||||
);
|
||||
const adaptedSchema = adaptSchema(schema);
|
||||
const resultSet = adaptBqrs(adaptedSchema, chunk);
|
||||
return {
|
||||
pageNumber: 0,
|
||||
numPages: numPagesOfResultSet(resultSet),
|
||||
numInterpretedPages: numInterpretedPages(this._interpretation),
|
||||
resultSet: { t: 'RawResultSet', ...resultSet },
|
||||
selectedTable: undefined,
|
||||
resultSetNames,
|
||||
};
|
||||
};
|
||||
|
||||
await this.postMessage({
|
||||
@@ -429,7 +448,8 @@ export class InterfaceManager extends DisposableObject {
|
||||
*/
|
||||
public async showPageOfRawResults(
|
||||
selectedTable: string,
|
||||
pageNumber: number
|
||||
pageNumber: number,
|
||||
sorted = false
|
||||
): Promise<void> {
|
||||
const results = this._displayedQuery;
|
||||
if (results === undefined) {
|
||||
@@ -451,17 +471,31 @@ export class InterfaceManager extends DisposableObject {
|
||||
if (schema === undefined)
|
||||
throw new Error(`Query result set '${selectedTable}' not found.`);
|
||||
|
||||
const getResultsPath = () => {
|
||||
if (sorted) {
|
||||
const resultsPath = results.sortedResultsInfo.get(selectedTable)?.resultsPath;
|
||||
if (resultsPath === undefined) {
|
||||
throw new Error(`Can't find sorted results for table ${selectedTable}`);
|
||||
}
|
||||
return resultsPath;
|
||||
}
|
||||
else {
|
||||
return results.query.resultsPaths.resultsPath;
|
||||
}
|
||||
};
|
||||
|
||||
const chunk = await this.cliServer.bqrsDecode(
|
||||
results.query.resultsPaths.resultsPath,
|
||||
getResultsPath(),
|
||||
schema.name,
|
||||
RAW_RESULTS_PAGE_SIZE,
|
||||
schema.pagination?.offsets[pageNumber]
|
||||
{
|
||||
offset: schema.pagination?.offsets[pageNumber],
|
||||
pageSize: RAW_RESULTS_PAGE_SIZE
|
||||
}
|
||||
);
|
||||
const adaptedSchema = adaptSchema(schema);
|
||||
const resultSet = adaptBqrs(adaptedSchema, chunk);
|
||||
|
||||
const parsedResultSets: ParsedResultSets = {
|
||||
t: 'ExtensionParsed',
|
||||
pageNumber,
|
||||
resultSet: { t: 'RawResultSet', ...resultSet },
|
||||
numPages: numPagesOfResultSet(resultSet),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { window as Window, OutputChannel, Progress, Disposable } from 'vscode';
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EventEmitter, Event, Uri, WorkspaceFolder, RelativePattern } from 'vscode';
|
||||
import { MultiFileSystemWatcher } from '@github/codeql-vscode-utils';
|
||||
import { MultiFileSystemWatcher } from './vscode-utils/multi-file-system-watcher';
|
||||
import { CodeQLCliServer, QlpacksInfo } from './cli';
|
||||
import { Discovery } from './discovery';
|
||||
|
||||
@@ -16,18 +16,17 @@ export class QLPackDiscovery extends Discovery<QlpacksInfo> {
|
||||
private readonly watcher = this.push(new MultiFileSystemWatcher());
|
||||
private _qlPacks: readonly QLPack[] = [];
|
||||
|
||||
constructor(private readonly workspaceFolder: WorkspaceFolder,
|
||||
private readonly cliServer: CodeQLCliServer) {
|
||||
|
||||
super();
|
||||
constructor(
|
||||
private readonly workspaceFolder: WorkspaceFolder,
|
||||
private readonly cliServer: CodeQLCliServer
|
||||
) {
|
||||
super('QL Pack Discovery');
|
||||
|
||||
// Watch for any changes to `qlpack.yml` files in this workspace folder.
|
||||
// TODO: The CLI server should tell us what paths to watch for.
|
||||
this.watcher.addWatch(new RelativePattern(this.workspaceFolder, '**/qlpack.yml'));
|
||||
this.watcher.addWatch(new RelativePattern(this.workspaceFolder, '**/.codeqlmanifest.json'));
|
||||
this.push(this.watcher.onDidChange(this.handleQLPackFileChanged, this));
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
public get onDidChangeQLPacks(): Event<void> { return this._onDidChangeQLPacks.event; }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as path from 'path';
|
||||
import { QLPackDiscovery } from './qlpack-discovery';
|
||||
import { QLPackDiscovery, QLPack } from './qlpack-discovery';
|
||||
import { Discovery } from './discovery';
|
||||
import { EventEmitter, Event, Uri, RelativePattern, env } from 'vscode';
|
||||
import { MultiFileSystemWatcher } from '@github/codeql-vscode-utils';
|
||||
import { EventEmitter, Event, Uri, RelativePattern, WorkspaceFolder, env, workspace } from 'vscode';
|
||||
import { MultiFileSystemWatcher } from './vscode-utils/multi-file-system-watcher';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
|
||||
/**
|
||||
@@ -114,15 +114,15 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
|
||||
private readonly watcher: MultiFileSystemWatcher = this.push(new MultiFileSystemWatcher());
|
||||
private _testDirectories: QLTestDirectory[] = [];
|
||||
|
||||
constructor(private readonly qlPackDiscovery: QLPackDiscovery,
|
||||
private readonly cliServer: CodeQLCliServer) {
|
||||
|
||||
super();
|
||||
constructor(
|
||||
private readonly qlPackDiscovery: QLPackDiscovery,
|
||||
private readonly workspaceFolder: WorkspaceFolder,
|
||||
private readonly cliServer: CodeQLCliServer
|
||||
) {
|
||||
super('QL Test Discovery');
|
||||
|
||||
this.push(this.qlPackDiscovery.onDidChangeQLPacks(this.handleDidChangeQLPacks, this));
|
||||
this.push(this.watcher.onDidChange(this.handleDidChange, this));
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +151,7 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
|
||||
const qlPacks = this.qlPackDiscovery.qlPacks;
|
||||
for (const qlPack of qlPacks) {
|
||||
//HACK: Assume that only QL packs whose name ends with '-tests' contain tests.
|
||||
if (qlPack.name.endsWith('-tests')) {
|
||||
if (this.isRelevantQlPack(qlPack)) {
|
||||
watchPaths.push(qlPack.uri.fsPath);
|
||||
const testPackage = await this.discoverTests(qlPack.uri.fsPath, qlPack.name);
|
||||
if (testPackage !== undefined) {
|
||||
@@ -160,10 +160,7 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
testDirectories: testDirectories,
|
||||
watchPaths: watchPaths
|
||||
};
|
||||
return { testDirectories, watchPaths };
|
||||
}
|
||||
|
||||
protected update(results: QLTestDiscoveryResults): void {
|
||||
@@ -177,6 +174,15 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
|
||||
this._onDidChangeTests.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only include qlpacks suffixed with '-tests' that are contained
|
||||
* within the provided workspace folder.
|
||||
*/
|
||||
private isRelevantQlPack(qlPack: QLPack): boolean {
|
||||
return qlPack.name.endsWith('-tests')
|
||||
&& workspace.getWorkspaceFolder(qlPack.uri)?.index === this.workspaceFolder.index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover all QL tests in the specified directory and its subdirectories.
|
||||
* @param fullPath The full path of the test directory.
|
||||
|
||||
@@ -105,11 +105,7 @@ class HistoryTreeDataProvider
|
||||
getChildren(
|
||||
element?: CompletedQuery
|
||||
): vscode.ProviderResult<CompletedQuery[]> {
|
||||
if (element == undefined) {
|
||||
return this.history;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
return element ? [] : this.history;
|
||||
}
|
||||
|
||||
getParent(_element: CompletedQuery): vscode.ProviderResult<CompletedQuery> {
|
||||
|
||||
@@ -117,7 +117,7 @@ export class CompletedQuery implements QueryWithResults {
|
||||
this.sortedResultsInfo.set(resultSetName, sortedResultSetInfo);
|
||||
}
|
||||
|
||||
async updateInterpretedSortState(_server: cli.CodeQLCliServer, sortState: InterpretedResultsSortState | undefined): Promise<void> {
|
||||
async updateInterpretedSortState(sortState: InterpretedResultsSortState | undefined): Promise<void> {
|
||||
this.interpretedResultsSortState = sortState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import * as cp from 'child_process';
|
||||
import * as path from 'path';
|
||||
// Import from the specific module within `semmle-vscode-utils`, rather than via `index.ts`, because
|
||||
// we avoid taking an accidental runtime dependency on `vscode` this way.
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils/out/disposable-object';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { Disposable } from 'vscode';
|
||||
import { CancellationToken, createMessageConnection, MessageConnection, RequestType } from 'vscode-jsonrpc';
|
||||
import * as cli from './cli';
|
||||
|
||||
@@ -34,7 +34,15 @@ export const tmpDirDisposal = {
|
||||
}
|
||||
};
|
||||
|
||||
export class UserCancellationException extends Error { }
|
||||
export class UserCancellationException extends Error {
|
||||
/**
|
||||
* @param message The error message
|
||||
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
|
||||
*/
|
||||
constructor(message?: string, public readonly silent = false) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of evaluation-time information about a query,
|
||||
@@ -307,7 +315,11 @@ async function checkDbschemeCompatibility(
|
||||
|
||||
/**
|
||||
* Prompts the user to save `document` if it has unsaved changes.
|
||||
* Returns true if we should save changes.
|
||||
*
|
||||
* @param document The document to save.
|
||||
*
|
||||
* @returns true if we should save changes and false if we should continue without saving changes.
|
||||
* @throws UserCancellationException if we should abort whatever operation triggered this prompt
|
||||
*/
|
||||
async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<boolean> {
|
||||
if (document.isDirty) {
|
||||
@@ -317,9 +329,14 @@ async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<b
|
||||
else {
|
||||
const yesItem = { title: 'Yes', isCloseAffordance: false };
|
||||
const alwaysItem = { title: 'Always Save', isCloseAffordance: false };
|
||||
const noItem = { title: 'No', isCloseAffordance: true };
|
||||
const noItem = { title: 'No (run anyway)', isCloseAffordance: false };
|
||||
const cancelItem = { title: 'Cancel', isCloseAffordance: true };
|
||||
const message = 'Query file has unsaved changes. Save now?';
|
||||
const chosenItem = await vscode.window.showInformationMessage(message, { modal: true }, yesItem, alwaysItem, noItem);
|
||||
const chosenItem = await vscode.window.showInformationMessage(
|
||||
message,
|
||||
{ modal: true },
|
||||
yesItem, alwaysItem, noItem, cancelItem
|
||||
);
|
||||
|
||||
if (chosenItem === alwaysItem) {
|
||||
await config.AUTOSAVE_SETTING.updateValue(true, vscode.ConfigurationTarget.Workspace);
|
||||
@@ -329,6 +346,10 @@ async function promptUserToSaveChanges(document: vscode.TextDocument): Promise<b
|
||||
if (chosenItem === yesItem) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (chosenItem === cancelItem) {
|
||||
throw new UserCancellationException('Query run cancelled.', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as Sarif from 'sarif';
|
||||
import * as path from 'path';
|
||||
import { LocationStyle, ResolvableLocationValue } from 'semmle-bqrs';
|
||||
import { LocationStyle, ResolvableLocationValue } from './bqrs-types';
|
||||
|
||||
export interface SarifLink {
|
||||
dest: number;
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { TestAdapterRegistrar } from 'vscode-test-adapter-util';
|
||||
import { QLTestFile, QLTestNode, QLTestDirectory, QLTestDiscovery } from './qltest-discovery';
|
||||
import { Event, EventEmitter, CancellationTokenSource, CancellationToken } from 'vscode';
|
||||
import { DisposableObject } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { QLPackDiscovery } from './qlpack-discovery';
|
||||
import { CodeQLCliServer } from './cli';
|
||||
import { getOnDiskWorkspaceFolders } from './helpers';
|
||||
@@ -98,7 +98,9 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
|
||||
super();
|
||||
|
||||
this.qlPackDiscovery = this.push(new QLPackDiscovery(workspaceFolder, cliServer));
|
||||
this.qlTestDiscovery = this.push(new QLTestDiscovery(this.qlPackDiscovery, cliServer));
|
||||
this.qlTestDiscovery = this.push(new QLTestDiscovery(this.qlPackDiscovery, workspaceFolder, cliServer));
|
||||
this.qlPackDiscovery.refresh();
|
||||
this.qlTestDiscovery.refresh();
|
||||
|
||||
this.push(this.qlTestDiscovery.onDidChangeTests(this.discoverTests, this));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { Uri, TextDocumentShowOptions, commands, window } from 'vscode';
|
||||
import { TestTreeNode } from './test-tree-node';
|
||||
import { DisposableObject, UIService } from '@github/codeql-vscode-utils';
|
||||
import { DisposableObject } from './vscode-utils/disposable-object';
|
||||
import { UIService } from './vscode-utils/ui-service';
|
||||
import { TestHub, TestController, TestAdapter, TestRunStartedEvent, TestRunFinishedEvent, TestEvent, TestSuiteEvent } from 'vscode-test-adapter-api';
|
||||
import { QLTestAdapter, getExpectedFile, getActualFile } from './test-adapter';
|
||||
import { logger } from './logging';
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as React from 'react';
|
||||
import { vscode } from './vscode-api';
|
||||
import { RawResultsSortState, SortDirection } from '../interface-types';
|
||||
import { nextSortDirection } from './result-table-utils';
|
||||
import { ColumnSchema } from 'semmle-bqrs';
|
||||
import { ColumnSchema } from '../bqrs-types';
|
||||
|
||||
interface Props {
|
||||
readonly columns: readonly ColumnSchema[];
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as path from 'path';
|
||||
import * as React from 'react';
|
||||
import * as Sarif from 'sarif';
|
||||
import * as Keys from '../result-keys';
|
||||
import { LocationStyle } from 'semmle-bqrs';
|
||||
import { LocationStyle } from '../bqrs-types';
|
||||
import * as octicons from './octicons';
|
||||
import { className, renderLocation, ResultTableProps, zebraStripe, selectableZebraStripe, jumpToLocation, nextSortDirection } from './result-table-utils';
|
||||
import { onNavigation, NavigationEvent } from './results';
|
||||
|
||||
@@ -30,7 +30,7 @@ export class RawTable extends React.Component<RawTableProps, {}> {
|
||||
const tableRows = dataRows.map((row: ResultRow, rowIndex: number) =>
|
||||
<RawTableRow
|
||||
key={rowIndex}
|
||||
rowIndex={rowIndex}
|
||||
rowIndex={rowIndex + this.props.offset}
|
||||
row={row}
|
||||
databaseUri={databaseUri}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { LocationValue, ResolvableLocationValue, tryGetResolvableLocation } from 'semmle-bqrs';
|
||||
import { LocationValue, ResolvableLocationValue } from '../bqrs-types';
|
||||
import { tryGetResolvableLocation } from '../bqrs-utils';
|
||||
import { RawResultsSortState, QueryMetadata, SortDirection } from '../interface-types';
|
||||
import { assertNever } from '../helpers-pure';
|
||||
import { ResultSet } from '../interface-types';
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { PathTable } from './alert-table';
|
||||
import { RawTable } from './raw-results-table';
|
||||
import { ResultTableProps, tableSelectionHeaderClassName, toggleDiagnosticsClassName, alertExtrasClassName } from './result-table-utils';
|
||||
import { ParsedResultSets, ExtensionParsedResultSets } from '../adapt';
|
||||
import { ParsedResultSets } from '../adapt';
|
||||
import { vscode } from './vscode-api';
|
||||
|
||||
|
||||
@@ -90,52 +90,23 @@ export class ResultTables
|
||||
}
|
||||
|
||||
private getResultSetNames(resultSets: ResultSet[]): string[] {
|
||||
if (this.props.parsedResultSets.t === 'ExtensionParsed') {
|
||||
return this.props.parsedResultSets.resultSetNames.concat([ALERTS_TABLE_NAME]);
|
||||
}
|
||||
else {
|
||||
return resultSets.map(resultSet => resultSet.schema.name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if we have a result set obtained from the extension that came
|
||||
* from the ExtensionParsed branch of ParsedResultSets. This is evidence
|
||||
* that the user has the experimental flag turned on that allows extension-side
|
||||
* bqrs parsing.
|
||||
*/
|
||||
paginationAllowed(): boolean {
|
||||
return this.props.parsedResultSets.t === 'ExtensionParsed';
|
||||
return this.props.parsedResultSets.resultSetNames.concat([ALERTS_TABLE_NAME]);
|
||||
}
|
||||
|
||||
constructor(props: ResultTablesProps) {
|
||||
super(props);
|
||||
const selectedTable = props.parsedResultSets.selectedTable || getDefaultResultSet(this.getResultSets());
|
||||
let selectedPage: string;
|
||||
|
||||
switch (props.parsedResultSets.t) {
|
||||
case 'ExtensionParsed':
|
||||
selectedPage = (props.parsedResultSets.pageNumber + 1) + '';
|
||||
break;
|
||||
case 'WebviewParsed':
|
||||
selectedPage = '';
|
||||
break;
|
||||
}
|
||||
const selectedPage = (props.parsedResultSets.pageNumber + 1) + '';
|
||||
this.state = { selectedTable, selectedPage };
|
||||
}
|
||||
|
||||
private onTableSelectionChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
|
||||
const selectedTable = event.target.value;
|
||||
|
||||
if (this.paginationAllowed()) {
|
||||
vscode.postMessage({
|
||||
t: 'changePage',
|
||||
pageNumber: 0,
|
||||
selectedTable
|
||||
});
|
||||
}
|
||||
else
|
||||
this.setState({ selectedTable });
|
||||
vscode.postMessage({
|
||||
t: 'changePage',
|
||||
pageNumber: 0,
|
||||
selectedTable
|
||||
});
|
||||
}
|
||||
|
||||
private alertTableExtras(): JSX.Element | undefined {
|
||||
@@ -164,15 +135,11 @@ export class ResultTables
|
||||
|
||||
getOffset(): number {
|
||||
const { parsedResultSets } = this.props;
|
||||
switch (parsedResultSets.t) {
|
||||
case 'ExtensionParsed':
|
||||
return parsedResultSets.pageNumber * RAW_RESULTS_PAGE_SIZE;
|
||||
case 'WebviewParsed':
|
||||
return 0;
|
||||
}
|
||||
return parsedResultSets.pageNumber * RAW_RESULTS_PAGE_SIZE;
|
||||
}
|
||||
|
||||
renderPageButtons(resultSets: ExtensionParsedResultSets): JSX.Element {
|
||||
renderPageButtons(): JSX.Element {
|
||||
const { parsedResultSets } = this.props;
|
||||
const selectedTable = this.state.selectedTable;
|
||||
|
||||
// FIXME: The extension, not the view, should be in charge of deciding whether to initially show
|
||||
@@ -180,8 +147,8 @@ export class ResultTables
|
||||
// on initial load of query results, resultSets.numPages will have the number of *raw* pages available,
|
||||
// not interpreted pages, because the extension doesn't know the view will default to showing alerts
|
||||
// instead.
|
||||
const numPages = selectedTable == ALERTS_TABLE_NAME ?
|
||||
resultSets.numInterpretedPages : resultSets.numPages;
|
||||
const numPages = Math.max(selectedTable === ALERTS_TABLE_NAME ?
|
||||
parsedResultSets.numInterpretedPages : parsedResultSets.numPages, 1);
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ selectedPage: e.target.value });
|
||||
@@ -201,14 +168,14 @@ export class ResultTables
|
||||
const prevPage = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
vscode.postMessage({
|
||||
t: 'changePage',
|
||||
pageNumber: Math.max(resultSets.pageNumber - 1, 0),
|
||||
pageNumber: Math.max(parsedResultSets.pageNumber - 1, 0),
|
||||
selectedTable,
|
||||
});
|
||||
};
|
||||
const nextPage = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
vscode.postMessage({
|
||||
t: 'changePage',
|
||||
pageNumber: Math.min(resultSets.pageNumber + 1, numPages - 1),
|
||||
pageNumber: Math.min(parsedResultSets.pageNumber + 1, numPages - 1),
|
||||
selectedTable,
|
||||
});
|
||||
};
|
||||
@@ -221,7 +188,12 @@ export class ResultTables
|
||||
value={this.state.selectedPage}
|
||||
onChange={onChange}
|
||||
onBlur={e => choosePage(e.target.value)}
|
||||
onKeyDown={e => { if (e.keyCode === 13) choosePage((e.target as HTMLInputElement).value); }}
|
||||
onKeyDown={e => {
|
||||
if (e.keyCode === 13) {
|
||||
choosePage((e.target as HTMLInputElement).value);
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
<span>
|
||||
/ {numPages}
|
||||
@@ -230,13 +202,6 @@ export class ResultTables
|
||||
</span>;
|
||||
}
|
||||
|
||||
renderButtons(): JSX.Element {
|
||||
if (this.props.parsedResultSets.t === 'ExtensionParsed' && this.paginationAllowed())
|
||||
return this.renderPageButtons(this.props.parsedResultSets);
|
||||
else
|
||||
return <span />;
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { selectedTable } = this.state;
|
||||
const resultSets = this.getResultSets();
|
||||
@@ -250,7 +215,7 @@ export class ResultTables
|
||||
resultSetNames.map(name => <option key={name} value={name}>{name}</option>);
|
||||
|
||||
return <div>
|
||||
{this.renderButtons()}
|
||||
{this.renderPageButtons()}
|
||||
<div className={tableSelectionHeaderClassName}>
|
||||
<select value={selectedTable} onChange={this.onTableSelectionChange}>
|
||||
{resultSetOptions}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import * as Rdom from 'react-dom';
|
||||
import * as bqrs from 'semmle-bqrs';
|
||||
import {
|
||||
ElementBase,
|
||||
PrimitiveColumnValue,
|
||||
PrimitiveTypeKind,
|
||||
tryGetResolvableLocation,
|
||||
} from 'semmle-bqrs';
|
||||
import { assertNever } from '../helpers-pure';
|
||||
import {
|
||||
DatabaseInfo,
|
||||
@@ -22,8 +15,6 @@ import {
|
||||
import { EventHandlers as EventHandlerList } from './event-handler-list';
|
||||
import { ResultTables } from './result-tables';
|
||||
import {
|
||||
ResultValue,
|
||||
ResultRow,
|
||||
ParsedResultSets,
|
||||
} from '../adapt';
|
||||
import { ResultSet } from '../interface-types';
|
||||
@@ -36,91 +27,6 @@ import { vscode } from './vscode-api';
|
||||
* Displaying query results.
|
||||
*/
|
||||
|
||||
async function* getChunkIterator(
|
||||
response: Response
|
||||
): AsyncIterableIterator<Uint8Array> {
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to load results: (${response.status}) ${response.statusText}`
|
||||
);
|
||||
}
|
||||
const reader = response.body!.getReader();
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
yield value!;
|
||||
}
|
||||
}
|
||||
|
||||
function translatePrimitiveValue(
|
||||
value: PrimitiveColumnValue,
|
||||
type: PrimitiveTypeKind
|
||||
): ResultValue {
|
||||
switch (type) {
|
||||
case 'i':
|
||||
case 'f':
|
||||
case 's':
|
||||
case 'd':
|
||||
case 'b':
|
||||
return value.toString();
|
||||
|
||||
case 'u':
|
||||
return {
|
||||
uri: value as string,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function parseResultSets(
|
||||
response: Response
|
||||
): Promise<readonly ResultSet[]> {
|
||||
const chunks = getChunkIterator(response);
|
||||
|
||||
const resultSets: ResultSet[] = [];
|
||||
|
||||
await bqrs.parse(chunks, (resultSetSchema) => {
|
||||
const columnTypes = resultSetSchema.columns.map((column) => column.type);
|
||||
const rows: ResultRow[] = [];
|
||||
resultSets.push({
|
||||
t: 'RawResultSet',
|
||||
schema: resultSetSchema,
|
||||
rows: rows,
|
||||
});
|
||||
|
||||
return (tuple) => {
|
||||
const row: ResultValue[] = [];
|
||||
tuple.forEach((value, index) => {
|
||||
const type = columnTypes[index];
|
||||
if (type.type === 'e') {
|
||||
const element: ElementBase = value as ElementBase;
|
||||
const label =
|
||||
element.label !== undefined ? element.label : element.id.toString(); //REVIEW: URLs?
|
||||
const resolvableLocation = tryGetResolvableLocation(element.location);
|
||||
if (resolvableLocation !== undefined) {
|
||||
row.push({
|
||||
label: label,
|
||||
location: resolvableLocation,
|
||||
});
|
||||
} else {
|
||||
// No location link.
|
||||
row.push(label);
|
||||
}
|
||||
} else {
|
||||
row.push(
|
||||
translatePrimitiveValue(value as PrimitiveColumnValue, type.type)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
rows.push(row);
|
||||
};
|
||||
});
|
||||
|
||||
return resultSets;
|
||||
}
|
||||
|
||||
interface ResultsInfo {
|
||||
parsedResultSets: ParsedResultSets;
|
||||
resultsPath: string;
|
||||
@@ -200,7 +106,6 @@ class App extends React.Component<{}, ResultsViewState> {
|
||||
this.updateStateWithNewResultsInfo({
|
||||
resultsPath: '', // FIXME: Not used for interpreted, refactor so this is not needed
|
||||
parsedResultSets: {
|
||||
t: 'ExtensionParsed',
|
||||
numPages: msg.numPages,
|
||||
numInterpretedPages: msg.numPages,
|
||||
resultSetNames: msg.resultSetNames,
|
||||
@@ -269,13 +174,7 @@ class App extends React.Component<{}, ResultsViewState> {
|
||||
resultsInfo: ResultsInfo
|
||||
): Promise<readonly ResultSet[]> {
|
||||
const parsedResultSets = resultsInfo.parsedResultSets;
|
||||
switch (parsedResultSets.t) {
|
||||
case 'WebviewParsed':
|
||||
return await this.fetchResultSets(resultsInfo);
|
||||
case 'ExtensionParsed': {
|
||||
return [{ t: 'RawResultSet', ...parsedResultSets.resultSet }];
|
||||
}
|
||||
}
|
||||
return [{ t: 'RawResultSet', ...parsedResultSets.resultSet }];
|
||||
}
|
||||
|
||||
private async loadResults(): Promise<void> {
|
||||
@@ -321,35 +220,6 @@ class App extends React.Component<{}, ResultsViewState> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is deprecated, because it calls `fetch`. We are moving
|
||||
* towards doing all bqrs parsing in the extension.
|
||||
*/
|
||||
private async fetchResultSets(
|
||||
resultsInfo: ResultsInfo
|
||||
): Promise<readonly ResultSet[]> {
|
||||
const unsortedResponse = await fetch(resultsInfo.resultsPath);
|
||||
const unsortedResultSets = await parseResultSets(unsortedResponse);
|
||||
return Promise.all(
|
||||
unsortedResultSets.map(async (unsortedResultSet) => {
|
||||
const sortedResultSetInfo = resultsInfo.sortedResultsMap.get(
|
||||
unsortedResultSet.schema.name
|
||||
);
|
||||
if (sortedResultSetInfo === undefined) {
|
||||
return unsortedResultSet;
|
||||
}
|
||||
const response = await fetch(sortedResultSetInfo.resultsPath);
|
||||
const resultSets = await parseResultSets(response);
|
||||
if (resultSets.length != 1) {
|
||||
throw new Error(
|
||||
`Expected sorted BQRS to contain a single result set, encountered ${resultSets.length} result sets.`
|
||||
);
|
||||
}
|
||||
return resultSets[0];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private getSortStates(
|
||||
resultsInfo: ResultsInfo
|
||||
): Map<string, RawResultsSortState> {
|
||||
@@ -369,7 +239,7 @@ class App extends React.Component<{}, ResultsViewState> {
|
||||
displayedResults.resultsInfo !== null
|
||||
) {
|
||||
const parsedResultSets = displayedResults.resultsInfo.parsedResultSets;
|
||||
const key = (parsedResultSets.t === 'ExtensionParsed' ? (parsedResultSets.selectedTable || '') + parsedResultSets.pageNumber : '');
|
||||
const key = (parsedResultSets.selectedTable || '') + parsedResultSets.pageNumber;
|
||||
return (
|
||||
<ResultTables
|
||||
key={key}
|
||||
@@ -405,7 +275,10 @@ class App extends React.Component<{}, ResultsViewState> {
|
||||
|
||||
componentDidMount(): void {
|
||||
this.vscodeMessageHandler = (evt) =>
|
||||
this.handleMessage(evt.data as IntoResultsViewMsg);
|
||||
evt.origin === window.origin
|
||||
? this.handleMessage(evt.data as IntoResultsViewMsg)
|
||||
: console.error(`Invalid event origin ${evt.origin}`);
|
||||
|
||||
window.addEventListener('message', this.vscodeMessageHandler);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
.vscode-codeql__table-selection-header button {
|
||||
padding: 0.3rem;
|
||||
margin: 0.2rem;
|
||||
border-radius: 5px;
|
||||
border: 0;
|
||||
font-size: large;
|
||||
color: var(--vscode-editor-foreground);
|
||||
background-color: var(--vscode-editorGutter-background);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'vscode-test';
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import { Uri, workspace } from 'vscode';
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { QLTestDiscovery } from '../../qltest-discovery';
|
||||
|
||||
describe('qltest-discovery', () => {
|
||||
describe('isRelevantQlPack', () => {
|
||||
it('should check if a qlpack is relevant', () => {
|
||||
const qlTestDiscover: any = new QLTestDiscovery(
|
||||
{ onDidChangeQLPacks: () => ({}) } as any,
|
||||
{ index: 0 } as any,
|
||||
{} as any
|
||||
);
|
||||
|
||||
const uri = workspace.workspaceFolders![0].uri;
|
||||
expect(qlTestDiscover.isRelevantQlPack({
|
||||
name: '-hucairz',
|
||||
uri
|
||||
})).to.be.false;
|
||||
|
||||
expect(qlTestDiscover.isRelevantQlPack({
|
||||
name: '-tests',
|
||||
uri: Uri.file('/a/b/')
|
||||
})).to.be.false;
|
||||
|
||||
expect(qlTestDiscover.isRelevantQlPack({
|
||||
name: '-tests',
|
||||
uri
|
||||
})).to.be.true;
|
||||
|
||||
expect(qlTestDiscover.isRelevantQlPack({
|
||||
name: '-tests',
|
||||
uri: Uri.file(path.join(uri.fsPath, 'other'))
|
||||
})).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as chai from 'chai';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import AstBuilder from '../../../contextual/astBuilder';
|
||||
import { QueryWithResults } from '../../../run-queries';
|
||||
import { CodeQLCliServer } from '../../../cli';
|
||||
import { DatabaseItem } from '../../../databases';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
/**
|
||||
*
|
||||
This test uses an AST generated from this file (already BQRS-decoded in ../data/astBuilder.json):
|
||||
|
||||
#include <common.h>
|
||||
|
||||
int interrupt_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void enable_interrupts(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int disable_interrupts(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
describe('AstBuilder', () => {
|
||||
let mockCli: CodeQLCliServer;
|
||||
let overrides: Record<string, object | undefined>;
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
mockCli = {
|
||||
bqrsDecode: sinon.stub().callsFake((_: string, resultSet: 'nodes' | 'edges' | 'graphProperties') => {
|
||||
return mockDecode(resultSet);
|
||||
})
|
||||
} as unknown as CodeQLCliServer;
|
||||
overrides = {
|
||||
nodes: undefined,
|
||||
edges: undefined,
|
||||
graphProperties: undefined
|
||||
};
|
||||
});
|
||||
|
||||
it('should build the AST roots', async () => {
|
||||
const astBuilder = createAstBuilder();
|
||||
const roots = await astBuilder.getRoots();
|
||||
|
||||
const options = { entities: ['id', 'url', 'string'] };
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'nodes', options);
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'edges', options);
|
||||
expect(mockCli.bqrsDecode).to.have.been.calledWith('/a/b/c', 'graphProperties', options);
|
||||
|
||||
expect(roots.map(
|
||||
r => ({ ...r, children: undefined })
|
||||
)).to.deep.eq(expectedRoots);
|
||||
});
|
||||
|
||||
it('should fail when graphProperties are not correct', async () => {
|
||||
overrides.graphProperties = {
|
||||
tuples: [
|
||||
[
|
||||
'semmle.graphKind',
|
||||
'hucairz'
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
const astBuilder = createAstBuilder();
|
||||
expect(astBuilder.getRoots()).to.be.rejectedWith('AST is invalid');
|
||||
});
|
||||
|
||||
function createAstBuilder() {
|
||||
return new AstBuilder({
|
||||
query: {
|
||||
resultsPaths: {
|
||||
resultsPath: '/a/b/c'
|
||||
}
|
||||
}
|
||||
} as QueryWithResults, mockCli, {} as DatabaseItem, '');
|
||||
}
|
||||
|
||||
function mockDecode(resultSet: 'nodes' | 'edges' | 'graphProperties') {
|
||||
if (overrides[resultSet]) {
|
||||
return overrides[resultSet];
|
||||
}
|
||||
|
||||
const mapper = {
|
||||
nodes: 0,
|
||||
edges: 1,
|
||||
graphProperties: 2
|
||||
};
|
||||
const index = mapper[resultSet] as number;
|
||||
if (index >= 0 && index <= 2) {
|
||||
return JSON.parse(fs.readFileSync(`${__dirname}/../data/astBuilder.json`, 'utf8'))[index];
|
||||
} else {
|
||||
throw new Error(`Invalid resultSet: ${resultSet}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const expectedRoots = [
|
||||
{
|
||||
id: 0,
|
||||
label: '[TopLevelFunction] int disable_interrupts()',
|
||||
location: {
|
||||
uri: 'file:/opt/src/arch/sandbox/lib/interrupts.c',
|
||||
startLine: 19,
|
||||
startColumn: 5,
|
||||
endLine: 19,
|
||||
endColumn: 22
|
||||
},
|
||||
order: 3,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
id: 26363,
|
||||
label: '[TopLevelFunction] void enable_interrupts()',
|
||||
location: {
|
||||
uri: 'file:/opt/src/arch/sandbox/lib/interrupts.c',
|
||||
startLine: 15,
|
||||
startColumn: 6,
|
||||
endLine: 15,
|
||||
endColumn: 22
|
||||
},
|
||||
order: 2,
|
||||
children: undefined
|
||||
},
|
||||
{
|
||||
id: 26364,
|
||||
label: '[TopLevelFunction] int interrupt_init()',
|
||||
location: {
|
||||
uri: 'file:/opt/src/arch/sandbox/lib/interrupts.c',
|
||||
startLine: 10,
|
||||
startColumn: 5,
|
||||
endLine: 10,
|
||||
endColumn: 18
|
||||
},
|
||||
order: 1,
|
||||
children: undefined
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'vscode-test';
|
||||
import 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { Uri, Range } from 'vscode';
|
||||
|
||||
import fileRangeFromURI from '../../../contextual/fileRangeFromURI';
|
||||
import { DatabaseItem } from '../../../databases';
|
||||
import { WholeFileLocation, LineColumnLocation } from '../../../bqrs-cli-types';
|
||||
|
||||
describe('fileRangeFromURI', () => {
|
||||
it('should return undefined when value is a string', () => {
|
||||
expect(fileRangeFromURI('hucairz', createMockDatabaseItem())).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should return a range for a WholeFileLocation', () => {
|
||||
expect(fileRangeFromURI({
|
||||
uri: 'file:///hucairz',
|
||||
} as WholeFileLocation, createMockDatabaseItem())).to.deep.eq({
|
||||
uri: Uri.parse('file:///hucairz', true),
|
||||
range: new Range(0, 0, 0, 0)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a range for a LineColumnLocation', () => {
|
||||
expect(fileRangeFromURI({
|
||||
uri: 'file:///hucairz',
|
||||
startLine: 1,
|
||||
startColumn: 2,
|
||||
endLine: 3,
|
||||
endColumn: 4,
|
||||
} as LineColumnLocation, createMockDatabaseItem())).to.deep.eq({
|
||||
uri: Uri.parse('file:///hucairz', true),
|
||||
range: new Range(0, 1, 2, 4)
|
||||
});
|
||||
});
|
||||
|
||||
function createMockDatabaseItem(): DatabaseItem {
|
||||
return {
|
||||
resolveSourceFile: (file: string) => Uri.file(file)
|
||||
} as DatabaseItem;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'vscode-test';
|
||||
import 'mocha';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import * as sinon from 'sinon';
|
||||
import * as chai from 'chai';
|
||||
import * as sinonChai from 'sinon-chai';
|
||||
import * as pq from 'proxyquire';
|
||||
import { KeyType } from '../../../contextual/keyType';
|
||||
|
||||
const proxyquire = pq.noPreserveCache().noCallThru();
|
||||
chai.use(chaiAsPromised);
|
||||
chai.use(sinonChai);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('queryResolver', () => {
|
||||
let module: Record<string, Function>;
|
||||
let writeFileSpy: sinon.SinonSpy;
|
||||
let resolveDatasetFolderSpy: sinon.SinonStub;
|
||||
let mockCli: Record<string, sinon.SinonStub>;
|
||||
beforeEach(() => {
|
||||
mockCli = {
|
||||
resolveQueriesInSuite: sinon.stub()
|
||||
};
|
||||
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);
|
||||
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',
|
||||
include: {
|
||||
kind: 'definitions',
|
||||
'tags contain': 'ide-contextual-queries/local-definitions'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when there are no queries found', async () => {
|
||||
mockCli.resolveQueriesInSuite.returns([]);
|
||||
|
||||
// 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);
|
||||
// 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'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('qlpackOfDatabase', () => {
|
||||
it('should get the qlpack of a database', async () => {
|
||||
resolveDatasetFolderSpy.returns({ qlpack: 'my-qlpack' });
|
||||
const db = {
|
||||
contents: {
|
||||
datasetUri: {
|
||||
fsPath: '/path/to/database'
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = await module.qlpackOfDatabase(mockCli, db);
|
||||
expect(result).to.eq('my-qlpack');
|
||||
expect(resolveDatasetFolderSpy).to.have.been.calledWith(mockCli, '/path/to/database');
|
||||
});
|
||||
});
|
||||
|
||||
function createModule() {
|
||||
writeFileSpy = sinon.spy();
|
||||
resolveDatasetFolderSpy = sinon.stub();
|
||||
return proxyquire('../../../contextual/queryResolver', {
|
||||
'fs-extra': {
|
||||
writeFile: writeFileSpy
|
||||
},
|
||||
|
||||
'../helpers': {
|
||||
resolveDatasetFolder: resolveDatasetFolderSpy,
|
||||
getOnDiskWorkspaceFolders: () => ({}),
|
||||
showAndLogErrorMessage: () => ({})
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,972 @@
|
||||
[
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
"name": "node",
|
||||
"kind": "Entity"
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"kind": "String"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"kind": "String"
|
||||
}
|
||||
],
|
||||
"tuples": [
|
||||
[
|
||||
{
|
||||
"id": 26359,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
""
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26360,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
""
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26361,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
""
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"label": "[TopLevelFunction] int disable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[TopLevelFunction] int disable_interrupts()"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"label": "[TopLevelFunction] int disable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"3"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26363,
|
||||
"label": "[TopLevelFunction] void enable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[TopLevelFunction] void enable_interrupts()"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26363,
|
||||
"label": "[TopLevelFunction] void enable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26364,
|
||||
"label": "[TopLevelFunction] int interrupt_init()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[TopLevelFunction] int interrupt_init()"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26364,
|
||||
"label": "[TopLevelFunction] int interrupt_init()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26365,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 9,
|
||||
"endLine": 21,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"Type",
|
||||
"[IntType] int"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26365,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 9,
|
||||
"endLine": 21,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[Literal] 0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26365,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 9,
|
||||
"endLine": 21,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"Value",
|
||||
"[Literal] 0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26365,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 9,
|
||||
"endLine": 21,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"ValueCategory",
|
||||
"prvalue"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26366,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 2,
|
||||
"endLine": 21,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[ReturnStmt] return ..."
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26367,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 20,
|
||||
"startColumn": 1,
|
||||
"endLine": 22,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[Block] { ... }"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26368,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 17,
|
||||
"startColumn": 2,
|
||||
"endLine": 17,
|
||||
"endColumn": 8
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[ReturnStmt] return ..."
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26369,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 16,
|
||||
"startColumn": 1,
|
||||
"endLine": 18,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[Block] { ... }"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26370,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 9,
|
||||
"endLine": 12,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"Type",
|
||||
"[IntType] int"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26370,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 9,
|
||||
"endLine": 12,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[Literal] 0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26370,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 9,
|
||||
"endLine": 12,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"Value",
|
||||
"[Literal] 0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26370,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 9,
|
||||
"endLine": 12,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"ValueCategory",
|
||||
"prvalue"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26371,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 2,
|
||||
"endLine": 12,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[ReturnStmt] return ..."
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26372,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 11,
|
||||
"startColumn": 1,
|
||||
"endLine": 13,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"[Block] { ... }"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
"name": "source",
|
||||
"kind": "Entity"
|
||||
},
|
||||
{
|
||||
"name": "target",
|
||||
"kind": "Entity"
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"kind": "String"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"kind": "String"
|
||||
}
|
||||
],
|
||||
"tuples": [
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"label": "[TopLevelFunction] int disable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26359,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"params"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"label": "[TopLevelFunction] int disable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26359,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"label": "[TopLevelFunction] int disable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26367,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 20,
|
||||
"startColumn": 1,
|
||||
"endLine": 22,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"body"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"label": "[TopLevelFunction] int disable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 19,
|
||||
"startColumn": 5,
|
||||
"endLine": 19,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26367,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 20,
|
||||
"startColumn": 1,
|
||||
"endLine": 22,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26363,
|
||||
"label": "[TopLevelFunction] void enable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26360,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"params"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26363,
|
||||
"label": "[TopLevelFunction] void enable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26360,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26363,
|
||||
"label": "[TopLevelFunction] void enable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26369,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 16,
|
||||
"startColumn": 1,
|
||||
"endLine": 18,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"body"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26363,
|
||||
"label": "[TopLevelFunction] void enable_interrupts()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 15,
|
||||
"startColumn": 6,
|
||||
"endLine": 15,
|
||||
"endColumn": 22
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26369,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 16,
|
||||
"startColumn": 1,
|
||||
"endLine": 18,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26364,
|
||||
"label": "[TopLevelFunction] int interrupt_init()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26361,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"params"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26364,
|
||||
"label": "[TopLevelFunction] int interrupt_init()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26361,
|
||||
"label": "",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26364,
|
||||
"label": "[TopLevelFunction] int interrupt_init()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26372,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 11,
|
||||
"startColumn": 1,
|
||||
"endLine": 13,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"body"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26364,
|
||||
"label": "[TopLevelFunction] int interrupt_init()",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 10,
|
||||
"startColumn": 5,
|
||||
"endLine": 10,
|
||||
"endColumn": 18
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26372,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 11,
|
||||
"startColumn": 1,
|
||||
"endLine": 13,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"2"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26366,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 2,
|
||||
"endLine": 21,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26365,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 9,
|
||||
"endLine": 21,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26366,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 2,
|
||||
"endLine": 21,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26365,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 9,
|
||||
"endLine": 21,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26367,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 20,
|
||||
"startColumn": 1,
|
||||
"endLine": 22,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26366,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 2,
|
||||
"endLine": 21,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26367,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 20,
|
||||
"startColumn": 1,
|
||||
"endLine": 22,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26366,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 21,
|
||||
"startColumn": 2,
|
||||
"endLine": 21,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26369,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 16,
|
||||
"startColumn": 1,
|
||||
"endLine": 18,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26368,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 17,
|
||||
"startColumn": 2,
|
||||
"endLine": 17,
|
||||
"endColumn": 8
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26369,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 16,
|
||||
"startColumn": 1,
|
||||
"endLine": 18,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26368,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 17,
|
||||
"startColumn": 2,
|
||||
"endLine": 17,
|
||||
"endColumn": 8
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26371,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 2,
|
||||
"endLine": 12,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26370,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 9,
|
||||
"endLine": 12,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26371,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 2,
|
||||
"endLine": 12,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26370,
|
||||
"label": "[Literal] 0",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 9,
|
||||
"endLine": 12,
|
||||
"endColumn": 9
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26372,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 11,
|
||||
"startColumn": 1,
|
||||
"endLine": 13,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26371,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 2,
|
||||
"endLine": 12,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
"semmle.label",
|
||||
"0"
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": 26372,
|
||||
"label": "[Block] { ... }",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 11,
|
||||
"startColumn": 1,
|
||||
"endLine": 13,
|
||||
"endColumn": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26371,
|
||||
"label": "[ReturnStmt] return ...",
|
||||
"url": {
|
||||
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
|
||||
"startLine": 12,
|
||||
"startColumn": 2,
|
||||
"endLine": 12,
|
||||
"endColumn": 10
|
||||
}
|
||||
},
|
||||
"semmle.order",
|
||||
"0"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
"name": "key",
|
||||
"kind": "String"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"kind": "String"
|
||||
}
|
||||
],
|
||||
"tuples": [["semmle.graphKind", "tree"]]
|
||||
}
|
||||
]
|
||||
@@ -2,7 +2,6 @@ import 'vscode-test';
|
||||
import 'mocha';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import * as sinon from 'sinon';
|
||||
// import * as sinonChai from 'sinon-chai';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as tmp from 'tmp';
|
||||
|
||||
@@ -6,11 +6,10 @@ import * as tmp from 'tmp';
|
||||
import { window, ViewColumn, Uri } from 'vscode';
|
||||
import {
|
||||
fileUriToWebviewUri,
|
||||
webviewUriToFileUri,
|
||||
tryResolveLocation,
|
||||
} from '../../interface-utils';
|
||||
import { getDefaultResultSetName } from '../../interface-types';
|
||||
import { LocationStyle } from 'semmle-bqrs';
|
||||
import { LocationStyle } from '../../bqrs-types';
|
||||
import { DatabaseItem } from '../../databases';
|
||||
|
||||
describe('interface-utils', () => {
|
||||
@@ -33,6 +32,7 @@ describe('interface-utils', () => {
|
||||
localResourceRoots: [fileUriOnDisk],
|
||||
}
|
||||
);
|
||||
|
||||
after(function() {
|
||||
panel.dispose();
|
||||
tmpFile.removeCallback();
|
||||
@@ -47,15 +47,6 @@ describe('interface-utils', () => {
|
||||
};
|
||||
}
|
||||
|
||||
it('should correctly round trip from filesystem to webview and back', function() {
|
||||
const { fileUriOnDisk, panel } = setupWebview('');
|
||||
const webviewUri = fileUriToWebviewUri(panel, fileUriOnDisk);
|
||||
const reconstructedFileUri = webviewUriToFileUri(webviewUri);
|
||||
expect(reconstructedFileUri.toString(true)).to.equal(
|
||||
fileUriOnDisk.toString(true)
|
||||
);
|
||||
});
|
||||
|
||||
it('does not double-encode # in URIs', function() {
|
||||
const { fileUriOnDisk, panel } = setupWebview('#');
|
||||
const webviewUri = fileUriToWebviewUri(panel, fileUriOnDisk);
|
||||
@@ -128,6 +119,26 @@ describe('interface-utils', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should resolve a five-part location with an empty path', () => {
|
||||
const mockDatabaseItem: DatabaseItem = ({
|
||||
resolveSourceFile: sinon.stub().returns(vscode.Uri.parse('abc')),
|
||||
} as unknown) as DatabaseItem;
|
||||
|
||||
expect(
|
||||
tryResolveLocation(
|
||||
{
|
||||
t: LocationStyle.FivePart,
|
||||
colStart: 1,
|
||||
colEnd: 3,
|
||||
lineStart: 4,
|
||||
lineEnd: 5,
|
||||
file: '',
|
||||
},
|
||||
mockDatabaseItem
|
||||
)
|
||||
).to.be.undefined;
|
||||
});
|
||||
|
||||
it('should resolve a string location for whole file', () => {
|
||||
const mockDatabaseItem: DatabaseItem = ({
|
||||
resolveSourceFile: sinon.stub().returns(vscode.Uri.parse('abc')),
|
||||
|
||||
@@ -9,8 +9,19 @@ type Suite = {
|
||||
extensionDevelopmentPath: string;
|
||||
extensionTestsPath: string;
|
||||
launchArgs: string[];
|
||||
version?: string;
|
||||
};
|
||||
|
||||
// Which version of vscode to test against. Can set to 'stable' or
|
||||
// 'insiders' or an explicit version number. See runTest.d.ts in
|
||||
// vscode-test for more details.
|
||||
|
||||
// For CI purposes we want to leave this at 'stable' to catch any bugs
|
||||
// that might show up with new vscode versions released, even though
|
||||
// this makes testing not-quite-pure, but it can be changed for local
|
||||
// testing against old versions if necessary.
|
||||
const VSCODE_VERSION = 'stable';
|
||||
|
||||
/**
|
||||
* Run an integration test suite `suite`, retrying if it segfaults, at
|
||||
* most `tries` times.
|
||||
@@ -53,15 +64,17 @@ async function main() {
|
||||
|
||||
// List of integration test suites.
|
||||
// The path to the extension test runner script is passed to --extensionTestsPath.
|
||||
const integrationTestSuites = [
|
||||
const integrationTestSuites: Suite[] = [
|
||||
// Tests with no workspace selected upon launch.
|
||||
{
|
||||
version: VSCODE_VERSION,
|
||||
extensionDevelopmentPath: extensionDevelopmentPath,
|
||||
extensionTestsPath: path.resolve(__dirname, 'no-workspace', 'index'),
|
||||
launchArgs: ['--disable-extensions'],
|
||||
},
|
||||
// Tests with a simple workspace selected upon launch.
|
||||
{
|
||||
version: VSCODE_VERSION,
|
||||
extensionDevelopmentPath: extensionDevelopmentPath,
|
||||
extensionTestsPath: path.resolve(__dirname, 'minimal-workspace', 'index'),
|
||||
launchArgs: [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Disposable } from "vscode";
|
||||
import { Disposable } from 'vscode';
|
||||
|
||||
/**
|
||||
* Base class to make it easier to implement a `Disposable` that owns other disposable object.
|
||||
@@ -7,9 +7,6 @@ export abstract class DisposableObject implements Disposable {
|
||||
private disposables: Disposable[] = [];
|
||||
private tracked?: Set<Disposable> = undefined;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `obj` to a list of objects to dispose when `this` is disposed. Objects added by `push` are
|
||||
* disposed in reverse order of being added.
|
||||
@@ -99,6 +99,12 @@ repository:
|
||||
comment-start:
|
||||
match: '// | /\*'
|
||||
|
||||
# A pattern that can start a run of whitespace or a comment.
|
||||
# Commonly used as a negative lookahead in the `end` regex of a nonterminal, when searching for
|
||||
# tokens that can't start a child of that nonterminal.
|
||||
whitespace-or-comment-start:
|
||||
match: '\s | $ | (?#comment-start)'
|
||||
|
||||
# All tokens that can appear in any context.
|
||||
non-context-sensitive:
|
||||
patterns:
|
||||
@@ -113,7 +119,7 @@ repository:
|
||||
name: keyword.operator.relational.ql
|
||||
|
||||
comparison-operator:
|
||||
match: '=|\!-'
|
||||
match: '=|\!\='
|
||||
name: keyword.operator.comparison.ql
|
||||
|
||||
arithmetic-operator:
|
||||
@@ -610,6 +616,12 @@ repository:
|
||||
- include: '#import-directive'
|
||||
- include: '#import-as-clause'
|
||||
- include: '#module-declaration'
|
||||
- include: '#newtype-declaration'
|
||||
# See the comment on newtype-declaration for why we include these next three nonterminals at the
|
||||
# module-member level instead of as part of the newtype-declaration.
|
||||
- include: '#newtype-branch-name-with-prefix'
|
||||
- include: '#predicate-parameter-list'
|
||||
- include: '#predicate-body'
|
||||
- include: '#class-declaration'
|
||||
- include: '#select-clause'
|
||||
- include: '#predicate-or-field-declaration'
|
||||
@@ -781,7 +793,7 @@ repository:
|
||||
bindingset-annotation:
|
||||
beginPattern: '#bindingset'
|
||||
# Ends after the next `]`, or when we encounter something other than a `[`.
|
||||
end: '(?! \s | (?#comment-start) | \[ ) |
|
||||
end: '(?! (?#whitespace-or-comment-start) | \[ ) |
|
||||
(?<=\])'
|
||||
name: meta.block.bindingset-annotation.ql
|
||||
patterns:
|
||||
@@ -802,7 +814,7 @@ repository:
|
||||
language-annotation:
|
||||
beginPattern: '#language'
|
||||
# Ends after the next `]`, or when we encounter something other than a `[`.
|
||||
end: '(?! \s | (?#comment-start) | \[ ) |
|
||||
end: '(?! (?#whitespace-or-comment-start) | \[ ) |
|
||||
(?<=\])'
|
||||
name: meta.block.language-annotation.ql
|
||||
patterns:
|
||||
@@ -824,7 +836,7 @@ repository:
|
||||
pragma-annotation:
|
||||
beginPattern: '#pragma'
|
||||
# Ends after the next `]`, or when we encounter something other than a `[`.
|
||||
end: '(?! \s | (?#comment-start) | \[ ) |
|
||||
end: '(?! (?#whitespace-or-comment-start) | \[ ) |
|
||||
(?<=\])'
|
||||
name: meta.block.pragma-annotation.ql
|
||||
patterns:
|
||||
@@ -841,34 +853,53 @@ repository:
|
||||
name: storage.modifier.ql
|
||||
|
||||
# The declaration of an IPA type.
|
||||
# This only includes the `newtype` keyword and the identifier for the IPA type itself. The
|
||||
# branches of the IPA type are modeled as separate nonterminals contained directly in the module
|
||||
# body. This is kind of hacky, but without it, we don't seem to have a way to get TextMate to
|
||||
# handle this:
|
||||
# ```ql
|
||||
# newtype TRoot =
|
||||
# TBranch1(int x) {
|
||||
# x = 5
|
||||
# } or
|
||||
# TBranch2() // No body
|
||||
#
|
||||
# TOther getOther() { any() }
|
||||
# ```
|
||||
# If the branches are within the newtype declaration node, it's very hard to get the upper-id for
|
||||
# the name of the IPA type to be included in the newtype declaration node, without also including
|
||||
# the `TOther` upper-id in the declaration node.
|
||||
newtype-declaration:
|
||||
beginPattern: '#newtype'
|
||||
# Ends when we see something other than one of:
|
||||
# - An upper-id (branch name)
|
||||
# - A comment
|
||||
# - Whitespace
|
||||
# - `=`
|
||||
# - `(`
|
||||
end: '(?! \s | (?#upper-id) | (?#comment-start) | \= | \( )'
|
||||
name: meta.block.newtype.ql
|
||||
patterns:
|
||||
- include: '#non-context-sensitive'
|
||||
- include: '#newtype-branch'
|
||||
|
||||
# The branch of an IPA type.
|
||||
newtype-branch:
|
||||
begin: '(?#upper-id)'
|
||||
beginCaptures:
|
||||
# We're expecting a newtype-declaration-without-keyword immediately after the `newtype` keyword,
|
||||
# so end if we see anything other than the upper-id that starts it, or whitespace, or a comment.
|
||||
# An upper-id can't start anything else at module scope, so once we see the rest of this
|
||||
# newtype-declaration, whatever comes next should end this block.
|
||||
end: '(?#upper-id)'
|
||||
endCaptures:
|
||||
'0':
|
||||
name: entity.name.type.ql
|
||||
# Ends after a `}`, or when we encounter something other than a `{`.
|
||||
end: '(?<=\}) | (?! \s | (?#comment-start) | \{ )'
|
||||
name: meta.block.newtype-branch.ql
|
||||
name: meta.block.newtype-declaration.ql
|
||||
patterns:
|
||||
- include: '#non-context-sensitive'
|
||||
|
||||
# A branch of an IPA type, including just the `=` or `or` prefix and the name of the branch.
|
||||
# The parameter list and body are separate nonterminals contained directly within the module body.
|
||||
# See the comment for newtype-declaration for why.
|
||||
newtype-branch-name-with-prefix:
|
||||
begin: '\= | (?#or)'
|
||||
beginCaptures:
|
||||
'0':
|
||||
patterns:
|
||||
- include: '#or'
|
||||
- include: '#comparison-operator'
|
||||
end: '(?#upper-id)'
|
||||
endCaptures:
|
||||
'0':
|
||||
name: entity.name.type.ql
|
||||
name: meta.block.newtype-branch-name-with-prefix.ql
|
||||
patterns:
|
||||
- include: '#predicate-body'
|
||||
- include: '#non-context-sensitive'
|
||||
- match: '(?#upper-id)'
|
||||
name: entity.name.type.ql
|
||||
|
||||
# The declaration of a class, include an alias.
|
||||
class-declaration:
|
||||
|
||||
@@ -29,15 +29,20 @@ describe('commands declared in package.json', function() {
|
||||
|
||||
commands.forEach((commandDecl: CmdDecl) => {
|
||||
const { command, title } = commandDecl;
|
||||
if (command.match(/^codeQL\./)
|
||||
if (
|
||||
command.match(/^codeQL\./)
|
||||
|| command.match(/^codeQLQueryResults\./)
|
||||
|| command.match(/^codeQLTests\./)) {
|
||||
|| command.match(/^codeQLTests\./)
|
||||
) {
|
||||
paletteCmds.add(command);
|
||||
expect(title).not.to.be.undefined;
|
||||
commandTitles[command] = title!;
|
||||
}
|
||||
else if (command.match(/^codeQLDatabases\./)
|
||||
|| command.match(/^codeQLQueryHistory\./)) {
|
||||
else if (
|
||||
command.match(/^codeQLDatabases\./)
|
||||
|| command.match(/^codeQLQueryHistory\./)
|
||||
|| command.match(/^codeQLAstViewer\./)
|
||||
) {
|
||||
scopedCmds.add(command);
|
||||
expect(title).not.to.be.undefined;
|
||||
commandTitles[command] = title!;
|
||||
@@ -97,5 +102,3 @@ describe('commands declared in package.json', function() {
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
import { LocationStyle, StringLocation, tryGetResolvableLocation } from 'semmle-bqrs';
|
||||
import { LocationStyle, StringLocation } from '../../src/bqrs-types';
|
||||
import { tryGetResolvableLocation } from '../../src/bqrs-utils';
|
||||
|
||||
describe('processing string locations', function () {
|
||||
it('should detect Windows whole-file locations', function () {
|
||||
|
||||
@@ -2,8 +2,6 @@ import { expect } from 'chai';
|
||||
import * as fs from 'fs-extra';
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import * as bqrs from 'semmle-bqrs';
|
||||
import { FileReader } from 'semmle-io-node';
|
||||
import * as tmp from 'tmp';
|
||||
import * as url from 'url';
|
||||
import { CancellationTokenSource } from 'vscode-jsonrpc';
|
||||
@@ -11,6 +9,7 @@ import * as messages from '../../src/messages';
|
||||
import * as qsClient from '../../src/queryserver-client';
|
||||
import * as cli from '../../src/cli';
|
||||
import { ProgressReporter, Logger } from '../../src/logging';
|
||||
import { ColumnValue } from '../../src/bqrs-cli-types';
|
||||
|
||||
|
||||
declare module 'url' {
|
||||
@@ -50,7 +49,7 @@ class Checkpoint<T> {
|
||||
}
|
||||
|
||||
type ResultSets = {
|
||||
[name: string]: bqrs.ColumnValue[][];
|
||||
[name: string]: ColumnValue[][];
|
||||
}
|
||||
|
||||
type QueryTestCase = {
|
||||
@@ -204,24 +203,14 @@ describe('using the query server', function() {
|
||||
|
||||
const actualResultSets: ResultSets = {};
|
||||
it(`should be able to parse results of query ${queryName}`, async function() {
|
||||
let fileReader: FileReader | undefined;
|
||||
try {
|
||||
await evaluationSucceeded.done();
|
||||
fileReader = await FileReader.open(RESULTS_PATH);
|
||||
const resultSetsReader = await bqrs.open(fileReader);
|
||||
for (const reader of resultSetsReader.resultSets) {
|
||||
const actualRows: bqrs.ColumnValue[][] = [];
|
||||
for await (const row of reader.readTuples()) {
|
||||
actualRows.push(row);
|
||||
}
|
||||
actualResultSets[reader.schema.name] = actualRows;
|
||||
}
|
||||
parsedResults.resolve();
|
||||
} finally {
|
||||
if (fileReader) {
|
||||
fileReader.dispose();
|
||||
}
|
||||
await evaluationSucceeded.done();
|
||||
const info = await cliServer.bqrsInfo(RESULTS_PATH);
|
||||
|
||||
for (const resultSet of info['result-sets']) {
|
||||
const decoded = await cliServer.bqrsDecode(RESULTS_PATH, resultSet.name);
|
||||
actualResultSets[resultSet.name] = decoded.tuples;
|
||||
}
|
||||
parsedResults.resolve();
|
||||
});
|
||||
|
||||
it(`should have correct results for query ${queryName}`, async function() {
|
||||
|
||||
@@ -1,3 +1,32 @@
|
||||
{
|
||||
"extends": "./node_modules/typescript-config/extension.tsconfig.json"
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strictNullChecks": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"preserveWatchOutput": true,
|
||||
"newLine": "lf",
|
||||
"noImplicitReturns": true,
|
||||
"experimentalDecorators": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"test",
|
||||
"**/view"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
require('ts-node').register({});
|
||||
const { compileTypeScript, watchTypeScript } = require('@github/codeql-gulp-tasks');
|
||||
|
||||
exports.default = compileTypeScript;
|
||||
exports.watchTypeScript = watchTypeScript;
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "semmle-bqrs",
|
||||
"description": "Parses Binary Query Result Sets generated by CodeQL",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"publisher": "GitHub",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/github/vscode-codeql"
|
||||
},
|
||||
"main": "./out/index",
|
||||
"files": [
|
||||
"out/**",
|
||||
"package.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
"format": "tsfmt -r"
|
||||
},
|
||||
"dependencies": {
|
||||
"leb": "^0.3.0",
|
||||
"reflect-metadata": "~0.1.13",
|
||||
"semmle-io": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.0.8",
|
||||
"@github/codeql-gulp-tasks": "^0.0.4",
|
||||
"typescript-config": "^0.0.1",
|
||||
"typescript-formatter": "^7.2.2"
|
||||
}
|
||||
}
|
||||
@@ -1,407 +0,0 @@
|
||||
import { ResultSetSchema, LocationStyle, ColumnTypeKind } from "./bqrs-schema";
|
||||
import { ResultSetsReader, ResultSetReader } from "./bqrs-file";
|
||||
import { ElementBase, ColumnValue } from "./bqrs-results";
|
||||
|
||||
/**
|
||||
* Represents a binding to all remaining columns, starting at the column index specified by
|
||||
* `startColumn`.
|
||||
*/
|
||||
export interface RestColumnIndex {
|
||||
startColumn: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Indentifies the result column to which a property is bound. May be the index of a specific
|
||||
* column, or an instance of `RestColumnIndex` to bind to all remaining columns.
|
||||
*/
|
||||
export type ColumnIndex = number | RestColumnIndex;
|
||||
|
||||
/**
|
||||
* Options that can be specified for a `@qlTable` attribute.
|
||||
*/
|
||||
export interface TableOptions {
|
||||
/**
|
||||
* The name of the table to bind to. If multiple values are specified, the property is bound to
|
||||
* the the table whose name is earliest in the list.
|
||||
*/
|
||||
name?: string | string[];
|
||||
}
|
||||
|
||||
export enum QLOption {
|
||||
Required = 'required',
|
||||
Optional = 'optional',
|
||||
Forbidden = 'forbidden'
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that can be specified for a `@qlElement` attribute.
|
||||
*/
|
||||
export interface ElementOptions {
|
||||
label?: QLOption;
|
||||
location?: QLOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* An attribute that binds the target property to a result column representing a QL element.
|
||||
* @param index Index of the column to be bound.
|
||||
* @param options Binding options.
|
||||
*/
|
||||
export function qlElement(index: ColumnIndex, options: ElementOptions = {}): PropertyDecorator {
|
||||
return (proto: any, key: PropertyKey): void => {
|
||||
column(proto, {
|
||||
key: key,
|
||||
index: index,
|
||||
type: 'e',
|
||||
options: {
|
||||
label: options.label ? options.label : QLOption.Required,
|
||||
location: options.location ? options.location : QLOption.Required
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An attribute that binds the target property to a result column containing a QL string.
|
||||
* @param index Index of the column to be bound.
|
||||
*/
|
||||
export function qlString(index: ColumnIndex): PropertyDecorator {
|
||||
return (proto: any, key: PropertyKey): void => {
|
||||
column(proto, {
|
||||
key: key,
|
||||
index: index,
|
||||
type: 's'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An attribute that binds the target property to a set of result columns. The individual
|
||||
* columns are bound to properties of the underlying type of the target property.
|
||||
* @param index Index of the first column to be bound.
|
||||
* @param type The type of the property.
|
||||
*/
|
||||
export function qlTuple(index: ColumnIndex, type: { new(): any }): PropertyDecorator {
|
||||
return (proto: any, key: PropertyKey): void => {
|
||||
column(proto, {
|
||||
key: key,
|
||||
index: index,
|
||||
type: type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type PropertyKey = string | symbol;
|
||||
|
||||
interface ColumnProperty {
|
||||
key: PropertyKey;
|
||||
index: ColumnIndex;
|
||||
type: ColumnTypeKind | { new(): any };
|
||||
}
|
||||
|
||||
interface ElementProperty extends ColumnProperty {
|
||||
type: 'e';
|
||||
options: Required<ElementOptions>;
|
||||
}
|
||||
|
||||
function isElement(property: ColumnProperty): property is ElementProperty {
|
||||
return property.type === 'e';
|
||||
}
|
||||
|
||||
const columnPropertiesSymbol = Symbol('columnProperties');
|
||||
|
||||
type PropertyDecorator = (proto: any, key: PropertyKey) => void;
|
||||
|
||||
function column<T extends ColumnProperty>(proto: any, property: T): void {
|
||||
let columnProperties: ColumnProperty[] | undefined = Reflect.getMetadata(columnPropertiesSymbol, proto);
|
||||
if (columnProperties === undefined) {
|
||||
columnProperties = [];
|
||||
Reflect.defineMetadata(columnPropertiesSymbol, columnProperties, proto);
|
||||
}
|
||||
columnProperties.push(property);
|
||||
}
|
||||
|
||||
interface TableProperty {
|
||||
key: PropertyKey;
|
||||
tableNames: string[];
|
||||
rowType: any;
|
||||
}
|
||||
|
||||
const tablePropertiesSymbol = Symbol('tableProperties');
|
||||
|
||||
/**
|
||||
* An attribute that binds the target property to the contents of a result table.
|
||||
* @param rowType The type representing a single row in the bound table. The type of the target
|
||||
* property must be an array of this type.
|
||||
* @param options Binding options.
|
||||
*/
|
||||
export function qlTable(rowType: any, options?: TableOptions): any {
|
||||
return (proto, key: PropertyKey) => {
|
||||
const realOptions = options || {};
|
||||
let names: string[];
|
||||
if (realOptions.name === undefined) {
|
||||
names = [key.toString()]
|
||||
}
|
||||
else if (typeof realOptions.name === 'string') {
|
||||
names = [realOptions.name];
|
||||
}
|
||||
else {
|
||||
names = realOptions.name;
|
||||
}
|
||||
|
||||
let tableProperties: TableProperty[] | undefined = Reflect.getMetadata(tablePropertiesSymbol, proto);
|
||||
if (tableProperties === undefined) {
|
||||
tableProperties = [];
|
||||
Reflect.defineMetadata(tablePropertiesSymbol, tableProperties, proto);
|
||||
}
|
||||
tableProperties.push({
|
||||
key: key,
|
||||
tableNames: names,
|
||||
rowType: rowType
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
type ParseTupleAction = (src: readonly ColumnValue[], dest: any) => void;
|
||||
|
||||
type TupleParser<T> = (src: readonly ColumnValue[]) => T;
|
||||
|
||||
export class CustomResultSet<TTuple> {
|
||||
public constructor(private reader: ResultSetReader,
|
||||
private readonly tupleParser: TupleParser<TTuple>) {
|
||||
}
|
||||
|
||||
public async* readTuples(): AsyncIterableIterator<TTuple> {
|
||||
for await (const tuple of this.reader.readTuples()) {
|
||||
yield this.tupleParser(tuple);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomResultSetBinder {
|
||||
private readonly boundColumns: boolean[];
|
||||
|
||||
private constructor(private readonly rowType: { new(): any },
|
||||
private readonly schema: ResultSetSchema) {
|
||||
|
||||
this.boundColumns = Array(schema.columns.length).fill(false);
|
||||
}
|
||||
|
||||
public static bind<TTuple>(reader: ResultSetReader, rowType: { new(): TTuple }):
|
||||
CustomResultSet<TTuple> {
|
||||
|
||||
const binder = new CustomResultSetBinder(rowType, reader.schema);
|
||||
const tupleParser = binder.bindRoot<TTuple>();
|
||||
|
||||
return new CustomResultSet<TTuple>(reader, tupleParser);
|
||||
}
|
||||
|
||||
private bindRoot<TTuple>(): TupleParser<TTuple> {
|
||||
const { action } = this.bindObject(this.rowType, 0, true);
|
||||
const unboundColumnIndex = this.boundColumns.indexOf(false);
|
||||
if (unboundColumnIndex >= 0) {
|
||||
throw new Error(`Column '${this.schema.name}[${unboundColumnIndex}]' is not bound to a property.`);
|
||||
}
|
||||
|
||||
return tuple => {
|
||||
let result = new this.rowType;
|
||||
action(tuple, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private checkElementProperty(index: ColumnIndex, propertyName: 'location' | 'label',
|
||||
hasProperty: boolean, expectsProperty: QLOption): void {
|
||||
|
||||
switch (expectsProperty) {
|
||||
case QLOption.Required:
|
||||
if (!hasProperty) {
|
||||
throw new Error(`Element column '${this.schema.name}[${index}]' does not have the required '${propertyName}' property.`);
|
||||
}
|
||||
break;
|
||||
|
||||
case QLOption.Forbidden:
|
||||
if (!hasProperty) {
|
||||
throw new Error(`Element column '${this.schema.name}[${index}]' has unexpected '${propertyName}' property.`);
|
||||
}
|
||||
break;
|
||||
|
||||
case QLOption.Optional:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bindObject(type: { new(): any }, startIndex: number, isRoot: boolean): {
|
||||
action: ParseTupleAction,
|
||||
lastColumn: number
|
||||
} {
|
||||
|
||||
const columnProperties: ColumnProperty[] | undefined =
|
||||
Reflect.getMetadata(columnPropertiesSymbol, type.prototype);
|
||||
if (columnProperties === undefined) {
|
||||
throw new Error(`Type '${type.toString()}' does not have any properties decorated with '@column'.`);
|
||||
}
|
||||
|
||||
const actions: ParseTupleAction[] = [];
|
||||
let restProperty: ColumnProperty | undefined = undefined;
|
||||
|
||||
let lastColumn = startIndex;
|
||||
for (const property of columnProperties) {
|
||||
if (typeof property.index === 'object') {
|
||||
if (!isRoot) {
|
||||
throw new Error(`Type '${type.toString()}' has a property bound to '...', but is not the root type.`);
|
||||
}
|
||||
if (restProperty !== undefined) {
|
||||
throw new Error(`Type '${type.toString()}' has multiple properties bound to '...'.`);
|
||||
}
|
||||
restProperty = property;
|
||||
}
|
||||
else {
|
||||
const index = property.index + startIndex;
|
||||
const { action, lastColumn: lastChildColumn } = this.bindColumn(index, type, property,
|
||||
property.key);
|
||||
actions.push(action);
|
||||
lastColumn = Math.max(lastColumn, lastChildColumn);
|
||||
}
|
||||
}
|
||||
|
||||
if (restProperty !== undefined) {
|
||||
const startIndex = (<RestColumnIndex>restProperty.index).startColumn;
|
||||
let index = startIndex;
|
||||
let elementIndex = 0;
|
||||
const elementActions: ParseTupleAction[] = [];
|
||||
while (index < this.schema.columns.length) {
|
||||
const { action, lastColumn: lastChildColumn } = this.bindColumn(index, type, restProperty, elementIndex);
|
||||
elementActions.push(action);
|
||||
index = lastChildColumn + 1;
|
||||
elementIndex++;
|
||||
}
|
||||
|
||||
const key = restProperty.key;
|
||||
actions.push((src, dest) => {
|
||||
const destArray = Array(elementActions.length);
|
||||
elementActions.forEach(action => action(src, destArray));
|
||||
dest[key] = destArray;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
action: (src, dest) => actions.forEach(action => action(src, dest)),
|
||||
lastColumn: lastColumn
|
||||
};
|
||||
}
|
||||
|
||||
private bindColumn(index: number, type: new () => any, property: ColumnProperty,
|
||||
key: PropertyKey | number): {
|
||||
action: ParseTupleAction,
|
||||
lastColumn: number
|
||||
} {
|
||||
|
||||
if ((index < 0) || (index >= this.schema.columns.length)) {
|
||||
throw new Error(`No matching column '${index}' found for property '${type.toString()}.${property.key.toString()}' when binding root type '${this.rowType.toString()}'.`);
|
||||
}
|
||||
if (typeof property.type === 'string') {
|
||||
// This property is bound to a single column
|
||||
return {
|
||||
action: this.bindSingleColumn(index, property, type, key),
|
||||
lastColumn: index
|
||||
};
|
||||
}
|
||||
else {
|
||||
// This property is a tuple that has properties that are bound to columns.
|
||||
const propertyType = property.type;
|
||||
const { action: objectParser, lastColumn: lastChildColumn } = this.bindObject(propertyType, index, false);
|
||||
return {
|
||||
action: (src, dest) => {
|
||||
const destObject = new propertyType;
|
||||
objectParser(src, destObject);
|
||||
dest[key] = destObject;
|
||||
},
|
||||
lastColumn: lastChildColumn
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private bindSingleColumn(index: number, property: ColumnProperty, type: new () => any,
|
||||
key: PropertyKey | number): ParseTupleAction {
|
||||
|
||||
if (this.boundColumns[index]) {
|
||||
throw new Error(`Column '${this.schema.name}[${index}]' is bound to multiple columns in root type '${this.rowType.toString()}'.`);
|
||||
}
|
||||
const column = this.schema.columns[index];
|
||||
if (column.type.type !== property.type) {
|
||||
throw new Error(`Column '${this.schema.name}[${index}]' has type '${column.type.type}', but property '${type.toString()}.${property.key.toString()}' expected type '${property.type}'.`);
|
||||
}
|
||||
this.boundColumns[index] = true;
|
||||
|
||||
if (isElement(property) && (column.type.type === 'e')) {
|
||||
const hasLabel = column.type.hasLabel;
|
||||
this.checkElementProperty(index, 'label', hasLabel, property.options.label);
|
||||
const hasLocation = column.type.locationStyle !== LocationStyle.None;
|
||||
this.checkElementProperty(index, 'location', hasLocation, property.options.location);
|
||||
return (src, dest) => {
|
||||
const srcElement = <ElementBase>src[index];
|
||||
const destElement: ElementBase = {
|
||||
id: srcElement.id
|
||||
};
|
||||
if (hasLabel) {
|
||||
destElement.label = srcElement.label;
|
||||
}
|
||||
if (hasLocation) {
|
||||
destElement.location = srcElement.location;
|
||||
}
|
||||
dest[key] = destElement;
|
||||
};
|
||||
}
|
||||
else {
|
||||
return (src, dest) => {
|
||||
dest[key] = src[index];
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ArrayElementType<T> = T extends Array<infer U> ? U : never;
|
||||
|
||||
export type CustomResultSets<T> = {
|
||||
[P in keyof T]: CustomResultSet<ArrayElementType<T[P]>>;
|
||||
}
|
||||
|
||||
export function createCustomResultSets<T>(reader: ResultSetsReader, type: { new(): T }):
|
||||
CustomResultSets<T> {
|
||||
|
||||
const tableProperties: TableProperty[] | undefined = Reflect.getMetadata(tablePropertiesSymbol, type.prototype);
|
||||
if (tableProperties === undefined) {
|
||||
throw new Error(`Type '${type.toString()}' does not have any properties decorated with '@table'.`);
|
||||
}
|
||||
|
||||
const customResultSets: Partial<CustomResultSets<T>> = {};
|
||||
|
||||
const boundProperties = new Set<PropertyKey>();
|
||||
|
||||
for (const resultSet of reader.resultSets) {
|
||||
const tableProperty = findPropertyForTable(resultSet.schema, tableProperties);
|
||||
if (tableProperty === undefined) {
|
||||
throw new Error(`No matching property found for result set '${resultSet.schema.name}'.`);
|
||||
}
|
||||
if (boundProperties.has(tableProperty.key)) {
|
||||
throw new Error(`Multiple result sets bound to property '${tableProperty.key.toString()}'.`);
|
||||
}
|
||||
boundProperties.add(tableProperty.key);
|
||||
customResultSets[tableProperty.key] = CustomResultSetBinder.bind(resultSet,
|
||||
tableProperty.rowType);
|
||||
}
|
||||
for (const tableProperty of tableProperties) {
|
||||
if (!boundProperties.has(tableProperty.key)) {
|
||||
throw new Error(`No matching table found for property '${tableProperty.key.toString()}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
return <CustomResultSets<T>>customResultSets;
|
||||
}
|
||||
|
||||
function findPropertyForTable(resultSet: ResultSetSchema, tableProperties: TableProperty[]):
|
||||
TableProperty | undefined {
|
||||
|
||||
const tableName = resultSet.name === '#select' ? 'select' : resultSet.name;
|
||||
return tableProperties.find(tableProperty => tableProperty.tableNames.find(name => name === tableName));
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
import { RandomAccessReader, StreamDigester } from 'semmle-io';
|
||||
import { parseResultSetsHeader, StringPool, parseResultSetSchema, readTuples } from './bqrs-parse';
|
||||
import { ResultSetsSchema, ResultSetSchema } from './bqrs-schema';
|
||||
import { ColumnValue } from './bqrs-results';
|
||||
|
||||
/**
|
||||
* The result of parsing data from a specific file region.
|
||||
*/
|
||||
interface RegionResult<T> {
|
||||
/** The parsed data. */
|
||||
result: T,
|
||||
/** The exclusive end position of the parsed data in the file. */
|
||||
finalOffset: number
|
||||
}
|
||||
|
||||
/** Reads data from the specified region of the file, and parses it using the given function. */
|
||||
async function inFileRegion<T>(
|
||||
file: RandomAccessReader,
|
||||
start: number,
|
||||
end: number | undefined,
|
||||
parse: (d: StreamDigester) => Promise<T>
|
||||
): Promise<RegionResult<T>> {
|
||||
const stream = file.readStream(start, end);
|
||||
try {
|
||||
const d = StreamDigester.fromChunkIterator(stream);
|
||||
const result = await parse(d);
|
||||
|
||||
return {
|
||||
result: result,
|
||||
finalOffset: start + d.position
|
||||
};
|
||||
}
|
||||
finally {
|
||||
stream.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single result set in a BQRS file.
|
||||
*/
|
||||
export interface ResultSetReader {
|
||||
/**
|
||||
* The schema that describes the result set.
|
||||
*/
|
||||
readonly schema: ResultSetSchema;
|
||||
/**
|
||||
* Reads all of the tuples in the result set.
|
||||
*/
|
||||
readTuples(): AsyncIterableIterator<ColumnValue[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Binary Query Result Sets ("BQRS") file.
|
||||
*
|
||||
* @remarks
|
||||
* Allows independant access to individual tables without having to parse the entire file up front.
|
||||
*/
|
||||
export interface ResultSetsReader {
|
||||
readonly schema: ResultSetsSchema;
|
||||
readonly resultSets: readonly ResultSetReader[];
|
||||
|
||||
findResultSetByName(name: string): ResultSetReader | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for a single `ResultSet` in a BQRS file.
|
||||
* Does not contain the result tuples themselves.
|
||||
* Includes the offset and length of the tuple data in the file,
|
||||
* which can be used to read the tuples.
|
||||
*/
|
||||
interface ResultSetInfo {
|
||||
schema: ResultSetSchema;
|
||||
rowsOffset: number;
|
||||
rowsLength: number;
|
||||
}
|
||||
|
||||
class ResultSetReaderImpl implements ResultSetReader {
|
||||
public readonly schema: ResultSetSchema;
|
||||
private readonly rowsOffset: number;
|
||||
private readonly rowsLength: number;
|
||||
|
||||
public constructor(private readonly resultSets: ResultSetsReaderImpl, info: ResultSetInfo) {
|
||||
this.schema = info.schema;
|
||||
this.rowsOffset = info.rowsOffset;
|
||||
this.rowsLength = info.rowsLength;
|
||||
}
|
||||
|
||||
public async* readTuples(): AsyncIterableIterator<ColumnValue[]> {
|
||||
const stream = this.resultSets.file.readStream(this.rowsOffset,
|
||||
this.rowsOffset + this.rowsLength);
|
||||
try {
|
||||
const d = StreamDigester.fromChunkIterator(stream);
|
||||
for await (const tuple of readTuples(d, this.schema, await this.resultSets.getStringPool())) {
|
||||
yield tuple;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
stream.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ResultSetsReaderImpl implements ResultSetsReader {
|
||||
private stringPool?: StringPool = undefined;
|
||||
private readonly _resultSets: ResultSetReaderImpl[];
|
||||
|
||||
private constructor(public readonly file: RandomAccessReader,
|
||||
public readonly schema: ResultSetsSchema, resultSets: ResultSetInfo[],
|
||||
private readonly stringPoolOffset: number) {
|
||||
|
||||
this._resultSets = resultSets.map((info) => {
|
||||
return new ResultSetReaderImpl(this, info);
|
||||
});
|
||||
}
|
||||
|
||||
public get resultSets(): readonly ResultSetReader[] {
|
||||
return this._resultSets;
|
||||
}
|
||||
|
||||
public findResultSetByName(name: string): ResultSetReader | undefined {
|
||||
return this._resultSets.find((resultSet) => resultSet.schema.name === name);
|
||||
}
|
||||
|
||||
public async getStringPool(): Promise<StringPool> {
|
||||
if (this.stringPool === undefined) {
|
||||
const { result: stringPoolBuffer } = await inFileRegion(this.file, this.stringPoolOffset,
|
||||
this.stringPoolOffset + this.schema.stringPoolSize,
|
||||
async d => await d.read(this.schema.stringPoolSize));
|
||||
this.stringPool = new StringPool(stringPoolBuffer);
|
||||
}
|
||||
|
||||
return this.stringPool;
|
||||
}
|
||||
|
||||
public static async open(file: RandomAccessReader): Promise<ResultSetsReader> {
|
||||
// Parse the header of the entire BQRS file.
|
||||
const { result: header, finalOffset: stringPoolOffset } =
|
||||
await inFileRegion(file, 0, undefined, d => parseResultSetsHeader(d));
|
||||
|
||||
// The header is followed by a shared string pool.
|
||||
// We have saved the offset and length of the string pool within the file,
|
||||
// so we can read it later when needed.
|
||||
// For now, skip over the string pool to reach the starting point of the first result set.
|
||||
let currentResultSetOffset = stringPoolOffset + header.stringPoolSize;
|
||||
|
||||
// Parse information about each result set within the file.
|
||||
const resultSets: ResultSetInfo[] = [];
|
||||
for (let resultSetIndex = 0; resultSetIndex < header.resultSetCount; resultSetIndex++) {
|
||||
// Read the length of this result set (encoded as a single byte).
|
||||
// Note: reading length and schema together from a file region may be more efficient.
|
||||
// Reading them separately just makes it easier to compute the
|
||||
// starting offset and length of the schema.
|
||||
const { result: resultSetLength, finalOffset: resultSetSchemaOffset } =
|
||||
await inFileRegion(file, currentResultSetOffset, undefined, d => d.readLEB128UInt32());
|
||||
|
||||
// Read the schema of this result set.
|
||||
const { result: resultSetSchema, finalOffset: resultSetRowsOffset } =
|
||||
await inFileRegion(file, resultSetSchemaOffset, undefined, d => parseResultSetSchema(d));
|
||||
const resultSetSchemaLength = resultSetRowsOffset - resultSetSchemaOffset;
|
||||
|
||||
// The schema is followed by the tuple/row data for the result set.
|
||||
// We save the offset and length of the tuple data within the file,
|
||||
// so we can read it later when needed.
|
||||
const info: ResultSetInfo = {
|
||||
// length of result set = length of schema + length of tuple data
|
||||
// The 1 byte that encodes the length itself is not counted.
|
||||
rowsLength: resultSetLength - resultSetSchemaLength,
|
||||
rowsOffset: resultSetRowsOffset,
|
||||
schema: resultSetSchema,
|
||||
};
|
||||
resultSets.push(info);
|
||||
// Skip over the tuple data of the current result set,
|
||||
// to reach the starting offset of the next result set.
|
||||
currentResultSetOffset = info.rowsOffset + info.rowsLength;
|
||||
}
|
||||
|
||||
const schema: ResultSetsSchema = {
|
||||
version: header.version,
|
||||
stringPoolSize: header.stringPoolSize,
|
||||
resultSets: resultSets.map(resultSet => resultSet.schema)
|
||||
};
|
||||
|
||||
const reader = new ResultSetsReaderImpl(file, schema, resultSets, stringPoolOffset);
|
||||
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
|
||||
export function open(file: RandomAccessReader): Promise<ResultSetsReader> {
|
||||
return ResultSetsReaderImpl.open(file);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user