Compare commits

...

25 Commits

Author SHA1 Message Date
Andrew Eisenberg
b7b5a6ec30 v1.3.7
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-11-24 14:02:25 -08:00
Andrew Eisenberg
da9576fee0 Add workflow_dispatch to release workflow 2020-11-23 17:56:36 -08:00
Andrew Eisenberg
579df25be4 Use the ast edge label when building the ast node label
The C PrintAST library now includes the edge name in the AST Viewer
tree.
2020-11-23 15:27:24 -08:00
Andrew Eisenberg
1886c0c9ec Add a setting to control page size
Also, set a max and min value on the input control of the page. This
prevents going to a negative page, or a page after the last one.
2020-11-21 09:45:52 -08:00
Andrew Eisenberg
f48176bebf Re-sort databases list after db rename 2020-11-20 15:12:47 -08:00
Andrew Eisenberg
83f64fbdcd Avoid dependabot error 2020-11-19 14:02:21 -08:00
Andrew Eisenberg
a7bf5e60f3 Add debug flag for query server
And separate flag for IDE server. Setting these flags to `true` will
start the respective Java processes in debug mode so that they can
be attached to a debugger.
2020-11-18 15:55:24 -08:00
Andrew Eisenberg
e0cd041d98 Clean databases folder on startup (#675)
Cleans orphan databases on startup. This commit also bumps the fs-extra
dependency to get readdir with dirent objects.

Adds the `asyncFilter` to filter arrays asynchronously.
2020-11-16 16:32:05 +00:00
Andrew Eisenberg
4f76e9da60 Use the value not the label for the print ast node
Fixes #659
2020-11-13 09:45:30 -08:00
Andrew Eisenberg
966cc5af92 Add more structured output for tests
The diff and the errors were always available, but they were not being
sent to the output.

Additionally, make sure to send output to both the test explorer log and
the codeql test log.
2020-11-09 14:31:02 -08:00
Andrew Eisenberg
f4998d90e7 Creates an empty .expected file when running test output compare
If the expected file does not already exists. This helps with test
creation and allows users to create tests more quickly.
2020-11-09 14:27:20 -08:00
Andrew Eisenberg
245496c854 Remove QLPackDiscovery
We no longer rely on qlpacks for our ql test structure. For this reason,
we no longer need to do qlpack discovery.
2020-11-09 11:53:42 -08:00
Andrew Eisenberg
d553f6c069 Restructure the tree in the Test Explorer View
With this change we display the tree based on the file system not based
on ql-packs. We also merge test folders whose only child is another
test folder.

Resolves #595
2020-11-09 11:53:42 -08:00
Andrew Eisenberg
afd0694111 Update changelog 2020-11-09 11:53:42 -08:00
Andrew Eisenberg
32db9cdec6 Open editor containing query location in non-preview mode 2020-11-05 10:32:58 -08:00
github-actions[bot]
ad3cd7e7ac Bump version to v1.3.7 (#672)
Co-authored-by: aeisenberg <aeisenberg@users.noreply.github.com>
2020-11-04 14:09:37 -08:00
Andrew Eisenberg
e719c68321 Update the contributing docs
Just adds some more details.
2020-11-04 12:47:38 -08:00
Andrew Eisenberg
ce3b4ed43d v1.3.6 (#671)
Some checks failed
Code Scanning - CodeQL / codeql (push) Has been cancelled
Release / Release (push) Has been cancelled
2020-11-04 12:06:05 -08:00
Andrew Eisenberg
2953c15e5e Avoid recursive selection changes in ast viewer
This will prevent selections jumping around when an ast entry is
selected and its child has the same source location as the current
selection.
2020-11-04 07:14:39 -08:00
Andrew Eisenberg
b2b1021207 Disable codeql test commands from the command palette
These commands are not applicable from the global context. They require
an argument to be passed in. So, they should be hidden in the command
palette.
2020-11-03 15:52:00 -08:00
Andrew Eisenberg
9ddfd58a2b Adds interface-types and result-keys to pure
Will ensure that these files never have vscode dependencies.
2020-11-03 12:56:52 -08:00
Andrew Eisenberg
fe1476f875 Ensure uris are using encoded strings (#653)
This fixes a bug where if there are special characters in a database
path, it is not possible to navigate to that file from the results view.

Note that the results from our BQRS returned properly encoded URIs, but
our paths coming from sarif were unencoded. Our path parsing handled
the latter correctly (even though these are not correct URIs) and the
former incorrectly.

The fix here is to first ensure all uris are properly encoded. We do
this by running `encodeURI` in sarif-utils (can't run encodeURIComponent
or else the path separators `/` will also be encoded).

Then, we ensure that when we resolve locations, we decode all file
paths.

This works in all cases I have tried. I still have an issue with running
View AST on some of these databases, but that I believe is a separate
issue.
2020-11-03 18:06:44 +00:00
alexet
067a87a07c Results View: Fix display of booleans 2020-11-03 08:09:53 -05:00
Andrew Eisenberg
5133ee713f Add the assert-pure query
This query ensures that all of our files marked as "pure" remain that
way. In this case "pure" means that it does not depend on vscode and
can therefore be run in tests outside of a runtime environment.

This commit also explicitly moves all of our "pure" files to the
`src/pure` directory.
2020-11-02 18:40:45 -08:00
aeisenberg
2ac7881cf2 Bump version to v1.3.6 2020-10-27 12:56:22 -07:00
71 changed files with 994 additions and 499 deletions

12
.github/codeql/codeql-config.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: "CodeQL config"
queries:
- name: Run standard queries
uses: security-and-quality
- name: Run custom javascript queries
uses: ./.github/codeql/queries
paths:
- ./extensions/ql-vscode
paths-ignore:
- '**/node_modules'
- '**/build'
- '**/out'

21
.github/codeql/queries/assert-pure.ql vendored Normal file
View File

@@ -0,0 +1,21 @@
/**
* @name Unwanted dependency on vscode API
* @kind problem
* @problem.severity error
* @id vscode-codeql/assert-pure
* @description The modules stored under `pure` and tested in the `pure-tests`
* are intended to be "pure".
*/
import javascript
class VSCodeImport extends ASTNode {
VSCodeImport() {
this.(Import).getImportedPath().getValue() = "vscode"
}
}
from Module m, VSCodeImport v
where
m.getFile().getRelativePath().regexpMatch(".*src/pure/.*") and
m.getAnImportedModule*().getAnImport() = v
select m, "This module is not pure: it has a transitive dependency on the vscode API imported $@", v, "here"

3
.github/codeql/queries/qlpack.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
name: vscode-codeql-custom-queries-javascript
version: 0.0.0
libraryPathDependencies: codeql-javascript

View File

@@ -17,6 +17,9 @@ jobs:
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: javascript
config-file: ./.github/codeql/codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -6,20 +6,16 @@
name: Release
on:
push:
# Path filters are not evaluated for pushes to tags.
# (source: https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onpushpull_requestpaths)
# So this workflow is triggered in the following events:
# - Release event: a SemVer tag, e.g. v1.0.0 or v1.0.0-alpha, is pushed
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
# OR
# - Test event: this file is modified on a branch in the main repo containing `/actions/` in the name.
branches:
- '**/actions/**'
pull_request:
paths:
- '**/workflows/release.yml'
workflow_dispatch:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
jobs:
build:
name: Release

5
.vscode/launch.json vendored
View File

@@ -16,8 +16,9 @@
"${workspaceRoot}/extensions/ql-vscode/out/**/*.js",
],
"env": {
// uncomment to allow debugging the language server Java process from a remote java debugger
// "DEBUG_LANGUAGE_SERVER": "true"
// change to 'true' debug the IDE or Query servers
"IDE_SERVER_JAVA_DEBUG": "false",
"QUERY_SERVER_JAVA_DEBUG": "false",
}
},
{

View File

@@ -91,15 +91,28 @@ Alternatively, you can run the tests inside of vscode. There are several vscode
## Releasing (write access required)
1. Double-check the `CHANGELOG.md` contains all desired change comments
and has the version to be released with date at the top.
1. Double-check that the extension `package.json` has the version you intend to release.
If you are doing a patch release (as opposed to minor or major version) this should already
be correct.
1. Trigger a release build on Actions by adding a new tag on branch `main` of the format `vxx.xx.xx`
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
* Go through all recent PRs and make sure they are properly accounted for.
* Make sure all changelog entries have links back to their PR(s) if appropriate.
1. Double-check that the extension `package.json` has the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
1. Create a PR for this release:
* This PR will contain any missing bits from steps 1 and 2. Most of the time, this will just be updating `CHANGELOG.md` with today's date.
* Create a new branch for the release named after the new version. For example: `v1.3.6`
* Create a new commit with a message the same as the branch name.
* Create a PR for this branch.
* Wait for the PR to be merged into `main`
1. Trigger a release build on Actions by adding a new tag on branch `main` named after the release, as above. Note that when you push to upstream, you will need to fully qualify the ref. A command like this will work:
```bash
git push upstream refs/tags/v1.3.6
```
* **IMPORTANT** Make sure you are on the `main` branch and your local checkout is fully updated when you add the tag.
* If you accidentally add the tag to the wrong ref, you can just force push it to the right one later.
1. Monitor the status of the release build in the `Release` workflow in the Actions tab.
1. Download the VSIX from the draft GitHub release at the top of [the releases page](https://github.com/github/vscode-codeql/releases) that is created when the release build finishes.
1. Optionally unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
1. Unzip the `.vsix` and inspect its `package.json` to make sure the version is what you expect,
or look at the source if there's any doubt the right code is being shipped.
1. Log into the [Visual Studio Marketplace](https://marketplace.visualstudio.com/manage/publishers/github).
1. Click the `...` menu in the CodeQL row and click **Update**.

View File

@@ -1,8 +1,28 @@
# CodeQL for Visual Studio Code: Changelog
## 1.3.7 - 24 November 2020
- Editors opened by navigating from the results view are no longer opened in _preview mode_. Now they are opened as a persistent editor. [#630](https://github.com/github/vscode-codeql/pull/630)
- When comparing the results of a failed QL test run and the `.expected` file does not exist, an empty `.expected` file is created and compared against the `.actual` file. [#669](https://github.com/github/vscode-codeql/pull/669)
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the QL Packs. [#624](https://github.com/github/vscode-codeql/pull/624)
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the QL Packs. [#624](https://github.com/github/vscode-codeql/pull/624)
- Add more structured output for tests. [#626](https://github.com/github/vscode-codeql/pull/626)
- Whenever the extension restarts, orphaned databases will be cleaned up. These are databases whose files are located inside of the extension's storage area, but are not imported into the workspace.
- After renaming a database, the database list is re-sorted. [#685](https://github.com/github/vscode-codeql/pull/685)
- Add a `codeQl.resultsDisplay.pageSize` setting to configure the number of results displayed in a single results view page. Increase the default page size from 100 to 200. [#686](https://github.com/github/vscode-codeql/pull/686)
- Update the AST Viewer to include edge labels (if available) in addition to the target node labels. So far, only C/C++ databases take advantage of this change. [#688](https://github.com/github/vscode-codeql/pull/688)
## 1.3.6 - 4 November 2020
- Fix URI encoding for databases that were created with special characters in their paths. [#648](https://github.com/github/vscode-codeql/pull/648)
- Disable CodeQL Test commands from the command palette [#667](https://github.com/github/vscode-codeql/pull/667)
- Fix display of booleans in results view. [#657](https://github.com/github/vscode-codeql/pull/657)
- Avoid recursive selection changes in AST Viewer. [#668](https://github.com/github/vscode-codeql/pull/668)
## 1.3.5 - 27 October 2020
- Fix a bug where archived source folders for databases were not showing any contents.
- Fix URI encoding for databases that were created with special characters in their paths.
## 1.3.4 - 22 October 2020
@@ -19,6 +39,8 @@
- Add a `View DIL` command on query history items. This opens a text editor containing the Datalog Intermediary Language representation of the compiled query.
- Remove feature flag for the AST Viewer. For more information on how to use the AST Viewer, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode/procedures/exploring-the-structure-of-your-source-code.html).
- The `codeQL.runningTests.numberOfThreads` setting is now used correctly when running tests.
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the qlpacks.
- Ensure output of CodeQL test runs includes compilation error messages and test failure messages.
## 1.3.3 - 16 September 2020

View File

@@ -1,6 +1,6 @@
{
"name": "vscode-codeql",
"version": "1.3.5",
"version": "1.3.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -217,9 +217,9 @@
"dev": true
},
"@types/fs-extra": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz",
"integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==",
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.3.tgz",
"integrity": "sha512-NKdGoXLTFTRED3ENcfCsH8+ekV4gbsysanx2OPbstXVV6fZMgUCqTxubs6I9r7pbOJbFgVq1rpFtLURjKCZWUw==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -310,9 +310,9 @@
"dev": true
},
"@types/node": {
"version": "12.12.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.50.tgz",
"integrity": "sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w==",
"version": "12.19.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.4.tgz",
"integrity": "sha512-o3oj1bETk8kBwzz1WlO6JWL/AfAA3Vm6J1B3C9CsdxHYp7XgPiH7OEXPUbZTndHlRaIElrANkQfe6ZmfJb3H2w==",
"dev": true
},
"@types/node-fetch": {
@@ -1251,6 +1251,11 @@
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
},
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
@@ -3748,13 +3753,14 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
"jsonfile": "^6.0.1",
"universalify": "^1.0.0"
}
},
"fs-mkdirp-stream": {
@@ -5026,11 +5032,19 @@
"dev": true
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"requires": {
"graceful-fs": "^4.1.6"
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
},
"dependencies": {
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
}
}
},
"jsx-ast-utils": {
@@ -9001,9 +9015,9 @@
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="
},
"unset-value": {
"version": "1.0.0",
@@ -10016,6 +10030,16 @@
"requires": {
"ansi-regex": "^2.0.0"
}
},
"yargs-parser": {
"version": "5.0.0-security.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz",
"integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==",
"dev": true,
"requires": {
"camelcase": "^3.0.0",
"object.assign": "^4.1.0"
}
}
}
},

View File

@@ -4,7 +4,7 @@
"description": "CodeQL for Visual Studio Code",
"author": "GitHub",
"private": true,
"version": "1.3.5",
"version": "1.3.7",
"publisher": "GitHub",
"license": "MIT",
"icon": "media/VS-marketplace-CodeQL-icon.png",
@@ -157,6 +157,11 @@
"default": 20,
"description": "Max number of simultaneous queries to run using the 'CodeQL: Run Queries' command."
},
"codeQL.resultsDisplay.pageSize": {
"type": "integer",
"default": 200,
"description": "Number of queries displayed per page of the results view."
},
"codeQL.queryHistory.format": {
"type": "string",
"default": "[%t] %q on %d - %s",
@@ -197,6 +202,10 @@
"dark": "media/dark/folder-opened-plus.svg"
}
},
{
"command": "codeQLDatabases.removeOrphanedDatabases",
"title": "Delete unused databases"
},
{
"command": "codeQLDatabases.chooseDatabaseArchive",
"title": "Choose Database from Archive",
@@ -355,11 +364,11 @@
},
{
"command": "codeQLTests.showOutputDifferences",
"title": "CodeQL: Show Test Output Differences"
"title": "Show Test Output Differences"
},
{
"command": "codeQLTests.acceptOutput",
"title": "CodeQL: Accept Test Output"
"title": "Accept Test Output"
},
{
"command": "codeQLAstViewer.gotoCode",
@@ -573,6 +582,10 @@
"command": "codeQLDatabases.chooseDatabaseArchive",
"when": "false"
},
{
"command": "codeQLDatabases.removeOrphanedDatabases",
"when": "false"
},
{
"command": "codeQLDatabases.chooseDatabaseInternet",
"when": "false"
@@ -628,6 +641,14 @@
{
"command": "codeQLAstViewer.clear",
"when": "false"
},
{
"command": "codeQLTests.acceptOutput",
"when": "false"
},
{
"command": "codeQLTests.showOutputDifferences",
"when": "false"
}
],
"editor/context": [
@@ -696,7 +717,7 @@
"dependencies": {
"child-process-promise": "^2.2.1",
"classnames": "~2.2.6",
"fs-extra": "^8.1.0",
"fs-extra": "^9.0.1",
"glob-promise": "^3.4.0",
"js-yaml": "^3.14.0",
"minimist": "~1.2.5",
@@ -719,7 +740,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/fs-extra": "^9.0.3",
"@types/glob": "^7.1.1",
"@types/google-protobuf": "^3.2.7",
"@types/gulp": "^4.0.6",
@@ -727,7 +748,7 @@
"@types/js-yaml": "^3.12.5",
"@types/jszip": "~3.1.6",
"@types/mocha": "~8.0.3",
"@types/node": "^12.0.8",
"@types/node": "^12.14.1",
"@types/node-fetch": "~2.5.2",
"@types/proxyquire": "~1.3.28",
"@types/react": "^16.8.17",

View File

@@ -84,7 +84,7 @@ export function encodeSourceArchiveUri(ref: ZipFileReference): vscode.Uri {
// This lets us separate the paths, ignoring the leading slash if we added one.
const sourceArchiveZipPathEndIndex = sourceArchiveZipPathStartIndex + sourceArchiveZipPath.length;
const authority = `${sourceArchiveZipPathStartIndex}-${sourceArchiveZipPathEndIndex}`;
return vscode.Uri.parse(zipArchiveScheme + ':/').with({
return vscode.Uri.parse(zipArchiveScheme + ':/', true).with({
path: encodedPath,
authority,
});

View File

@@ -8,15 +8,16 @@ import {
TreeItem,
TreeView,
TextEditorSelectionChangeEvent,
TextEditorSelectionChangeKind,
Location,
Range
} from 'vscode';
import * as path from 'path';
import { DatabaseItem } from './databases';
import { UrlValue, BqrsId } from './bqrs-cli-types';
import { UrlValue, BqrsId } from './pure/bqrs-cli-types';
import { showLocation } from './interface-utils';
import { isStringLoc, isWholeFileLoc, isLineColumnLoc } from './bqrs-utils';
import { isStringLoc, isWholeFileLoc, isLineColumnLoc } from './pure/bqrs-utils';
import { commandRunner } from './helpers';
import { DisposableObject } from './vscode-utils/disposable-object';
@@ -33,7 +34,7 @@ export interface ChildAstItem extends AstItem {
parent: ChildAstItem | AstItem;
}
class AstViewerDataProvider extends DisposableObject implements TreeDataProvider<AstItem> {
class AstViewerDataProvider extends DisposableObject implements TreeDataProvider<AstItem> {
public roots: AstItem[] = [];
public db: DatabaseItem | undefined;
@@ -47,9 +48,9 @@ class AstViewerDataProvider extends DisposableObject implements TreeDataProvide
super();
this.push(
commandRunner('codeQLAstViewer.gotoCode',
async (item: AstItem) => {
await showLocation(item.fileLocation);
})
async (item: AstItem) => {
await showLocation(item.fileLocation);
})
);
}
@@ -160,6 +161,11 @@ export class AstViewer extends DisposableObject {
return;
}
// Avoid recursive tree-source code updates.
if (e.kind === TextEditorSelectionChangeKind.Command) {
return;
}
if (
this.treeView.visible &&
e.textEditor.document.uri.fsPath === this.currentFile &&

View File

@@ -11,12 +11,13 @@ import * as tk from 'tree-kill';
import { promisify } from 'util';
import { CancellationToken, Disposable } from 'vscode';
import { BQRSInfo, DecodedBqrsChunk } from './bqrs-cli-types';
import { BQRSInfo, DecodedBqrsChunk } from './pure/bqrs-cli-types';
import { CliConfig } from './config';
import { DistributionProvider, FindDistributionResultKind } from './distribution';
import { assertNever } from './helpers-pure';
import { QueryMetadata, SortDirection } from './interface-types';
import { assertNever } from './pure/helpers-pure';
import { QueryMetadata, SortDirection } from './pure/interface-types';
import { Logger, ProgressReporter } from './logging';
import { CompilationMessage } from './pure/messages';
/**
* The version of the SARIF format that we are using.
@@ -90,10 +91,11 @@ export interface TestRunOptions {
export interface TestCompleted {
test: string;
pass: boolean;
messages: string[];
messages: CompilationMessage[];
compilationMs: number;
evaluationMs: number;
expected: string;
diff: string[] | undefined;
}
/**
@@ -470,7 +472,11 @@ export class CodeQLCliServer implements Disposable {
const subcommandArgs = [
testPath
];
return await this.runJsonCodeQlCliCommand<ResolvedTests>(['resolve', 'tests'], subcommandArgs, 'Resolving tests');
return await this.runJsonCodeQlCliCommand<ResolvedTests>(
['resolve', 'tests', '--strict-test-discovery'],
subcommandArgs,
'Resolving tests'
);
}
/**
@@ -702,7 +708,7 @@ export class CodeQLCliServer implements Disposable {
private async refreshVersion() {
const distribution = await this.distributionProvider.getDistribution();
switch(distribution.kind) {
switch (distribution.kind) {
case FindDistributionResultKind.CompatibleDistribution:
// eslint-disable-next-line no-fallthrough
case FindDistributionResultKind.IncompatibleDistribution:
@@ -901,3 +907,16 @@ async function logStream(stream: Readable, logger: Logger): Promise<void> {
logger.log(line);
}
}
export function shouldDebugIdeServer() {
return 'IDE_SERVER_JAVA_DEBUG' in process.env
&& process.env.IDE_SERVER_JAVA_DEBUG !== '0'
&& process.env.IDE_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';
}
export function shouldDebugQueryServer() {
return 'QUERY_SERVER_JAVA_DEBUG' in process.env
&& process.env.QUERY_SERVER_JAVA_DEBUG !== '0'
&& process.env.QUERY_SERVER_JAVA_DEBUG?.toLocaleLowerCase() !== 'false';
}

View File

@@ -14,12 +14,12 @@ import {
FromCompareViewMessage,
ToCompareViewMessage,
QueryCompareResult,
} from '../interface-types';
} from '../pure/interface-types';
import { Logger } from '../logging';
import { CodeQLCliServer } from '../cli';
import { DatabaseManager } from '../databases';
import { getHtmlForWebview, jumpToLocation } from '../interface-utils';
import { transformBqrsResultSet, RawResultSet, BQRSInfo } from '../bqrs-cli-types';
import { transformBqrsResultSet, RawResultSet, BQRSInfo } from '../pure/bqrs-cli-types';
import resultsDiff from './resultsDiff';
interface ComparePair {

View File

@@ -1,5 +1,5 @@
import { RawResultSet } from '../bqrs-cli-types';
import { QueryCompareResult } from '../interface-types';
import { RawResultSet } from '../pure/bqrs-cli-types';
import { QueryCompareResult } from '../pure/interface-types';
/**
* Compare the rows of two queries. Use deep equality to determine if

View File

@@ -5,7 +5,7 @@ import * as Rdom from 'react-dom';
import {
ToCompareViewMessage,
SetComparisonsMessage,
} from '../../interface-types';
} from '../../pure/interface-types';
import CompareSelector from './CompareSelector';
import { vscode } from '../../view/vscode-api';
import CompareTable from './CompareTable';

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import { SetComparisonsMessage } from '../../interface-types';
import { SetComparisonsMessage } from '../../pure/interface-types';
import RawTableHeader from '../../view/RawTableHeader';
import { className } from '../../view/result-table-utils';
import { ResultRow } from '../../bqrs-cli-types';
import { ResultRow } from '../../pure/bqrs-cli-types';
import RawTableRow from '../../view/RawTableRow';
import { vscode } from '../../view/vscode-api';

View File

@@ -68,10 +68,12 @@ const TIMEOUT_SETTING = new Setting('timeout', RUNNING_QUERIES_SETTING);
const MEMORY_SETTING = new Setting('memory', RUNNING_QUERIES_SETTING);
const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING);
const RUNNING_TESTS_SETTING = new Setting('runningTests', ROOT_SETTING);
const RESULTS_DISPLAY_SETTING = new Setting('resultsDisplay', ROOT_SETTING);
export const NUMBER_OF_TEST_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_TESTS_SETTING);
export const MAX_QUERIES = new Setting('maxQueries', RUNNING_QUERIES_SETTING);
export const AUTOSAVE_SETTING = new Setting('autoSave', RUNNING_QUERIES_SETTING);
export const PAGE_SIZE = new Setting('pageSize', RESULTS_DISPLAY_SETTING);
/** When these settings change, the running query server should be restarted. */
const QUERY_SERVER_RESTARTING_SETTINGS = [NUMBER_OF_THREADS_SETTING, MEMORY_SETTING, DEBUG_SETTING];

View File

@@ -1,6 +1,6 @@
import { QueryWithResults } from '../run-queries';
import { CodeQLCliServer } from '../cli';
import { DecodedBqrsChunk, BqrsId, EntityValue } from '../bqrs-cli-types';
import { DecodedBqrsChunk, BqrsId, EntityValue } from '../pure/bqrs-cli-types';
import { DatabaseItem } from '../databases';
import { ChildAstItem, AstItem } from '../astViewer';
import fileRangeFromURI from './fileRangeFromURI';
@@ -45,17 +45,18 @@ export default class AstBuilder {
const parentToChildren = new Map<BqrsId, BqrsId[]>();
const childToParent = new Map<BqrsId, BqrsId>();
const astOrder = new Map<BqrsId, number>();
const edgeLabels = new Map<BqrsId, string>();
const roots = [];
// Build up the parent-child relationships
edgeTuples.tuples.forEach(tuple => {
const [source, target, tupleType, orderValue] = tuple as [EntityValue, EntityValue, string, string];
const [source, target, tupleType, value] = tuple as [EntityValue, EntityValue, string, string];
const sourceId = source.id!;
const targetId = target.id!;
switch (tupleType) {
case 'semmle.order':
astOrder.set(targetId, Number(orderValue));
astOrder.set(targetId, Number(value));
break;
case 'semmle.label': {
@@ -65,6 +66,11 @@ export default class AstBuilder {
parentToChildren.set(sourceId, children = []);
}
children.push(targetId);
// ignore values that indicate a numeric order.
if (!Number.isFinite(Number(value))) {
edgeLabels.set(targetId, value);
}
break;
}
@@ -75,18 +81,22 @@ export default class AstBuilder {
// populate parents and children
nodeTuples.tuples.forEach(tuple => {
const [entity, tupleType, orderValue] = tuple as [EntityValue, string, string];
const [entity, tupleType, value] = tuple as [EntityValue, string, string];
const id = entity.id!;
switch (tupleType) {
case 'semmle.order':
astOrder.set(id, Number(orderValue));
astOrder.set(id, Number(value));
break;
case 'semmle.label': {
// If an edge label exists, include it and separate from the node label using ':'
const nodeLabel = value ?? entity.label;
const edgeLabel = edgeLabels.get(id);
const label = [edgeLabel, nodeLabel].filter(e => e).join(': ');
const item = {
id,
label: entity.label,
label,
location: entity.url,
fileLocation: fileRangeFromURI(entity.url, this.db),
children: [] as ChildAstItem[],

View File

@@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import { UrlValue, LineColumnLocation } from '../bqrs-cli-types';
import { isEmptyPath } from '../bqrs-utils';
import { UrlValue, LineColumnLocation } from '../pure/bqrs-cli-types';
import { isEmptyPath } from '../pure/bqrs-utils';
import { DatabaseItem } from '../databases';
@@ -20,9 +20,8 @@ export default function fileRangeFromURI(uri: UrlValue | undefined, db: Database
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);
if (uri.uri.startsWith('file:')) {
return new vscode.Location(db.resolveSourceFile(uri.uri), range);
}
return undefined;
} catch (e) {

View File

@@ -1,11 +1,11 @@
import * as vscode from 'vscode';
import { decodeSourceArchiveUri, encodeArchiveBasePath } from '../archive-filesystem-provider';
import { ColumnKindCode, EntityValue, getResultSetSchema, ResultSetSchema } from '../bqrs-cli-types';
import { ColumnKindCode, EntityValue, getResultSetSchema, ResultSetSchema } from '../pure/bqrs-cli-types';
import { CodeQLCliServer } from '../cli';
import { DatabaseManager, DatabaseItem } from '../databases';
import fileRangeFromURI from './fileRangeFromURI';
import * as messages from '../messages';
import * as messages from '../pure/messages';
import { QueryServerClient } from '../queryserver-client';
import { QueryWithResults, compileAndRunQueryAgainstDatabase } from '../run-queries';
import { ProgressCallback } from '../helpers';
@@ -43,7 +43,7 @@ export async function getLocationsForUriString(
token: vscode.CancellationToken,
filter: (src: string, dest: string) => boolean
): Promise<FullLocationLink[]> {
const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString));
const uri = decodeSourceArchiveUri(vscode.Uri.parse(uriString, true));
const sourceArchiveUri = encodeArchiveBasePath(uri.sourceArchiveZipPath);
const db = dbm.findDatabaseItemBySourceArchive(sourceArchiveUri);

View File

@@ -4,7 +4,7 @@ import { decodeSourceArchiveUri, encodeArchiveBasePath, zipArchiveScheme } from
import { CodeQLCliServer } from '../cli';
import { DatabaseManager } from '../databases';
import { CachedOperation, ProgressCallback, withProgress } from '../helpers';
import * as messages from '../messages';
import * as messages from '../pure/messages';
import { QueryServerClient } from '../queryserver-client';
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from '../run-queries';
import AstBuilder from './astBuilder';
@@ -136,7 +136,7 @@ export class TemplatePrintAstProvider {
return new AstBuilder(
queryResults, this.cli,
this.dbm.findDatabaseItem(vscode.Uri.parse(queryResults.database.databaseUri!))!,
this.dbm.findDatabaseItem(vscode.Uri.parse(queryResults.database.databaseUri!, true))!,
document.fileName
);
}

View File

@@ -8,7 +8,7 @@ import {
TreeItem,
Uri,
window,
env
env,
} from 'vscode';
import * as fs from 'fs-extra';
@@ -18,6 +18,8 @@ import {
DatabaseItem,
DatabaseManager,
getUpgradesDirectories,
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,
} from './databases';
import {
commandRunner,
@@ -35,7 +37,8 @@ import {
promptImportInternetDatabase,
promptImportLgtmDatabase,
} from './databaseFetcher';
import { CancellationToken } from 'vscode-jsonrpc';
import { CancellationToken } from 'vscode';
import { asyncFilter } from './pure/helpers-pure';
type ThemableIconPath = { light: string; dark: string } | string;
@@ -229,7 +232,9 @@ export class DatabaseUI extends DisposableObject {
canSelectMany: true,
})
);
}
init() {
logger.log('Registering database panel commands.');
this.push(
commandRunnerWithProgress(
@@ -340,6 +345,12 @@ export class DatabaseUI extends DisposableObject {
this.handleOpenFolder
)
);
this.push(
commandRunner(
'codeQLDatabases.removeOrphanedDatabases',
this.handleRemoveOrphanedDatabases
)
);
}
private handleMakeCurrentDatabase = async (
@@ -360,6 +371,53 @@ export class DatabaseUI extends DisposableObject {
}
};
handleRemoveOrphanedDatabases = async (): Promise<void> => {
logger.log('Removing orphaned databases from workspace storage.');
let dbDirs =
// read directory
(await fs.readdir(this.storagePath, { withFileTypes: true }))
// remove non-directories
.filter(dirent => dirent.isDirectory())
// get the full path
.map(dirent => path.join(this.storagePath, dirent.name))
// remove databases still in workspace
.filter(dbDir => {
const dbUri = Uri.file(dbDir);
return this.databaseManager.databaseItems.every(item => item.databaseUri.fsPath !== dbUri.fsPath);
});
// remove non-databases
dbDirs = await asyncFilter(dbDirs, isLikelyDatabaseRoot);
if (!dbDirs.length) {
logger.log('No orphaned databases found.');
return;
}
// delete
const failures = [] as string[];
await Promise.all(
dbDirs.map(async dbDir => {
try {
logger.log(`Deleting orphaned database '${dbDir}'.`);
await fs.rmdir(dbDir, { recursive: true } as any); // typings doesn't recognize the options argument
} catch (e) {
failures.push(`${path.basename(dbDir)}`);
}
})
);
if (failures.length) {
const dirname = path.dirname(failures[0]);
showAndLogErrorMessage(
`Failed to delete unused databases:\n ${
failures.join('\n ')
}\n. To delete unused databases, please remove them manually from the storage folder ${dirname}.`
);
}
};
handleChooseDatabaseArchive = async (
progress: ProgressCallback,
token: CancellationToken
@@ -653,7 +711,7 @@ export class DatabaseUI extends DisposableObject {
dbPath = path.dirname(dbPath);
}
if (isLikelyDbFolder(dbPath)) {
if (isLikelyDbLanguageFolder(dbPath)) {
dbPath = path.dirname(dbPath);
}
return Uri.file(dbPath);
@@ -668,9 +726,3 @@ export class DatabaseUI extends DisposableObject {
}
}
}
// TODO: Get the list of supported languages from a list that will be auto-updated.
const dbRegeEx = /^db-(javascript|go|cpp|java|python|csharp)$/;
function isLikelyDbFolder(dbPath: string) {
return path.basename(dbPath).match(dbRegeEx);
}

View File

@@ -341,28 +341,29 @@ export class DatabaseItemImpl implements DatabaseItem {
}
}
public resolveSourceFile(uri: string | undefined): vscode.Uri {
public resolveSourceFile(uriStr: string | undefined): vscode.Uri {
const sourceArchive = this.sourceArchive;
// Sometimes, we are passed a path, sometimes a file URI.
// We need to convert this to a file path that is probably inside of a zip file.
const file = uri?.replace(/file:/, '');
const uri = uriStr ? vscode.Uri.parse(uriStr, true) : undefined;
if (uri && uri.scheme !== 'file') {
throw new Error(`Invalid uri scheme in ${uriStr}. Only 'file' is allowed.`);
}
if (!sourceArchive) {
if (file) {
// Treat it as an absolute path.
return vscode.Uri.file(file);
if (uri) {
return uri;
} else {
return this.databaseUri;
}
}
if (file) {
const absoluteFilePath = file.replace(':', '_');
// Strip any leading slashes from the file path, and replace `:` with `_`.
const relativeFilePath = absoluteFilePath.replace(/^\/*/, '').replace(':', '_');
if (uri) {
const relativeFilePath = decodeURI(uri.path).replace(':', '_').replace(/^\/*/, '');
if (sourceArchive.scheme === zipArchiveScheme) {
const zipRef = decodeSourceArchiveUri(sourceArchive);
const pathWithinSourceArchive = zipRef.pathWithinSourceArchive === '/'
? relativeFilePath
: zipRef.pathWithinSourceArchive + '/' + relativeFilePath;
return encodeSourceArchiveUri({
pathWithinSourceArchive: zipRef.pathWithinSourceArchive + '/' + absoluteFilePath,
pathWithinSourceArchive,
sourceArchiveZipPath: zipRef.sourceArchiveZipPath,
});
@@ -396,10 +397,7 @@ export class DatabaseItemImpl implements DatabaseItem {
* Holds if the database item refers to an exported snapshot
*/
public async hasMetadataFile(): Promise<boolean> {
return (await Promise.all([
fs.pathExists(path.join(this.databaseUri.fsPath, '.dbinfo')),
fs.pathExists(path.join(this.databaseUri.fsPath, 'codeql-database.yml'))
])).some(x => x);
return await isLikelyDatabaseRoot(this.databaseUri.fsPath);
}
/**
@@ -579,7 +577,7 @@ export class DatabaseManager extends DisposableObject {
displayName,
dateAdded
};
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri), undefined, fullOptions,
const item = new DatabaseItemImpl(vscode.Uri.parse(state.uri, true), undefined, fullOptions,
(event) => {
this._onDidChangeDatabaseItem.fire(event);
});
@@ -669,7 +667,8 @@ export class DatabaseManager extends DisposableObject {
item.name = newName;
this.updatePersistedDatabaseList();
this._onDidChangeDatabaseItem.fire({
item,
// pass undefined so that the entire tree is rebuilt in order to re-sort
item: undefined,
kind: DatabaseEventKind.Rename
});
}
@@ -729,3 +728,23 @@ export function getUpgradesDirectories(scripts: string[]): vscode.Uri[] {
const uniqueParentDirs = new Set(parentDirs);
return Array.from(uniqueParentDirs).map(filePath => vscode.Uri.file(filePath));
}
// TODO: Get the list of supported languages from a list that will be auto-updated.
export async function isLikelyDatabaseRoot(fsPath: string) {
const [a, b, c] = (await Promise.all([
// databases can have either .dbinfo or codeql-database.yml.
fs.pathExists(path.join(fsPath, '.dbinfo')),
fs.pathExists(path.join(fsPath, 'codeql-database.yml')),
// they *must* have a db-language folder
(await fs.readdir(fsPath)).some(isLikelyDbLanguageFolder)
]));
return (a || b) && c;
}
export function isLikelyDbLanguageFolder(dbPath: string) {
return !!path.basename(dbPath).startsWith('db-');
}

View File

@@ -45,7 +45,7 @@ import {
GithubRateLimitedError
} from './distribution';
import * as helpers from './helpers';
import { assertNever } from './helpers-pure';
import { assertNever } from './pure/helpers-pure';
import { spawnIdeServer } from './ide-server';
import { InterfaceManager } from './interface';
import { WebviewReveal } from './interface-utils';
@@ -58,7 +58,7 @@ import { compileAndRunQueryAgainstDatabase, tmpDirDisposal } from './run-queries
import { QLTestAdapterFactory } from './test-adapter';
import { TestUIService } from './test-ui';
import { CompareInterfaceManager } from './compare/compare-interface';
import { gatherQlFiles } from './files';
import { gatherQlFiles } from './pure/files';
/**
* extension.ts
@@ -349,6 +349,7 @@ async function activateWithInstalledDistribution(
getContextStoragePath(ctx),
ctx.extensionPath
);
databaseUI.init();
ctx.subscriptions.push(databaseUI);
logger.log('Initializing query history manager.');
@@ -643,6 +644,8 @@ async function activateWithInstalledDistribution(
title: 'Calculate AST'
}));
commands.executeCommand('codeQLDatabases.removeOrphanedDatabases');
logger.log('Successfully finished extension initialization.');
}

View File

@@ -12,7 +12,7 @@ import { ideServerLogger } from './logging';
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()) {
if (cli.shouldDebugIdeServer()) {
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9009,server=y,suspend=n,quiet=y');
}
const child = cli.spawnServer(
@@ -28,9 +28,3 @@ 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

@@ -15,11 +15,16 @@ import {
import {
tryGetResolvableLocation,
isLineColumnLoc
} from './bqrs-utils';
} from './pure/bqrs-utils';
import { DatabaseItem, DatabaseManager } from './databases';
import { ViewSourceFileMsg } from './interface-types';
import { ViewSourceFileMsg } from './pure/interface-types';
import { Logger } from './logging';
import { LineColumnLocation, WholeFileLocation, UrlValue, ResolvableLocationValue } from './bqrs-cli-types';
import {
LineColumnLocation,
WholeFileLocation,
UrlValue,
ResolvableLocationValue
} from './pure/bqrs-cli-types';
/**
* This module contains functions and types that are sharedd between
@@ -162,7 +167,13 @@ export async function showLocation(location?: Location) {
const editor =
editorsWithDoc.length > 0
? editorsWithDoc[0]
: await Window.showTextDocument(doc, ViewColumn.One);
: await Window.showTextDocument(
doc, {
// avoid preview mode so editor is sticky and will be added to navigation and search histories.
preview: false,
viewColumn: ViewColumn.One,
});
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.

View File

@@ -15,7 +15,7 @@ import * as cli from './cli';
import { CodeQLCliServer } from './cli';
import { DatabaseEventKind, DatabaseItem, DatabaseManager } from './databases';
import { showAndLogErrorMessage } from './helpers';
import { assertNever } from './helpers-pure';
import { assertNever } from './pure/helpers-pure';
import {
FromResultsViewMsg,
Interpretation,
@@ -26,17 +26,15 @@ import {
SortedResultsMap,
InterpretedResultsSortState,
SortDirection,
RAW_RESULTS_PAGE_SIZE,
INTERPRETED_RESULTS_PAGE_SIZE,
ALERTS_TABLE_NAME,
RawResultsSortState,
} from './interface-types';
} from './pure/interface-types';
import { Logger } from './logging';
import { commandRunner } from './helpers';
import * as messages from './messages';
import * as messages from './pure/messages';
import { CompletedQuery, interpretResults } from './query-results';
import { QueryInfo, tmpDir } from './run-queries';
import { parseSarifLocation, parseSarifPlainTextMessage } from './sarif-utils';
import { parseSarifLocation, parseSarifPlainTextMessage } from './pure/sarif-utils';
import {
WebviewReveal,
fileUriToWebviewUri,
@@ -46,8 +44,9 @@ import {
shownLocationLineDecoration,
jumpToLocation,
} from './interface-utils';
import { getDefaultResultSetName, ParsedResultSets } from './interface-types';
import { RawResultSet, transformBqrsResultSet, ResultSetSchema } from './bqrs-cli-types';
import { getDefaultResultSetName, ParsedResultSets } from './pure/interface-types';
import { RawResultSet, transformBqrsResultSet, ResultSetSchema } from './pure/bqrs-cli-types';
import { PAGE_SIZE } from './config';
/**
* interface.ts
@@ -89,11 +88,11 @@ function sortInterpretedResults(
}
function numPagesOfResultSet(resultSet: RawResultSet): number {
return Math.ceil(resultSet.schema.rows / RAW_RESULTS_PAGE_SIZE);
return Math.ceil(resultSet.schema.rows / PAGE_SIZE.getValue<number>());
}
function numInterpretedPages(interpretation: Interpretation | undefined): number {
return Math.ceil((interpretation?.sarif.runs[0].results?.length || 0) / INTERPRETED_RESULTS_PAGE_SIZE);
return Math.ceil((interpretation?.sarif.runs[0].results?.length || 0) / PAGE_SIZE.getValue<number>());
}
export class InterfaceManager extends DisposableObject {
@@ -378,7 +377,7 @@ export class InterfaceManager extends DisposableObject {
// Use sorted results path if it exists. This may happen if we are
// reloading the results view after it has been sorted in the past.
const resultsPath = results.getResultsPath(selectedTable);
const pageSize = PAGE_SIZE.getValue<number>();
const chunk = await this.cliServer.bqrsDecode(
resultsPath,
schema.name,
@@ -388,12 +387,13 @@ export class InterfaceManager extends DisposableObject {
// if there are interpreted results, but speculatively
// send anyway.
offset: schema.pagination?.offsets[0],
pageSize: RAW_RESULTS_PAGE_SIZE
pageSize
}
);
const resultSet = transformBqrsResultSet(schema, chunk);
const parsedResultSets: ParsedResultSets = {
pageNumber: 0,
pageSize,
numPages: numPagesOfResultSet(resultSet),
numInterpretedPages: numInterpretedPages(this._interpretation),
resultSet: { ...resultSet, t: 'RawResultSet' },
@@ -442,6 +442,7 @@ export class InterfaceManager extends DisposableObject {
metadata: this._displayedQuery.query.metadata,
pageNumber,
resultSetNames,
pageSize: PAGE_SIZE.getValue(),
numPages: numInterpretedPages(this._interpretation),
});
}
@@ -450,7 +451,7 @@ export class InterfaceManager extends DisposableObject {
const resultsPath = results.getResultsPath(selectedTable);
const schemas = await this.cliServer.bqrsInfo(
resultsPath,
RAW_RESULTS_PAGE_SIZE
PAGE_SIZE.getValue()
);
return schemas['result-sets'];
}
@@ -483,18 +484,20 @@ export class InterfaceManager extends DisposableObject {
if (schema === undefined)
throw new Error(`Query result set '${selectedTable}' not found.`);
const pageSize = PAGE_SIZE.getValue<number>();
const chunk = await this.cliServer.bqrsDecode(
results.getResultsPath(selectedTable, sorted),
schema.name,
{
offset: schema.pagination?.offsets[pageNumber],
pageSize: RAW_RESULTS_PAGE_SIZE
pageSize
}
);
const resultSet = transformBqrsResultSet(schema, chunk);
const parsedResultSets: ParsedResultSets = {
pageNumber,
pageSize,
resultSet: { t: 'RawResultSet', ...resultSet },
numPages: numPagesOfResultSet(resultSet),
numInterpretedPages: numInterpretedPages(this._interpretation),
@@ -559,8 +562,8 @@ export class InterfaceManager extends DisposableObject {
function getPageOfRun(run: Sarif.Run): Sarif.Run {
return {
...run, results: run.results?.slice(
INTERPRETED_RESULTS_PAGE_SIZE * pageNumber,
INTERPRETED_RESULTS_PAGE_SIZE * (pageNumber + 1)
PAGE_SIZE.getValue<number>() * pageNumber,
PAGE_SIZE.getValue<number>() * (pageNumber + 1)
)
};
}

View File

@@ -77,10 +77,10 @@ export interface WholeFileLocation {
endColumn: never;
}
export type UrlValue = LineColumnLocation | WholeFileLocation | string;
export type ResolvableLocationValue = WholeFileLocation | LineColumnLocation;
export type UrlValue = ResolvableLocationValue | string;
export type ColumnValue = EntityValue | number | string | boolean;
export type ResultRow = ColumnValue[];

View File

@@ -1,4 +1,9 @@
import { UrlValue, ResolvableLocationValue, LineColumnLocation, WholeFileLocation } from './bqrs-cli-types';
import {
UrlValue,
ResolvableLocationValue,
LineColumnLocation,
WholeFileLocation
} from './bqrs-cli-types';
/**
* The CodeQL filesystem libraries use this pattern in `getURL()` predicates

View File

@@ -21,3 +21,11 @@ class ExhaustivityCheckingError extends Error {
export function assertNever(value: never): never {
throw new ExhaustivityCheckingError(value);
}
/**
* Use to perform array filters where the predicate is asynchronous.
*/
export const asyncFilter = async function <T>(arr: T[], predicate: (arg0: T) => Promise<boolean>) {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_, index) => results[index]);
};

View File

@@ -23,16 +23,6 @@ export type ResultSet = RawTableResultSet | PathTableResultSet;
*/
export const RAW_RESULTS_LIMIT = 10000;
/**
* Show this many rows in a raw result table at a time.
*/
export const RAW_RESULTS_PAGE_SIZE = 100;
/**
* Show this many rows in an interpreted results table at a time.
*/
export const INTERPRETED_RESULTS_PAGE_SIZE = 100;
export interface DatabaseInfo {
name: string;
databaseUri: string;
@@ -124,6 +114,7 @@ export interface ShowInterpretedPageMsg {
metadata?: QueryMetadata;
pageNumber: number;
numPages: number;
pageSize: number;
resultSetNames: string[];
}
@@ -352,6 +343,7 @@ export function getDefaultResultSetName(
export interface ParsedResultSets {
pageNumber: number;
pageSize: number;
numPages: number;
numInterpretedPages: number;
selectedTable?: string; // when undefined, means 'show default table'

View File

@@ -72,10 +72,28 @@ export function getPathRelativeToSourceLocationPrefix(
sourceLocationPrefix: string,
sarifRelativeUri: string
) {
const normalizedSourceLocationPrefix = sourceLocationPrefix.replace(/\\/g, '/');
return `file:${normalizedSourceLocationPrefix}/${sarifRelativeUri}`;
// convert a platform specific path into encoded path uri segments
// need to be careful about drive letters and ensure that there
// is a starting '/'
let prefix = '';
if (sourceLocationPrefix[1] === ':') {
// assume this is a windows drive separator
prefix = sourceLocationPrefix.substring(0, 2);
sourceLocationPrefix = sourceLocationPrefix.substring(2);
}
const normalizedSourceLocationPrefix = prefix + sourceLocationPrefix.replace(/\\/g, '/')
.split('/')
.map(encodeURIComponent)
.join('/');
const slashPrefix = normalizedSourceLocationPrefix.startsWith('/') ? '' : '/';
return `file:${slashPrefix + normalizedSourceLocationPrefix}/${sarifRelativeUri}`;
}
/**
*
* @param loc specifies the database-relative location of the source location
* @param sourceLocationPrefix a file path (usually a full path) to the database containing the source location.
*/
export function parseSarifLocation(
loc: Sarif.Location,
sourceLocationPrefix: string

View File

@@ -1,58 +0,0 @@
import { EventEmitter, Event, Uri, WorkspaceFolder, RelativePattern } from 'vscode';
import { MultiFileSystemWatcher } from './vscode-utils/multi-file-system-watcher';
import { CodeQLCliServer, QlpacksInfo } from './cli';
import { Discovery } from './discovery';
export interface QLPack {
name: string;
uri: Uri;
}
/**
* Service to discover all available QL packs in a workspace folder.
*/
export class QLPackDiscovery extends Discovery<QlpacksInfo> {
private readonly _onDidChangeQLPacks = this.push(new EventEmitter<void>());
private readonly watcher = this.push(new MultiFileSystemWatcher());
private _qlPacks: readonly QLPack[] = [];
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));
}
public get onDidChangeQLPacks(): Event<void> { return this._onDidChangeQLPacks.event; }
public get qlPacks(): readonly QLPack[] { return this._qlPacks; }
private handleQLPackFileChanged(_uri: Uri): void {
this.refresh();
}
protected discover(): Promise<QlpacksInfo> {
// Only look for QL packs in this workspace folder.
return this.cliServer.resolveQlpacks([this.workspaceFolder.uri.fsPath], []);
}
protected update(results: QlpacksInfo): void {
const qlPacks: QLPack[] = [];
for (const id in results) {
qlPacks.push(...results[id].map(fsPath => {
return {
name: id,
uri: Uri.file(fsPath)
};
}));
}
this._qlPacks = qlPacks;
this._onDidChangeQLPacks.fire();
}
}

View File

@@ -1,7 +1,6 @@
import * as path from 'path';
import { QLPackDiscovery, QLPack } from './qlpack-discovery';
import { Discovery } from './discovery';
import { EventEmitter, Event, Uri, RelativePattern, WorkspaceFolder, env, workspace } from 'vscode';
import { EventEmitter, Event, Uri, RelativePattern, WorkspaceFolder, env } from 'vscode';
import { MultiFileSystemWatcher } from './vscode-utils/multi-file-system-watcher';
import { CodeQLCliServer } from './cli';
@@ -29,9 +28,8 @@ export abstract class QLTestNode {
* A directory containing one or more QL tests or other test directories.
*/
export class QLTestDirectory extends QLTestNode {
private _children: QLTestNode[] = [];
constructor(_path: string, _name: string) {
constructor(_path: string, _name: string, private _children: QLTestNode[] = []) {
super(_path, _name);
}
@@ -55,10 +53,23 @@ export class QLTestDirectory extends QLTestNode {
}
public finish(): void {
// remove empty directories
this._children.filter(child =>
child instanceof QLTestFile || child.children.length > 0
);
this._children.sort((a, b) => a.name.localeCompare(b.name, env.language));
for (const child of this._children) {
this._children.forEach((child, i) => {
child.finish();
}
if (child.children?.length === 1 && child.children[0] instanceof QLTestDirectory) {
// collapse children
const replacement = new QLTestDirectory(
child.children[0].path,
child.name + ' / ' + child.children[0].name,
Array.from(child.children[0].children)
);
this._children[i] = replacement;
}
});
}
private createChildDirectory(name: string): QLTestDirectory {
@@ -96,14 +107,15 @@ export class QLTestFile extends QLTestNode {
*/
interface QLTestDiscoveryResults {
/**
* The root test directory for each QL pack that contains tests.
* A directory that contains one or more QL Tests, or other QLTestDirectories.
*/
testDirectories: QLTestDirectory[];
testDirectory: QLTestDirectory | undefined;
/**
* The list of file system paths to watch. If any of these paths changes, the discovery results
* may be out of date.
* The file system path to a directory to watch. If any ql or qlref file changes in
* this directory, then this signifies a change in tests.
*/
watchPaths: string[];
watchPath: string;
}
/**
@@ -112,31 +124,30 @@ interface QLTestDiscoveryResults {
export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
private readonly _onDidChangeTests = this.push(new EventEmitter<void>());
private readonly watcher: MultiFileSystemWatcher = this.push(new MultiFileSystemWatcher());
private _testDirectories: QLTestDirectory[] = [];
private _testDirectory: QLTestDirectory | undefined;
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));
}
/**
* Event to be fired when the set of discovered tests may have changed.
*/
public get onDidChangeTests(): Event<void> { return this._onDidChangeTests.event; }
public get onDidChangeTests(): Event<void> {
return this._onDidChangeTests.event;
}
/**
* The root test directory for each QL pack that contains tests.
* The root directory. There is at least one test in this directory, or
* in a subdirectory of this.
*/
public get testDirectories(): QLTestDirectory[] { return this._testDirectories; }
private handleDidChangeQLPacks(): void {
this.refresh();
public get testDirectory(): QLTestDirectory | undefined {
return this._testDirectory;
}
private handleDidChange(uri: Uri): void {
@@ -144,72 +155,45 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
this.refresh();
}
}
protected async discover(): Promise<QLTestDiscoveryResults> {
const testDirectories: QLTestDirectory[] = [];
const watchPaths: string[] = [];
const qlPacks = this.qlPackDiscovery.qlPacks;
for (const qlPack of qlPacks) {
//HACK: Assume that only QL packs whose name ends with '-tests' contain tests.
if (this.isRelevantQlPack(qlPack)) {
watchPaths.push(qlPack.uri.fsPath);
const testPackage = await this.discoverTests(qlPack.uri.fsPath, qlPack.name);
if (testPackage !== undefined) {
testDirectories.push(testPackage);
}
}
}
return { testDirectories, watchPaths };
const testDirectory = await this.discoverTests();
return {
testDirectory,
watchPath: this.workspaceFolder.uri.fsPath
};
}
protected update(results: QLTestDiscoveryResults): void {
this._testDirectories = results.testDirectories;
this._testDirectory = results.testDirectory;
// Watch for changes to any `.ql` or `.qlref` file in any of the QL packs that contain tests.
this.watcher.clear();
results.watchPaths.forEach(watchPath => {
this.watcher.addWatch(new RelativePattern(watchPath, '**/*.{ql,qlref}'));
});
this.watcher.addWatch(new RelativePattern(results.watchPath, '**/*.{ql,qlref}'));
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.
* @param name The display name to use for the returned `TestDirectory` object.
* @returns A `QLTestDirectory` object describing the contents of the directory, or `undefined` if
* no tests were found.
*/
private async discoverTests(fullPath: string, name: string): Promise<QLTestDirectory | undefined> {
private async discoverTests(): Promise<QLTestDirectory> {
const fullPath = this.workspaceFolder.uri.fsPath;
const name = this.workspaceFolder.name;
const resolvedTests = (await this.cliServer.resolveTests(fullPath))
.filter((testPath) => !QLTestDiscovery.ignoreTestPath(testPath));
if (resolvedTests.length === 0) {
return undefined;
const rootDirectory = new QLTestDirectory(fullPath, name);
for (const testPath of resolvedTests) {
const relativePath = path.normalize(path.relative(fullPath, testPath));
const dirName = path.dirname(relativePath);
const parentDirectory = rootDirectory.createDirectory(dirName);
parentDirectory.addChild(new QLTestFile(testPath, path.basename(testPath)));
}
else {
const rootDirectory = new QLTestDirectory(fullPath, name);
for (const testPath of resolvedTests) {
const relativePath = path.normalize(path.relative(fullPath, testPath));
const dirName = path.dirname(relativePath);
const parentDirectory = rootDirectory.createDirectory(dirName);
parentDirectory.addChild(new QLTestFile(testPath, path.basename(testPath)));
}
rootDirectory.finish();
rootDirectory.finish();
return rootDirectory;
}
return rootDirectory;
}
/**

View File

@@ -453,7 +453,7 @@ export class QueryHistoryManager extends DisposableObject {
queryText: encodeURIComponent(await this.getQueryText(singleItem)),
});
const uri = vscode.Uri.parse(
`codeql:${singleItem.query.queryID}-${queryName}?${params.toString()}`
`codeql:${singleItem.query.queryID}-${queryName}?${params.toString()}`, true
);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc, { preview: false });
@@ -545,9 +545,7 @@ export class QueryHistoryManager extends DisposableObject {
private async tryOpenExternalFile(fileLocation: string) {
const uri = vscode.Uri.file(fileLocation);
try {
await vscode.window.showTextDocument(uri, {
preview: false
});
await vscode.window.showTextDocument(uri, { preview: false });
} catch (e) {
if (
e.message.includes(

View File

@@ -1,12 +1,12 @@
import { env, TreeItem } from 'vscode';
import { QueryWithResults, tmpDir, QueryInfo } from './run-queries';
import * as messages from './messages';
import * as messages from './pure/messages';
import * as cli from './cli';
import * as sarif from 'sarif';
import * as fs from 'fs-extra';
import * as path from 'path';
import { RawResultsSortState, SortedResultSetInfo, DatabaseInfo, QueryMetadata, InterpretedResultsSortState, ResultsPaths } from './interface-types';
import { RawResultsSortState, SortedResultSetInfo, DatabaseInfo, QueryMetadata, InterpretedResultsSortState, ResultsPaths } from './pure/interface-types';
import { QueryHistoryConfig } from './config';
import { QueryHistoryItemOptions } from './query-history';
@@ -96,8 +96,8 @@ export class CompletedQuery implements QueryWithResults {
}
getLabel(): string {
return this.options?.label
|| this.config.format;
return this.options?.label
|| this.config.format;
}
get didRunSuccessfully(): boolean {

View File

@@ -6,8 +6,8 @@ import { CancellationToken, createMessageConnection, MessageConnection, RequestT
import * as cli from './cli';
import { QueryServerConfig } from './config';
import { Logger, ProgressReporter } from './logging';
import { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from './messages';
import * as messages from './messages';
import { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from './pure/messages';
import * as messages from './pure/messages';
type ServerOpts = {
logger: Logger;
@@ -107,6 +107,11 @@ export class QueryServerClient extends DisposableObject {
if (this.config.debug) {
args.push('--debug', '--tuple-counting');
}
if (cli.shouldDebugQueryServer()) {
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9010,server=y,suspend=n,quiet=y');
}
const child = cli.spawnServer(
this.config.codeQlPath,
'CodeQL query server',

View File

@@ -16,9 +16,9 @@ import * as cli from './cli';
import * as config from './config';
import { DatabaseItem, getUpgradesDirectories } from './databases';
import * as helpers from './helpers';
import { DatabaseInfo, QueryMetadata, ResultsPaths } from './interface-types';
import { DatabaseInfo, QueryMetadata, ResultsPaths } from './pure/interface-types';
import { logger } from './logging';
import * as messages from './messages';
import * as messages from './pure/messages';
import { QueryHistoryItemOptions } from './query-history';
import * as qsClient from './queryserver-client';
import { isQuickQueryPath } from './quick-query';

View File

@@ -16,7 +16,6 @@ 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 './vscode-utils/disposable-object';
import { QLPackDiscovery } from './qlpack-discovery';
import { CodeQLCliServer } from './cli';
import { getOnDiskWorkspaceFolders } from './helpers';
import { testLogger } from './logging';
@@ -82,7 +81,6 @@ function changeExtension(p: string, ext: string): string {
* Test adapter for QL tests.
*/
export class QLTestAdapter extends DisposableObject implements TestAdapter {
private readonly qlPackDiscovery: QLPackDiscovery;
private readonly qlTestDiscovery: QLTestDiscovery;
private readonly _tests = this.push(
new EventEmitter<TestLoadStartedEvent | TestLoadFinishedEvent>());
@@ -97,9 +95,7 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
) {
super();
this.qlPackDiscovery = this.push(new QLPackDiscovery(workspaceFolder, cliServer));
this.qlTestDiscovery = this.push(new QLTestDiscovery(this.qlPackDiscovery, workspaceFolder, cliServer));
this.qlPackDiscovery.refresh();
this.qlTestDiscovery = this.push(new QLTestDiscovery(workspaceFolder, cliServer));
this.qlTestDiscovery.refresh();
this.push(this.qlTestDiscovery.onDidChangeTests(this.discoverTests, this));
@@ -160,20 +156,20 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
private discoverTests(): void {
this._tests.fire({ type: 'started' } as TestLoadStartedEvent);
const testDirectories = this.qlTestDiscovery.testDirectories;
const children = testDirectories.map(
testDirectory => QLTestAdapter.createTestSuiteInfo(testDirectory, testDirectory.name)
);
const testSuite: TestSuiteInfo = {
type: 'suite',
label: 'CodeQL',
id: '.',
children
};
const testDirectory = this.qlTestDiscovery.testDirectory;
let testSuite: TestSuiteInfo | undefined;
if (testDirectory?.children.length) {
const children = QLTestAdapter.createTestOrSuiteInfos(testDirectory.children);
testSuite = {
type: 'suite',
label: 'CodeQL',
id: testDirectory.path,
children
};
}
this._tests.fire({
type: 'finished',
suite: children.length > 0 ? testSuite : undefined
suite: testSuite
} as TestLoadFinishedEvent);
}
@@ -221,10 +217,26 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
cancellationToken: cancellationToken,
logger: testLogger
})) {
const state = event.pass
? 'passed'
: event.messages?.length
? 'errored'
: 'failed';
let message: string | undefined;
if (event.diff?.length) {
message = ['', `${state}: ${event.test}`, ...event.diff, ''].join('\n');
testLogger.log(message);
}
(event.diff || []).join('\n');
this._testStates.fire({
type: 'test',
state: event.pass ? 'passed' : 'failed',
test: event.test
state,
test: event.test,
message,
decorations: event.messages?.map(msg => ({
line: msg.position.line,
message: msg.message
}))
});
}
}

View File

@@ -1,10 +1,20 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { Uri, TextDocumentShowOptions, commands, window } from 'vscode';
import {
TestHub,
TestController,
TestAdapter,
TestRunStartedEvent,
TestRunFinishedEvent,
TestEvent,
TestSuiteEvent
} from 'vscode-test-adapter-api';
import { showAndLogWarningMessage } from './helpers';
import { TestTreeNode } from './test-tree-node';
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';
@@ -78,12 +88,17 @@ export class TestUIService extends UIService implements TestController {
preserveFocus: true,
preview: true
};
if (!await fs.pathExists(expectedPath)) {
showAndLogWarningMessage(`'${path.basename(expectedPath)}' does not exist. Creating an empty file.`);
await fs.createFile(expectedPath);
}
if (await fs.pathExists(actualPath)) {
const actualUri = Uri.file(actualPath);
await commands.executeCommand<void>('vscode.diff', expectedUri, actualUri,
`Expected vs. Actual for ${path.basename(testId)}`, options);
}
else {
} else {
await window.showTextDocument(expectedUri, options);
}
}

View File

@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
import { DatabaseItem } from './databases';
import * as helpers from './helpers';
import { logger } from './logging';
import * as messages from './messages';
import * as messages from './pure/messages';
import * as qsClient from './queryserver-client';
import { upgradesTmpDir } from './run-queries';

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import { vscode } from './vscode-api';
import { RawResultsSortState, SortDirection } from '../interface-types';
import { RawResultsSortState, SortDirection } from '../pure/interface-types';
import { nextSortDirection } from './result-table-utils';
import { Column } from '../bqrs-cli-types';
import { Column } from '../pure/bqrs-cli-types';
interface Props {
readonly columns: readonly Column[];

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { ResultRow } from '../bqrs-cli-types';
import { ResultRow } from '../pure/bqrs-cli-types';
import { zebraStripe } from './result-table-utils';
import RawTableValue from './RawTableValue';

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { renderLocation } from './result-table-utils';
import { ColumnValue } from '../bqrs-cli-types';
import { ColumnValue } from '../pure/bqrs-cli-types';
interface Props {
value: ColumnValue;
@@ -15,7 +15,7 @@ export default function RawTableValue(props: Props): JSX.Element {
|| typeof v === 'number'
|| typeof v === 'boolean'
) {
return <span>{v}</span>;
return <span>{v.toString()}</span>;
}
return renderLocation(v.url, v.label, props.databaseUri);

View File

@@ -1,15 +1,19 @@
import * as path from 'path';
import * as React from 'react';
import * as Sarif from 'sarif';
import * as Keys from '../result-keys';
import * as Keys from '../pure/result-keys';
import * as octicons from './octicons';
import { className, renderLocation, ResultTableProps, zebraStripe, selectableZebraStripe, jumpToLocation, nextSortDirection } from './result-table-utils';
import { onNavigation, NavigationEvent } from './results';
import { PathTableResultSet } from '../interface-types';
import { parseSarifPlainTextMessage, parseSarifLocation, isNoLocation } from '../sarif-utils';
import { InterpretedResultsSortColumn, SortDirection, InterpretedResultsSortState } from '../interface-types';
import { PathTableResultSet } from '../pure/interface-types';
import {
parseSarifPlainTextMessage,
parseSarifLocation,
isNoLocation
} from '../pure/sarif-utils';
import { InterpretedResultsSortColumn, SortDirection, InterpretedResultsSortState } from '../pure/interface-types';
import { vscode } from './vscode-api';
import { isWholeFileLoc, isLineColumnLoc } from '../bqrs-utils';
import { isWholeFileLoc, isLineColumnLoc } from '../pure/bqrs-utils';
export type PathTableProps = ResultTableProps & { resultSet: PathTableResultSet };
export interface PathTableState {

View File

@@ -1,10 +1,10 @@
import * as React from 'react';
import { ResultTableProps, className } from './result-table-utils';
import { RAW_RESULTS_LIMIT, RawResultsSortState } from '../interface-types';
import { RawTableResultSet } from '../interface-types';
import { RAW_RESULTS_LIMIT, RawResultsSortState } from '../pure/interface-types';
import { RawTableResultSet } from '../pure/interface-types';
import RawTableHeader from './RawTableHeader';
import RawTableRow from './RawTableRow';
import { ResultRow } from '../bqrs-cli-types';
import { ResultRow } from '../pure/bqrs-cli-types';
export type RawTableProps = ResultTableProps & {
resultSet: RawTableResultSet;

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import { UrlValue, ResolvableLocationValue } from '../bqrs-cli-types';
import { isStringLoc, tryGetResolvableLocation } from '../bqrs-utils';
import { RawResultsSortState, QueryMetadata, SortDirection } from '../interface-types';
import { assertNever } from '../helpers-pure';
import { ResultSet } from '../interface-types';
import { UrlValue, ResolvableLocationValue } from '../pure/bqrs-cli-types';
import { isStringLoc, tryGetResolvableLocation } from '../pure/bqrs-utils';
import { RawResultsSortState, QueryMetadata, SortDirection } from '../pure/interface-types';
import { assertNever } from '../pure/helpers-pure';
import { ResultSet } from '../pure/interface-types';
import { vscode } from './vscode-api';
export interface ResultTableProps {

View File

@@ -6,17 +6,21 @@ import {
QueryMetadata,
ResultsPaths,
InterpretedResultsSortState,
RAW_RESULTS_PAGE_SIZE,
ResultSet,
ALERTS_TABLE_NAME,
SELECT_TABLE_NAME,
getDefaultResultSetName,
ParsedResultSets,
IntoResultsViewMsg
} from '../interface-types';
} from '../pure/interface-types';
import { PathTable } from './alert-table';
import { RawTable } from './raw-results-table';
import { ResultTableProps, tableSelectionHeaderClassName, toggleDiagnosticsClassName, alertExtrasClassName } from './result-table-utils';
import {
ResultTableProps,
tableSelectionHeaderClassName,
toggleDiagnosticsClassName,
alertExtrasClassName
} from './result-table-utils';
import { vscode } from './vscode-api';
@@ -95,7 +99,7 @@ export class ResultTables
return resultSets;
}
private getResultSetNames(resultSets: ResultSet[]): string[] {
private getResultSetNames(): string[] {
return this.props.parsedResultSets.resultSetNames.concat([ALERTS_TABLE_NAME]);
}
@@ -164,7 +168,7 @@ export class ResultTables
getOffset(): number {
const { parsedResultSets } = this.props;
return parsedResultSets.pageNumber * RAW_RESULTS_PAGE_SIZE;
return parsedResultSets.pageNumber * parsedResultSets.pageSize;
}
renderPageButtons(): JSX.Element {
@@ -215,6 +219,8 @@ export class ResultTables
type="number"
size={3}
value={this.state.selectedPage}
min="1"
max={numPages}
onChange={onChange}
onBlur={e => choosePage(e.target.value)}
onKeyDown={e => {
@@ -234,7 +240,7 @@ export class ResultTables
render(): React.ReactNode {
const { selectedTable } = this.state;
const resultSets = this.getResultSets();
const resultSetNames = this.getResultSetNames(resultSets);
const resultSetNames = this.getResultSetNames();
const resultSet = resultSets.find(resultSet => resultSet.schema.name == selectedTable);
const nonemptyRawResults = resultSets.some(resultSet => resultSet.t == 'RawResultSet' && resultSet.rows.length > 0);
@@ -279,7 +285,7 @@ export class ResultTables
break;
default:
// noop
// noop
}
}

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import * as Rdom from 'react-dom';
import { assertNever } from '../helpers-pure';
import { assertNever } from '../pure/helpers-pure';
import {
DatabaseInfo,
Interpretation,
@@ -12,10 +12,10 @@ import {
ResultsPaths,
ALERTS_TABLE_NAME,
ParsedResultSets
} from '../interface-types';
} from '../pure/interface-types';
import { EventHandlers as EventHandlerList } from './event-handler-list';
import { ResultTables } from './result-tables';
import { ResultSet } from '../interface-types';
import { ResultSet } from '../pure/interface-types';
import { vscode } from './vscode-api';
/**
@@ -105,6 +105,7 @@ class App extends React.Component<{}, ResultsViewState> {
resultsPath: '', // FIXME: Not used for interpreted, refactor so this is not needed
parsedResultSets: {
numPages: msg.numPages,
pageSize: msg.pageSize,
numInterpretedPages: msg.numPages,
resultSetNames: msg.resultSetNames,
pageNumber: msg.pageNumber,

View File

@@ -1,4 +1,4 @@
import { FromCompareViewMessage, FromResultsViewMsg } from '../interface-types';
import { FromCompareViewMessage, FromResultsViewMsg } from '../pure/interface-types';
export interface VsCodeApi {
/**

View File

@@ -1,40 +1,60 @@
import 'vscode-test';
import 'mocha';
import * as path from 'path';
import { Uri, workspace } from 'vscode';
import { Uri, WorkspaceFolder } 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
describe('discoverTests', () => {
it('should run discovery', async () => {
const baseUri = Uri.parse('file:/a/b');
const baseDir = baseUri.fsPath;
const cDir = Uri.parse('file:/a/b/c').fsPath;
const dFile = Uri.parse('file:/a/b/c/d.ql').fsPath;
const eFile = Uri.parse('file:/a/b/c/e.ql').fsPath;
const hDir = Uri.parse('file:/a/b/c/f/g/h').fsPath;
const iFile = Uri.parse('file:/a/b/c/f/g/h/i.ql').fsPath;
const qlTestDiscover = new QLTestDiscovery(
{
uri: baseUri,
name: 'My tests'
} as unknown as WorkspaceFolder,
{
resolveTests() {
return [
Uri.parse('file:/a/b/c/d.ql').fsPath,
Uri.parse('file:/a/b/c/e.ql').fsPath,
Uri.parse('file:/a/b/c/f/g/h/i.ql').fsPath
];
}
} as any
);
const uri = workspace.workspaceFolders![0].uri;
expect(qlTestDiscover.isRelevantQlPack({
name: '-hucairz',
uri
})).to.be.false;
const result = await (qlTestDiscover as any).discover();
expect(result.watchPath).to.eq(baseDir);
expect(result.testDirectory.path).to.eq(baseDir);
expect(result.testDirectory.name).to.eq('My tests');
expect(qlTestDiscover.isRelevantQlPack({
name: '-tests',
uri: Uri.file('/a/b/')
})).to.be.false;
let children = result.testDirectory.children;
expect(children[0].path).to.eq(cDir);
expect(children[0].name).to.eq('c');
expect(children.length).to.eq(1);
expect(qlTestDiscover.isRelevantQlPack({
name: '-tests',
uri
})).to.be.true;
children = children[0].children;
expect(children[0].path).to.eq(dFile);
expect(children[0].name).to.eq('d.ql');
expect(children[1].path).to.eq(eFile);
expect(children[1].name).to.eq('e.ql');
expect(qlTestDiscover.isRelevantQlPack({
name: '-tests',
uri: Uri.file(path.join(uri.fsPath, 'other'))
})).to.be.true;
// A merged foler
expect(children[2].path).to.eq(hDir);
expect(children[2].name).to.eq('f / g / h');
expect(children.length).to.eq(3);
children = children[2].children;
expect(children[0].path).to.eq(iFile);
expect(children[0].name).to.eq('i.ql');
});
});
});

View File

@@ -65,6 +65,66 @@ describe('AstBuilder', () => {
)).to.deep.eq(expectedRoots);
});
it('should build an AST child without edge label', async () => {
// just test one of the children to make sure that the structure is right
// this label should only come from the node, not the edge
const astBuilder = createAstBuilder();
const roots = await astBuilder.getRoots();
expect(roots[0].children[0].parent).to.eq(roots[0]);
// break the recursion
(roots[0].children[0] as any).parent = undefined;
(roots[0].children[0] as any).children = undefined;
const child = {
children: undefined,
fileLocation: undefined,
id: 26359,
label: 'params',
location: {
endColumn: 22,
endLine: 19,
startColumn: 5,
startLine: 19,
uri: 'file:/opt/src/arch/sandbox/lib/interrupts.c'
},
order: 0,
parent: undefined
};
expect(roots[0].children[0]).to.deep.eq(child);
});
it('should build an AST child with edge label', async () => {
// just test one of the children to make sure that the structure is right
// this label should only come from both the node and the edge
const astBuilder = createAstBuilder();
const roots = await astBuilder.getRoots();
expect(roots[0].children[1].parent).to.eq(roots[0]);
// break the recursion
(roots[0].children[1] as any).parent = undefined;
(roots[0].children[1] as any).children = undefined;
const child = {
children: undefined,
fileLocation: undefined,
id: 26367,
label: 'body: [Block] { ... }',
location: {
endColumn: 1,
endLine: 22,
startColumn: 1,
startLine: 20,
uri: 'file:/opt/src/arch/sandbox/lib/interrupts.c'
},
order: 2,
parent: undefined
};
expect(roots[0].children[1]).to.deep.eq(child);
});
it('should fail when graphProperties are not correct', async () => {
overrides.graphProperties = {
tuples: [

View File

@@ -5,13 +5,33 @@ import { Uri, Range } from 'vscode';
import fileRangeFromURI from '../../../contextual/fileRangeFromURI';
import { DatabaseItem } from '../../../databases';
import { WholeFileLocation, LineColumnLocation } from '../../../bqrs-cli-types';
import { WholeFileLocation, LineColumnLocation } from '../../../pure/bqrs-cli-types';
describe('fileRangeFromURI', () => {
it('should return undefined when value is a string', () => {
it('should return undefined when value is not a file URI', () => {
expect(fileRangeFromURI('hucairz', createMockDatabaseItem())).to.be.undefined;
});
it('should fail to find a location when not a file URI and a full 5 part location', () => {
expect(fileRangeFromURI({
uri: 'https://yahoo.com',
startLine: 1,
startColumn: 2,
endLine: 3,
endColumn: 4,
} as LineColumnLocation, createMockDatabaseItem())).to.be.undefined;
});
it('should fail to find a location when there is a silly protocol', () => {
expect(fileRangeFromURI({
uri: 'filesilly://yahoo.com',
startLine: 1,
startColumn: 2,
endLine: 3,
endColumn: 4,
} as LineColumnLocation, createMockDatabaseItem())).to.be.undefined;
});
it('should return undefined when value is an empty uri', () => {
expect(fileRangeFromURI({
uri: 'file:/',
@@ -46,7 +66,7 @@ describe('fileRangeFromURI', () => {
function createMockDatabaseItem(): DatabaseItem {
return {
resolveSourceFile: (file: string) => Uri.file(file)
resolveSourceFile: (file: string) => Uri.parse(file)
} as DatabaseItem;
}
});

View File

@@ -18,7 +18,7 @@
[
{
"id": 26359,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -28,12 +28,12 @@
}
},
"semmle.label",
""
"params"
],
[
{
"id": 26360,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -48,7 +48,7 @@
[
{
"id": 26361,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -63,7 +63,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -78,7 +78,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -93,7 +93,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -108,7 +108,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -123,7 +123,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -138,7 +138,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -153,7 +153,7 @@
[
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -168,7 +168,7 @@
[
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -183,7 +183,7 @@
[
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -198,7 +198,7 @@
[
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -213,7 +213,7 @@
[
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -228,7 +228,7 @@
[
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -243,7 +243,7 @@
[
{
"id": 26368,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 17,
@@ -258,7 +258,7 @@
[
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -273,7 +273,7 @@
[
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -288,7 +288,7 @@
[
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -303,7 +303,7 @@
[
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -318,7 +318,7 @@
[
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -333,7 +333,7 @@
[
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -348,7 +348,7 @@
[
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -385,7 +385,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -396,7 +396,7 @@
},
{
"id": 26359,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -406,12 +406,12 @@
}
},
"semmle.label",
"params"
"1234"
],
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -422,7 +422,7 @@
},
{
"id": 26359,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -437,7 +437,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -448,7 +448,7 @@
},
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -463,7 +463,7 @@
[
{
"id": 0,
"label": "[TopLevelFunction] int disable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] int disable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 19,
@@ -474,7 +474,7 @@
},
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -489,7 +489,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -500,7 +500,7 @@
},
{
"id": 26360,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -515,7 +515,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -526,7 +526,7 @@
},
{
"id": 26360,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -541,7 +541,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -552,7 +552,7 @@
},
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -567,7 +567,7 @@
[
{
"id": 26363,
"label": "[TopLevelFunction] void enable_interrupts()",
"label": "SHOULD NOT USE [TopLevelFunction] void enable_interrupts()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 15,
@@ -578,7 +578,7 @@
},
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -593,7 +593,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -604,7 +604,7 @@
},
{
"id": 26361,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -619,7 +619,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -630,7 +630,7 @@
},
{
"id": 26361,
"label": "",
"label": "SHOULD NOT USE ",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -645,7 +645,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -656,7 +656,7 @@
},
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -671,7 +671,7 @@
[
{
"id": 26364,
"label": "[TopLevelFunction] int interrupt_init()",
"label": "SHOULD NOT USE [TopLevelFunction] int interrupt_init()",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 10,
@@ -682,7 +682,7 @@
},
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -697,7 +697,7 @@
[
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -708,7 +708,7 @@
},
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -723,7 +723,7 @@
[
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -734,7 +734,7 @@
},
{
"id": 26365,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -749,7 +749,7 @@
[
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -760,7 +760,7 @@
},
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -775,7 +775,7 @@
[
{
"id": 26367,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 20,
@@ -786,7 +786,7 @@
},
{
"id": 26366,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 21,
@@ -801,7 +801,7 @@
[
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -812,7 +812,7 @@
},
{
"id": 26368,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 17,
@@ -827,7 +827,7 @@
[
{
"id": 26369,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 16,
@@ -838,7 +838,7 @@
},
{
"id": 26368,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 17,
@@ -853,7 +853,7 @@
[
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -864,7 +864,7 @@
},
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -879,7 +879,7 @@
[
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -890,7 +890,7 @@
},
{
"id": 26370,
"label": "[Literal] 0",
"label": "SHOULD NOT USE [Literal] 0",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -905,7 +905,7 @@
[
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -916,7 +916,7 @@
},
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,
@@ -931,7 +931,7 @@
[
{
"id": 26372,
"label": "[Block] { ... }",
"label": "SHOULD NOT USE [Block] { ... }",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 11,
@@ -942,7 +942,7 @@
},
{
"id": 26371,
"label": "[ReturnStmt] return ...",
"label": "SHOULD NOT USE [ReturnStmt] return ...",
"url": {
"uri": "file:/opt/src/arch/sandbox/lib/interrupts.c",
"startLine": 12,

View File

@@ -49,11 +49,59 @@ describe('databases-ui', () => {
const parentDir = path.join(dir, 'db-hucairz');
const dbDir = path.join(parentDir, 'db-javascript');
const file = path.join(dbDir, 'nested');
await fs.mkdirs(dbDir);
await fs.createFile(file);
fs.mkdirsSync(dbDir);
fs.createFileSync(file);
const uri = await fixDbUri(Uri.file(file));
expect(uri.toString()).to.eq(Uri.file(parentDir).toString());
});
});
it('should delete orphaned databases', async () => {
const storageDir = tmp.dirSync().name;
const db1 = createDatabase(storageDir, 'db1-imported', 'cpp');
const db2 = createDatabase(storageDir, 'db2-notimported', 'cpp');
const db3 = createDatabase(storageDir, 'db3-invalidlanguage', 'hucairz');
// these two should be deleted
const db4 = createDatabase(storageDir, 'db2-notimported-with-db-info', 'cpp', '.dbinfo');
const db5 = createDatabase(storageDir, 'db2-notimported-with-codeql-database.yml', 'cpp', 'codeql-database.yml');
const databaseUI = new DatabaseUI(
{} as any,
{
databaseItems: [
{ databaseUri: Uri.file(db1) }
],
onDidChangeDatabaseItem: () => { /**/ },
onDidChangeCurrentDatabaseItem: () => { /**/ },
} as any,
{} as any,
storageDir,
storageDir
);
await databaseUI.handleRemoveOrphanedDatabases();
expect(fs.pathExistsSync(db1)).to.be.true;
expect(fs.pathExistsSync(db2)).to.be.true;
expect(fs.pathExistsSync(db3)).to.be.true;
expect(fs.pathExistsSync(db4)).to.be.false;
expect(fs.pathExistsSync(db5)).to.be.false;
databaseUI.dispose();
});
function createDatabase(storageDir: string, dbName: string, language: string, extraFile?: string) {
const parentDir = path.join(storageDir, dbName);
const dbDir = path.join(parentDir, `db-${language}`);
fs.mkdirsSync(dbDir);
if (extraFile) {
fs.createFileSync(path.join(parentDir, extraFile));
}
return parentDir;
}
});

View File

@@ -9,7 +9,8 @@ import {
DatabaseItem,
DatabaseManager,
DatabaseItemImpl,
DatabaseContents
DatabaseContents,
isLikelyDbLanguageFolder
} from '../../databases';
import { QueryServerConfig } from '../../config';
import { Logger } from '../../logging';
@@ -24,8 +25,9 @@ describe('databases', () => {
databaseManager = new DatabaseManager(
{
workspaceState: {
update: updateSpy
}
update: updateSpy,
get: sinon.stub()
},
} as unknown as ExtensionContext,
{} as QueryServerConfig,
{} as Logger,
@@ -81,45 +83,37 @@ describe('databases', () => {
expect(mockDbItem.name).to.eq('new name');
expect(updateSpy).to.have.been.calledWith('databaseList', ['new name']);
expect(spy).to.have.been.calledWith({
item: mockDbItem,
item: undefined,
kind: DatabaseEventKind.Rename
});
});
describe('resolveSourceFile', () => {
describe('unzipped source archive', () => {
it('should resolve a source file in an unzipped database', () => {
const db = createMockDB();
const resolved = db.resolveSourceFile('abc');
expect(resolved.toString()).to.eq('file:///sourceArchive-uri/abc');
});
it('should fail to resolve when not a uri', () => {
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
(db as any)._contents.sourceArchiveUri = undefined;
expect(() => db.resolveSourceFile('abc')).to.throw('Scheme is missing');
});
it('should resolve a source file in an unzipped database with trailing slash', () => {
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
const resolved = db.resolveSourceFile('abc');
expect(resolved.toString()).to.eq('file:///sourceArchive-uri/abc');
});
it('should resolve a source uri in an unzipped database with trailing slash', () => {
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
const resolved = db.resolveSourceFile('file:/abc');
expect(resolved.toString()).to.eq('file:///sourceArchive-uri/abc');
});
it('should fail to resolve when not a file uri', () => {
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
(db as any)._contents.sourceArchiveUri = undefined;
expect(() => db.resolveSourceFile('http://abc')).to.throw('Invalid uri scheme');
});
describe('no source archive', () => {
it('should resolve a file', () => {
it('should resolve undefined', () => {
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
(db as any)._contents.sourceArchiveUri = undefined;
const resolved = db.resolveSourceFile('abc');
expect(resolved.toString()).to.eq('file:///abc');
const resolved = db.resolveSourceFile(undefined);
expect(resolved.toString()).to.eq('file:///database-uri');
});
it('should resolve an empty file', () => {
const db = createMockDB(Uri.parse('file:/sourceArchive-uri/'));
(db as any)._contents.sourceArchiveUri = undefined;
const resolved = db.resolveSourceFile('file:');
expect(resolved.toString()).to.eq('file:///database-uri');
expect(resolved.toString()).to.eq('file:///');
});
});
@@ -160,7 +154,7 @@ describe('databases', () => {
pathWithinSourceArchive: 'def'
}));
const resolved = db.resolveSourceFile('file:');
expect(resolved.toString()).to.eq('codeql-zip-archive://1-18/sourceArchive-uri/def');
expect(resolved.toString()).to.eq('codeql-zip-archive://1-18/sourceArchive-uri/def/');
});
});
@@ -186,4 +180,9 @@ describe('databases', () => {
);
}
});
it('should find likely db language folders', () => {
expect(isLikelyDbLanguageFolder('db-javascript')).to.be.true;
expect(isLikelyDbLanguageFolder('dbnot-a-db')).to.be.false;
});
});

View File

@@ -8,7 +8,7 @@ import {
fileUriToWebviewUri,
tryResolveLocation,
} from '../../interface-utils';
import { getDefaultResultSetName } from '../../interface-types';
import { getDefaultResultSetName } from '../../pure/interface-types';
import { DatabaseItem } from '../../databases';
describe('interface-utils', () => {

View File

@@ -8,8 +8,8 @@ import * as chaiAsPromised from 'chai-as-promised';
import { CompletedQuery, interpretResults } from '../../query-results';
import { QueryInfo, QueryWithResults, tmpDir } from '../../run-queries';
import { QueryHistoryConfig } from '../../config';
import { EvaluationResult, QueryResultType } from '../../messages';
import { SortDirection, SortedResultSetInfo } from '../../interface-types';
import { EvaluationResult, QueryResultType } from '../../pure/messages';
import { SortDirection, SortedResultSetInfo } from '../../pure/interface-types';
import { CodeQLCliServer, SourceInfo } from '../../cli';
chai.use(chaiAsPromised);
@@ -204,7 +204,7 @@ describe('CompletedQuery', () => {
});
function mockCompletedQuery() {
return new CompletedQuery(
return new CompletedQuery(
mockQueryWithResults(),
mockQueryHistoryConfig()
);

View File

@@ -5,11 +5,11 @@ import * as path from 'path';
import * as tmp from 'tmp';
import * as url from 'url';
import { CancellationTokenSource } from 'vscode-jsonrpc';
import * as messages from '../../messages';
import * as messages from '../../pure/messages';
import * as qsClient from '../../queryserver-client';
import * as cli from '../../cli';
import { ProgressReporter, Logger } from '../../logging';
import { ColumnValue } from '../../bqrs-cli-types';
import { ColumnValue } from '../../pure/bqrs-cli-types';
import { FindDistributionResultKind } from '../../distribution';

View File

@@ -6,7 +6,7 @@ import * as sinon from 'sinon';
import * as chaiAsPromised from 'chai-as-promised';
import { QueryInfo } from '../../run-queries';
import { QlProgram, Severity, compileQuery } from '../../messages';
import { QlProgram, Severity, compileQuery } from '../../pure/messages';
import { DatabaseItem } from '../../databases';
chai.use(chaiAsPromised);

View File

@@ -0,0 +1,106 @@
import 'vscode-test';
import 'mocha';
import * as sinon from 'sinon';
import { Uri, WorkspaceFolder } from 'vscode';
import { expect } from 'chai';
import { QLTestAdapter } from '../../test-adapter';
import { CodeQLCliServer } from '../../cli';
describe('test-adapter', () => {
let adapter: QLTestAdapter;
let runTestsSpy: sinon.SinonStub;
let resolveTestsSpy: sinon.SinonStub;
let resolveQlpacksSpy: sinon.SinonStub;
let sandox: sinon.SinonSandbox;
beforeEach(() => {
sandox = sinon.createSandbox();
mockRunTests();
resolveQlpacksSpy = sandox.stub().resolves({});
resolveTestsSpy = sandox.stub().resolves([]);
adapter = new QLTestAdapter({
name: 'ABC',
uri: Uri.parse('file:/ab/c')
} as WorkspaceFolder, {
runTests: runTestsSpy,
resolveQlpacks: resolveQlpacksSpy,
resolveTests: resolveTestsSpy
} as unknown as CodeQLCliServer);
});
afterEach(() => {
sandox.restore();
});
it('should run some tests', async () => {
const listenerSpy = sandox.spy();
adapter.testStates(listenerSpy);
const testsPath = Uri.parse('file:/ab/c').fsPath;
const dPath = Uri.parse('file:/ab/c/d.ql').fsPath;
const gPath = Uri.parse('file:/ab/c/e/f/g.ql').fsPath;
const hPath = Uri.parse('file:/ab/c/e/f/h.ql').fsPath;
await adapter.run([testsPath]);
expect(listenerSpy.getCall(0).args).to.deep.eq([
{ type: 'started', tests: [testsPath] }
]);
expect(listenerSpy.getCall(1).args).to.deep.eq([{
type: 'test',
state: 'passed',
test: dPath,
message: undefined,
decorations: []
}]);
expect(listenerSpy.getCall(2).args).to.deep.eq([{
type: 'test',
state: 'errored',
test: gPath,
message: `\nerrored: ${gPath}\npqr\nxyz\n`,
decorations: [
{ line: 1, message: 'abc' }
]
}]);
expect(listenerSpy.getCall(3).args).to.deep.eq([{
type: 'test',
state: 'failed',
test: hPath,
message: `\nfailed: ${hPath}\njkh\ntuv\n`,
decorations: []
}]);
expect(listenerSpy.getCall(4).args).to.deep.eq([{ type: 'finished' }]);
expect(listenerSpy).to.have.callCount(5);
});
function mockRunTests() {
// runTests is an async generator function. This is not directly supported in sinon
// However, we can pretend the same thing by just returning an async array.
runTestsSpy = sandox.stub();
runTestsSpy.returns(
(async function*() {
yield Promise.resolve({
test: Uri.parse('file:/ab/c/d.ql').fsPath,
pass: true,
messages: []
});
yield Promise.resolve({
test: Uri.parse('file:/ab/c/e/f/g.ql').fsPath,
pass: false,
diff: ['pqr', 'xyz'],
// a compile error
messages: [
{ position: { line: 1 }, message: 'abc' }
]
});
yield Promise.resolve({
test: Uri.parse('file:/ab/c/e/f/h.ql').fsPath,
pass: false,
diff: ['jkh', 'tuv'],
messages: []
});
})()
);
}
});

View File

@@ -32,7 +32,6 @@ describe('commands declared in package.json', function() {
if (
command.match(/^codeQL\./)
|| command.match(/^codeQLQueryResults\./)
|| command.match(/^codeQLTests\./)
) {
paletteCmds.add(command);
expect(title).not.to.be.undefined;
@@ -42,6 +41,7 @@ describe('commands declared in package.json', function() {
command.match(/^codeQLDatabases\./)
|| command.match(/^codeQLQueryHistory\./)
|| command.match(/^codeQLAstViewer\./)
|| command.match(/^codeQLTests\./)
) {
scopedCmds.add(command);
expect(title).not.to.be.undefined;
@@ -69,8 +69,6 @@ describe('commands declared in package.json', function() {
disabledInPalette.add(commandDecl.command);
});
it('should have commands appropriately prefixed', function() {
paletteCmds.forEach(command => {
expect(commandTitles[command], `command ${command} should be prefixed with 'CodeQL: ', since it is accessible from the command palette`).to.match(/^CodeQL: /);

View File

@@ -4,7 +4,7 @@ import * as sinonChai from 'sinon-chai';
import 'mocha';
import * as path from 'path';
import { gatherQlFiles } from '../../src/files';
import { gatherQlFiles } from '../../src/pure/files';
chai.use(sinonChai);
const expect = chai.expect;
@@ -13,9 +13,6 @@ describe('files', () => {
const dataDir = path.join(path.dirname(__dirname), 'data');
const data2Dir = path.join(path.dirname(__dirname), 'data2');
it('should pass', () => {
expect(true).to.be.eq(true);
});
it('should find one file', async () => {
const singleFile = path.join(dataDir, 'query.ql');
const result = await gatherQlFiles([singleFile]);

View File

@@ -0,0 +1,22 @@
import { fail } from 'assert';
import { expect } from 'chai';
import { asyncFilter } from '../../src/pure/helpers-pure';
describe('helpers-pure', () => {
it('should filter asynchronously', async () => {
expect(await asyncFilter([1, 2, 3], x => Promise.resolve(x > 2))).to.deep.eq([3]);
});
it('should throw on error when filtering', async () => {
const rejects = (x: number) => x === 3
? Promise.reject(new Error('opps'))
: Promise.resolve(true);
try {
await asyncFilter([1, 2, 3], rejects);
fail('Should have thrown');
} catch (e) {
expect(e.message).to.eq('opps');
}
});
});

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai';
import 'mocha';
import { tryGetResolvableLocation } from '../../src/bqrs-utils';
import { tryGetResolvableLocation } from '../../src/pure/bqrs-utils';
describe('processing string locations', function() {
it('should detect Windows whole-file locations', function() {

View File

@@ -2,7 +2,12 @@ import 'mocha';
import { expect } from 'chai';
import * as Sarif from 'sarif';
import { getPathRelativeToSourceLocationPrefix, parseSarifLocation, parseSarifPlainTextMessage, unescapeSarifText } from '../../src/sarif-utils';
import {
getPathRelativeToSourceLocationPrefix,
parseSarifLocation,
parseSarifPlainTextMessage,
unescapeSarifText
} from '../../src/pure/sarif-utils';
describe('parsing sarif', () => {
@@ -46,20 +51,24 @@ describe('parsing sarif', () => {
it('should normalize source locations', () => {
expect(getPathRelativeToSourceLocationPrefix('C:\\a\\b', '?x=test'))
.to.eq('file:C:/a/b/?x=test');
.to.eq('file:/C:/a/b/?x=test');
expect(getPathRelativeToSourceLocationPrefix('C:\\a\\b', '%3Fx%3Dtest'))
.to.eq('file:C:/a/b/%3Fx%3Dtest');
.to.eq('file:/C:/a/b/%3Fx%3Dtest');
expect(getPathRelativeToSourceLocationPrefix('C:\\a =\\b c?', '?x=test'))
.to.eq('file:/C:/a%20%3D/b%20c%3F/?x=test');
expect(getPathRelativeToSourceLocationPrefix('/a/b/c', '?x=test'))
.to.eq('file:/a/b/c/?x=test');
});
describe('parseSarifLocation', () => {
it('should parse a sarif location with "no location"', () => {
expect(parseSarifLocation({ }, '')).to.deep.equal({
expect(parseSarifLocation({}, '')).to.deep.equal({
hint: 'no physical location'
});
expect(parseSarifLocation({ physicalLocation: {} }, '')).to.deep.equal({
hint: 'no artifact location'
});
expect(parseSarifLocation({ physicalLocation: { artifactLocation: { } } }, '')).to.deep.equal({
expect(parseSarifLocation({ physicalLocation: { artifactLocation: {} } }, '')).to.deep.equal({
hint: 'artifact location has no uri'
});
});
@@ -73,7 +82,7 @@ describe('parsing sarif', () => {
}
};
expect(parseSarifLocation(location, 'prefix')).to.deep.equal({
uri: 'file:prefix/abc?x=test',
uri: 'file:/prefix/abc?x=test',
userVisibleFile: 'abc?x=test'
});
});
@@ -82,13 +91,13 @@ describe('parsing sarif', () => {
const location: Sarif.Location = {
physicalLocation: {
artifactLocation: {
uri: 'file:abc%3Fx%3Dtest'
uri: 'file:/abc%3Fx%3Dtest'
}
}
};
expect(parseSarifLocation(location, 'prefix')).to.deep.equal({
uri: 'file:abc%3Fx%3Dtest',
userVisibleFile: 'abc?x=test'
uri: 'file:/abc%3Fx%3Dtest',
userVisibleFile: '/abc?x=test'
});
});

View File

@@ -6,9 +6,7 @@
"module": "commonjs",
"target": "es2017",
"outDir": "out",
"lib": [
"es6"
],
"lib": ["ES2020"],
"moduleResolution": "node",
"sourceMap": true,
"rootDir": "src",
@@ -21,12 +19,6 @@
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"test",
"**/view"
]
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "test", "**/view"]
}