Compare commits

...

111 Commits

Author SHA1 Message Date
jcreedcmu
8a5514c696 Date CHANGELOG for release (#579)
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-09-16 16:22:01 -04:00
Andrew Eisenberg
29f92575ee Update npm dependencies fix security vulns
* node-fetch
* bl
2020-09-16 07:57:31 -07:00
Aditya Sharad
5d63431b8c Actions: Pin version of upload-artifact action (#549) 2020-09-08 16:25:18 -07:00
Andrew Eisenberg
17eee86765 Update changelog 2020-09-02 08:16:27 -07:00
Andrew Eisenberg
95d5274fd4 Avoid showing a link when the underlying path is empty
A common situation when a file is not relevant for a particular result
is to return an empty file path location.

Currently, we are displaying this situation as a hyperlink in the
results, but when clicking on the link, there is an error.

To mirror the behaviour of Eclipse, we should avoid showing a link here.
This commit changes that behaviour.
2020-09-02 08:16:27 -07:00
Dave Bartolomeo
959552544a Fix highlighting after disembodied IPA branch
Fixes #543
```ql
newtype TA = TB()

private predicate foo() { any() }
```
Our TextMate grammar didn't realize that the newtype declaration ended after the closing paren of the branch's parameter list, so the `private` modifier was highlighted incorrectly.

It's surprisingly tricky to get TextMate to handle this correctly, so I wound up just treating the IPA declaration head (`newtype TA`), the branch head (`= TB`), the branch parameter list, and the branch body as directly children of the module body. This is kind of hacky, but it does fix the bug without introducing any new cases where we have incorrect highlighting of valid code.
2020-09-01 22:31:16 -07:00
Andrew Eisenberg
16fab7f45d Fix typo 2020-08-26 08:27:11 -07:00
Andrew Eisenberg
cb03da3716 Avoid running query when a user cancels when there are unsaved changes
Fixes #538

Adds a new menu item to cancel a query run when the query is unsaved.

Also, ensures that no warning message is sent to the console.
2020-08-25 07:43:52 -07:00
Andrew Eisenberg
f968f8e2f5 Add a top-level tsconfig.json
The reason to add this is that I am getting misleadings errors in
vscode that this file is missing. By adding this file, I no longer
see these errors.
2020-08-24 10:58:17 -07:00
jcreedcmu
c247292181 Merge pull request #537 from jcreedcmu/jcreed/fix-paginated-sorting
Fix changing page forgetting about sorting
2020-08-14 09:47:46 -04:00
Jason Reed
518e6c14cc Add changelog entry 2020-08-14 08:09:28 -04:00
Jason Reed
37cf525c8e Fix changing page forgetting about sorting 2020-08-14 08:06:31 -04:00
jcreedcmu
1f4e69940d Merge pull request #536 from jcreedcmu/jcreed/fix-none
Fix #535
2020-08-13 11:04:09 -04:00
Jason Reed
72878fb6fd Pass up empty string at this stage 2020-08-13 09:43:04 -04:00
Jason Reed
6b343b4581 Add changelog entry 2020-08-13 08:23:02 -04:00
Jason Reed
b191f68599 Fix #535. 2020-08-13 08:19:55 -04:00
Andrew Eisenberg
ef84d8d362 Update changelog after release
Add a simple perl script that will augment the CHANGELOG with
an [UNRELEASED] section when creating the PR after a release.
2020-08-12 11:33:18 -07:00
github-actions[bot]
ef55d9d4e0 Bump version to v1.3.3 2020-08-12 10:43:21 -07:00
Andrew Eisenberg
ff841950ae Update Chnagelog for v1.3.2
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Build Extension / Build (ubuntu-latest) (push) Has been cancelled
Build Extension / Build (windows-latest) (push) Has been cancelled
Build Extension / Test (ubuntu-latest) (push) Has been cancelled
Build Extension / Test (windows-latest) (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-08-12 10:35:35 -07:00
Andrew Eisenberg
aaf9e1fb9c Update changelog 2020-08-12 10:35:35 -07:00
jcreedcmu
7f885755c2 Merge pull request #529 from jcreedcmu/jcreed/fix-527
Fix sorting of raw results
2020-08-12 12:31:39 -04:00
Jason Reed
8c55e3ef2d Simplify argument passing 2020-08-12 12:25:20 -04:00
Jason Reed
039343efa2 Fix #527. 2020-08-12 12:10:02 -04:00
Jason Reed
d0982f34a4 Defunctionalize updating sort state
This leads to less sharing of codepaths which is a little bad (slightly more
repetition and rendundancy) but a lot good (can independently fix the way
raw results are redisplayed so as to be actually correct).
2020-08-12 12:10:02 -04:00
jcreedcmu
890821b273 Merge pull request #528 from aeisenberg/aeisenberg/ast-changelog
Update changelog to include line about experimental AST Viewer
2020-08-12 11:22:33 -04:00
Andrew Eisenberg
84e2cf7986 Update changelog to include line about experimental AST Viewer 2020-08-12 07:37:08 -07:00
Andrew Eisenberg
648bf4b629 Add a debug flag to allow remote debugging (#524)
With this flag on, it is possible to remote-debug the language server in a java debugger.
2020-08-06 11:08:26 -07:00
Henning Makholm
8ccb7c4fa4 Merge pull request #522 from github/shati-patel-patch-1
Update pull_request_template.md
2020-07-31 21:31:31 +02:00
Shati Patel
73fc37d370 Update pull_request_template.md
The team has been renamed 🙂
2020-07-31 20:27:28 +01:00
Aditya Sharad
0a3d4095b7 Merge pull request #521 from adityasharad/actions/label-issue
Actions: Autolabel issues when opened
2020-07-31 09:40:41 -07:00
Aditya Sharad
32d4deb575 Update label-issue.yml 2020-07-31 08:57:33 -07:00
Aditya Sharad
d2409054e2 Actions: Autolabel issues when opened 2020-07-30 16:59:07 -07:00
jcreedcmu
6ae5cd3ac3 Merge pull request #519 from aeisenberg/aeisenberg/remove-from-changelog
Remove unreleased feature from changelog
2020-07-27 13:05:09 -04:00
Andrew Eisenberg
0dfc64c7e8 Remove unreleased feature from changelog 2020-07-27 09:53:31 -07:00
Andrew Eisenberg
6a9c9a1eb4 Add catch handler for discovery failures
Display a reasonable message to users if there is a failure.
2020-07-27 08:34:03 -07:00
Andrew Eisenberg
f62cce32da Change how we check for relevant ql packs 2020-07-27 08:34:03 -07:00
Andrew Eisenberg
a36ff8ca1e Update changelog 2020-07-27 08:34:03 -07:00
Andrew Eisenberg
0d1199bb64 Filters qltest-discovery
qlpack tests that are not contained within the current workspace folder
will be filtered from the test runner view.

This also fixes a test that should have been failing but wasn't.
2020-07-27 08:34:03 -07:00
jcreedcmu
3edd8ec1d1 Merge pull request #516 from aeisenberg/aeisenberg/refactor-contextual
Refactor contextual queries
2020-07-24 08:49:37 -04:00
jcreedcmu
4a030dc2f4 Merge pull request #514 from aeisenberg/aeisenberg/fix-ast-viewer-0-id
Fix AST viewer bug where nodes with id=0 did not have children
2020-07-24 08:47:55 -04:00
jcreedcmu
a4f19c9b5d Merge pull request #515 from aeisenberg/aeisenberg/launch-no-npx
Remove reference to npx in luanch config
2020-07-24 08:45:14 -04:00
Andrew Eisenberg
353a87de12 Refactor contextual queries
Break the  file into logically contained
smaller files. And add unit tests for .
2020-07-23 15:00:04 -07:00
Andrew Eisenberg
a2cda79ceb Remove reference to npx in luanch config
Users should not need to install npx in order to launch
the extension.
2020-07-23 12:45:08 -07:00
Andrew Eisenberg
bc73712987 Fix AST viewer bug where nodes with id=0 did not have children 2020-07-23 12:43:11 -07:00
Jason Reed
09c4e7e99b Fix broken launch config
We need to provide the `--extensionDevelopmentPath` flag in these
launch configurations.

It appears to be unnecessary to include
`${workspaceRoot}/extensions/ql-vscode/out/vscode-tests/**/*.js`
in addition to the strictly more general pattern
${workspaceRoot}/extensions/ql-vscode/out/**/*.js

An unfortunate UI fact appears to be that the log of the gulp build is
focused whenever these tasks are run, even though the log you actually
care about seeing is in the `Debug Console` section. Not sure how to
fix that.
2020-07-23 12:40:29 -07:00
jcreedcmu
d0e0ad619b Merge pull request #511 from ceh-forks/ceh-skip-log
Suppress database downloaded message when action canceled
2020-07-23 14:02:24 -04:00
Emil Hessman
e4ff8d1fa8 Only focus database panel on successful download 2020-07-23 19:44:36 +02:00
Andrew Eisenberg
9052851f9a Run CodeQL Action on PRs 2020-07-23 10:25:16 -07:00
jcreedcmu
a946965331 Merge pull request #508 from jcreedcmu/jcreed/untangle3
Fix documentation for current build process
2020-07-23 09:43:40 -04:00
Andrew Eisenberg
10177412f6 Merge pull request #492 from aeisenberg/aeisenberg/ast-viewer
Add the AST Viewer
2020-07-23 06:36:11 -07:00
jcreedcmu
4519e0f951 Update CONTRIBUTING.md
Co-authored-by: Shati Patel <42641846+shati-patel@users.noreply.github.com>
2020-07-23 09:14:37 -04:00
Emil Hessman
0d2b44cdba Suppress database downloaded message when action canceled 2020-07-23 06:40:43 +02:00
Andrew Eisenberg
0045891f9d Clean up ast builder code 2020-07-22 13:34:01 -07:00
Jason Reed
2b712827df Clean up build instructions 2020-07-22 13:05:13 -04:00
Andrew Eisenberg
65b5b68df6 Remove duplicate changelog line 2020-07-21 12:28:50 -07:00
Andrew Eisenberg
f21296e4f6 Merge branch 'aeisenberg/ast-viewer' of github.com:aeisenberg/vscode-codeql into aeisenberg/ast-viewer 2020-07-21 10:10:23 -07:00
Jason Reed
762edd137c Fix CONTRIBUTING.md to reflect changes to build process. 2020-07-21 13:09:44 -04:00
jcreedcmu
b3dc7d75a8 Merge pull request #503 from jcreedcmu/jcreed/untangle2
Try moving build to just gulp
2020-07-21 12:56:34 -04:00
Jason Reed
9ad0bf6f43 Call into package.json scripts from actions workflow 2020-07-21 12:20:17 -04:00
Jason Reed
f8804f946c Use explicit path for vsce 2020-07-21 12:19:03 -04:00
Jason Reed
3c07be5f74 Move type dependency to devDependencies 2020-07-21 12:13:01 -04:00
Jason Reed
cd329eeaeb Fix source maps 2020-07-21 10:09:42 -04:00
Jason Reed
2671414f32 Extract rush from vscode tasks 2020-07-21 09:21:11 -04:00
Andrew Eisenberg
b6bd534857 Fixes pagination when there are no results
When there are no results, always ensure that max pages is 1.

This commit also changes the way pagination buttons are displayed,
removing their border.
2020-07-20 07:11:56 -07:00
Andrew Eisenberg
8093d9a529 Check window event origins
Fixes codescanning warnings:

- https://github.com/github/vscode-codeql/security/code-scanning/1
- https://github.com/github/vscode-codeql/security/code-scanning/2
2020-07-17 10:25:25 -07:00
jcreedcmu
aebab082c2 Merge branch 'main' into aeisenberg/ast-viewer 2020-07-17 10:53:15 -04:00
Andrew Eisenberg
36d612e5b0 Add feature flag for ast viewer
Set `codeQL.experimentalAstViewer` to true in settings
in order for component to be enabled.
2020-07-16 15:42:26 -07:00
Andrew Eisenberg
8459edb57c Fix tests and reformatting
* Fix command-linting tests.
* Fix failing windows test and Use Uri.parse(_, true)
* Use  Uri.parse(_, true). That is the preferred API.
* Reformat comments.
2020-07-16 14:42:48 -07:00
Andrew Eisenberg
af965c941a Update changelog 2020-07-16 14:42:48 -07:00
Andrew Eisenberg
eaa26e5ef7 Add the AST Viewer
This commit adds the AST Viewer for viewing the QL AST of a file in a
database.

The different components are as follows:

1. There is a new view `codeQLAstViewer`, which displays the AST
2. This view is backed by the `AstViewerDataProvider` and `AstViewer` classes in astView.ts
3. To generate an AST, we use contextual queries, similar to how Find references/declarations are implemented. In particular, in `definitions.ts` there is `TemplatePrintAstProvider` which provides an AST for a given source buffer.
  - Similar to the other queries, we first determine which database the buffer belongs to.
  - Based on that, we generate a synthetic qlpack and run the templatized `printAst.ql` query
  - We plug in the archive-relative path name of the source file.
  - After the query is run, we wrap the results in an `AstBuilder` instance.
  - When requested, the `AstBuilder` will generate the full AST of the file from the BQRS results.
  - The AST roots (all top-level elements, functions, variable declarations, etc, are roots) are passed to the `AstViewer` instance, which handles the display lifecycle and other VS Code-specific functions.

There are a few unrelated pieces here, which can be pulled out to another PR if required:

- The `codeQLQueryHistory` view now has a _welcome_ message to make it more obvious to users how to start.
- `definitions.ts` is moved to the `contextual` subfolder.
- `fileRangeFromURI` is extracted from `definitions.ts` to its own file so it can be reused.

Also, note that this relies on https://github.com/github/codeql/pull/3931 for the C/C++ query to be available in the QL sources. Other languages will need similar queries.
2020-07-16 14:42:47 -07:00
Andrew Eisenberg
546ec2eb1c Update changelog 2020-07-16 09:10:05 -07:00
Andrew Eisenberg
565ea0d8a0 Use proper check for existence of search path
Fixes #499
2020-07-16 09:10:05 -07:00
Jason Reed
258f43132c Relax version constraints in package.json 2020-07-16 09:19:07 -04:00
Jason Reed
b7a72b9d21 Remove now unused rush configuration 2020-07-16 09:10:53 -04:00
Jason Reed
d2138907b9 Fix test section of workflow file 2020-07-16 08:51:35 -04:00
Jason Reed
bce3413158 Run npm-installed copy of vsce 2020-07-16 08:49:47 -04:00
Jason Reed
2b53396146 Fix warning 2020-07-16 08:49:07 -04:00
Jason Reed
19a76dcbee Update action to not depend on rush 2020-07-16 08:43:07 -04:00
Jason Reed
56b62ff758 Fix package deploy to not depend on rush 2020-07-16 08:39:17 -04:00
Jason Reed
9083c5d649 Reconcile vscode-engine and api versions 2020-07-16 08:00:37 -04:00
Jason Reed
49c0d39a50 Replace javascript gulpfile with typescript 2020-07-14 13:51:49 -04:00
jcreedcmu
57ea215639 Merge pull request #496 from jcreedcmu/jcreed/untangle
Reduce dependencies on internal modules
2020-07-14 13:03:03 -04:00
Jason Reed
528cbc8d49 Move more config into local typescript gulpfile 2020-07-14 12:52:06 -04:00
Jason Reed
2c5b672c81 Make stub typescript gulpfile 2020-07-14 12:11:54 -04:00
Jason Reed
f0055910c1 Remove typescript-config package 2020-07-14 12:02:51 -04:00
Jason Reed
657df5e07d inline tsconfig inheritance 2020-07-14 11:54:34 -04:00
Jason Reed
53d5c2438a Remove now unused library. 2020-07-14 08:19:45 -04:00
Jason Reed
ac941eb9dd Copy semmle-vscode-utils into extension. 2020-07-14 08:17:30 -04:00
Jason Reed
e5e854822d Remove now-unused libraries. 2020-07-14 08:00:11 -04:00
Jason Reed
868b356588 Sharpen comment slightly. 2020-07-14 07:46:43 -04:00
Jason Reed
2dd841e667 Pacify lint.
Apparently the linter wants a tsconfig file to be able to lint the
compare view typescript. I made the configFile specification in the
webpack.config.ts more specific so that we use the same config
every time during webview build.
2020-07-13 13:04:22 -04:00
Jason Reed
609fea404d Remove extension dependency on semmle-io-node 2020-07-13 12:59:13 -04:00
Jason Reed
24da63fbfa Remove extension dependency on semmle-bqrs 2020-07-13 12:48:55 -04:00
Jason Reed
10156b1f49 Remove semmle-bqrs dependency from test. 2020-07-13 12:46:17 -04:00
Jason Reed
3694fdaecb Make tsconfig.json selection during webpack deterministic.
Without this `configFile` option, ts-loader apparently does not
guarantee a deterministic choice of which of the three `tsconfig.json`
files below `extensions/ql-vscode` actually gets used during webpack.
This leads to very strange behavior as even removing dead code can
change which `tsconfig.json` 'wins the race'. I observed that removing
a dependence on `semmle-bqrs` from `src/view` *tended* to make
`ts-loader` choose `src/compare/view/tsconfig.json` instead.
2020-07-13 12:39:37 -04:00
Jason Reed
4c30374dc3 Extract tryGetResolvableLocation from semmle-bqrs 2020-07-13 11:01:11 -04:00
Jason Reed
26d83b5cef Reduce dependencies on semmle-bqrs.
Eliminate references to types in library semmle-bqrs in favor of a
local copy of those same types in bqrs-types.ts.
2020-07-13 10:56:11 -04:00
Andrew Eisenberg
3639dcb806 Fix tests and reformatting
* Fix command-linting tests.
* Fix failing windows test and Use Uri.parse(_, true)
* Use  Uri.parse(_, true). That is the preferred API.
* Reformat comments.
2020-07-10 08:17:11 -07:00
Andrew Eisenberg
4aa752135d Update changelog 2020-07-10 08:16:40 -07:00
Andrew Eisenberg
80c6ea6eac Add the AST Viewer
This commit adds the AST Viewer for viewing the QL AST of a file in a
database.

The different components are as follows:

1. There is a new view `codeQLAstViewer`, which displays the AST
2. This view is backed by the `AstViewerDataProvider` and `AstViewer` classes in astView.ts
3. To generate an AST, we use contextual queries, similar to how Find references/declarations are implemented. In particular, in `definitions.ts` there is `TemplatePrintAstProvider` which provides an AST for a given source buffer.
  - Similar to the other queries, we first determine which database the buffer belongs to.
  - Based on that, we generate a synthetic qlpack and run the templatized `printAst.ql` query
  - We plug in the archive-relative path name of the source file.
  - After the query is run, we wrap the results in an `AstBuilder` instance.
  - When requested, the `AstBuilder` will generate the full AST of the file from the BQRS results.
  - The AST roots (all top-level elements, functions, variable declarations, etc, are roots) are passed to the `AstViewer` instance, which handles the display lifecycle and other VS Code-specific functions.

There are a few unrelated pieces here, which can be pulled out to another PR if required:

- The `codeQLQueryHistory` view now has a _welcome_ message to make it more obvious to users how to start.
- `definitions.ts` is moved to the `contextual` subfolder.
- `fileRangeFromURI` is extracted from `definitions.ts` to its own file so it can be reused.

Also, note that this relies on https://github.com/github/codeql/pull/3931 for the C/C++ query to be available in the QL sources. Other languages will need similar queries.
2020-07-10 08:16:40 -07:00
jcreedcmu
2243c21afc Merge pull request #494 from jcreedcmu/jcreed/fix-integration-tests
Remove failing integration test
2020-07-10 11:06:41 -04:00
Jason Reed
46bddcd8fa Remove dead code and associated test. 2020-07-10 09:11:08 -04:00
Jason Reed
df5dccc3f6 'Pin' to stable instead 2020-07-10 08:59:23 -04:00
Jason Reed
3207c594e7 Pin to vscode version for integration testing 2020-07-09 18:39:19 -04:00
jcreedcmu
70de59eabd Merge pull request #491 from jcreedcmu/jcreed/cleanup
Remove pagination feature flag
2020-07-08 11:31:23 -04:00
Jason Reed
27dd804731 Fix display of offsets in raw results table 2020-07-08 08:56:42 -04:00
Jason Reed
240e0fbd4e Remove feature flag 2020-07-08 08:56:42 -04:00
Jason Reed
f65caa0d85 Remove ExtensionParsedResultSets type 2020-07-08 08:56:42 -04:00
Jason Reed
e7192eb423 Remove WebviewParsed branch from ParsedResultSets
Also remove dead code downstream from it.
2020-07-08 08:56:42 -04:00
jcreedcmu
06b51326a3 Merge pull request #490 from github/version/bump-to-v1.3.2
Bump version to v1.3.2
2020-07-07 14:44:42 -04:00
github-actions[bot]
82a6ef4844 Bump version to v1.3.2 2020-07-07 18:36:48 +00:00
135 changed files with 12819 additions and 12102 deletions

View File

@@ -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.

View File

@@ -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
View 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 -

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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"
}
]
}
}

View File

@@ -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:

View File

@@ -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.

View File

@@ -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

View File

@@ -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"
],
}
]
}

View File

@@ -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"
// ]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -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"
}
]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,8 +0,0 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "./common.tsconfig.json",
"compilerOptions": {
"declaration": false,
"strict": true
}
}

View File

@@ -1,4 +0,0 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "./common.tsconfig.json"
}

View File

@@ -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": {}
}

View File

@@ -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: {

View File

@@ -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.

View File

@@ -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;

View 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;
}
}

View 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);

View File

@@ -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) => {

View File

@@ -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');

View File

@@ -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"]
}

View 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'));
}

View File

@@ -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$/,

View 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

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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": {

View File

@@ -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;

View 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;
}
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -1,4 +1,4 @@
import { DisposableObject } from '@github/codeql-vscode-utils';
import { DisposableObject } from '../vscode-utils/disposable-object';
import {
WebviewPanel,
ExtensionContext,

View File

@@ -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) {

View File

@@ -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"
]
}
}

View File

@@ -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);

View 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';
}
}

View 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;
}
}
}

View 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';
}
}

View 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;
}

View 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;
}

View 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
);
}
}

View File

@@ -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')) {

View 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,

View File

@@ -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';

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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.');
}

View File

@@ -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';
}

View File

@@ -3,7 +3,7 @@ import {
ResolvableLocationValue,
ColumnSchema,
ResultSetSchema,
} from 'semmle-bqrs';
} from './bqrs-types';
import { ResultRow, ParsedResultSets, RawResultSet } from './adapt';
/**

View File

@@ -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/)) {

View File

@@ -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),

View File

@@ -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';

View File

@@ -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; }

View File

@@ -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.

View File

@@ -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> {

View File

@@ -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;
}
}

View File

@@ -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';

View File

@@ -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;

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -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';

View File

@@ -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[];

View File

@@ -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';

View File

@@ -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}
/>

View File

@@ -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';

View File

@@ -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}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
});
});
});

View File

@@ -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
}
];

View File

@@ -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;
}
});

View File

@@ -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: () => ({})
}
});
}
});

View File

@@ -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"]]
}
]

View File

@@ -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';

View File

@@ -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')),

View File

@@ -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: [

View File

@@ -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.

View File

@@ -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:

View File

@@ -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() {
});

View File

@@ -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 () {

View File

@@ -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() {

View File

@@ -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"
]
}

View File

@@ -1,7 +0,0 @@
'use strict';
require('ts-node').register({});
const { compileTypeScript, watchTypeScript } = require('@github/codeql-gulp-tasks');
exports.default = compileTypeScript;
exports.watchTypeScript = watchTypeScript;

View File

@@ -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"
}
}

View File

@@ -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));
}

View File

@@ -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