diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 15d1499c6..ee63d840a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -199,6 +199,7 @@ jobs: run: echo "cli-versions=$(cat ./extensions/ql-vscode/supported_cli_versions.json | jq -rc)" >> $GITHUB_OUTPUT outputs: cli-versions: ${{ steps.set-variables.outputs.cli-versions }} + cli-test: name: CLI Test runs-on: ${{ matrix.os }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0454b038f..120ef8764 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,49 +67,19 @@ jobs: # TODO Run tests, or check that a test run on the same branch succeeded. - - name: Create release - id: create-release - uses: actions/create-release@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - # This gives us a chance to manually review the created release before publishing it, - # as well as to test the release workflow by pushing temporary tags. - # Once we have set all required release metadata in this step, we can set this to `false`. - draft: true - prerelease: false - - - name: Upload release asset - uses: actions/upload-release-asset@v1.0.1 - if: success() - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - # Get the `upload_url` from the `create-release` step above. - upload_url: ${{ steps.create-release.outputs.upload_url }} - # Get the `vsix_path` and `ref_name` from the `prepare-artifacts` step above. - asset_path: ${{ steps.prepare-artifacts.outputs.vsix_path }} - asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }} - asset_content_type: application/zip - - name: Create sourcemap ZIP file run: | cd dist/vscode-codeql/out zip -r ../../vscode-codeql-sourcemaps.zip *.map - - name: Upload sourcemap ZIP file - uses: actions/upload-release-asset@v1.0.1 - if: success() + - name: Create release + id: create-release + run: | + gh release create ${{ github.ref_name }} --draft --title "Release ${{ github.ref_name }}" \ + '${{ steps.prepare-artifacts.outputs.vsix_path }}#${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}' \ + 'dist/vscode-codeql-sourcemaps.zip#${{ format('vscode-codeql-sourcemaps-{0}.zip', steps.prepare-artifacts.outputs.ref_name) }}' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - # Get the `upload_url` from the `create-release` step above. - upload_url: ${{ steps.create-release.outputs.upload_url }} - asset_path: dist/vscode-codeql-sourcemaps.zip - asset_name: ${{ format('vscode-codeql-sourcemaps-{0}.zip', steps.prepare-artifacts.outputs.ref_name) }} - asset_content_type: application/zip ### # Do Post release work: version bump and changelog PR @@ -164,10 +134,7 @@ jobs: - name: Publish to Registry run: | - npx vsce publish -p $VSCE_TOKEN --packagePath *.vsix || \ - echo "Failed to publish to VS Code Marketplace. \ - If this was an authentication problem, please make sure the \ - auth token hasn't expired." + npx vsce publish -p $VSCE_TOKEN --packagePath *.vsix open-vsx-publish: name: Publish to Open VSX Registry diff --git a/extensions/ql-vscode/CHANGELOG.md b/extensions/ql-vscode/CHANGELOG.md index 56dcf5fc9..910758c11 100644 --- a/extensions/ql-vscode/CHANGELOG.md +++ b/extensions/ql-vscode/CHANGELOG.md @@ -2,6 +2,8 @@ ## [UNRELEASED] +- Restart the CodeQL language server whenever the _CodeQL: Restart Query Server_ command is invoked. This avoids bugs where the CLI version changes to support new language features, but the language server is not updated. [#2238](https://github.com/github/vscode-codeql/pull/2238) + ## 1.8.1 - 23 March 2023 - Show data flow paths of a variant analysis in a new tab. [#2172](https://github.com/github/vscode-codeql/pull/2172) & [#2182](https://github.com/github/vscode-codeql/pull/2182) diff --git a/extensions/ql-vscode/package-lock.json b/extensions/ql-vscode/package-lock.json index 3a2dee1bd..9e93afdfa 100644 --- a/extensions/ql-vscode/package-lock.json +++ b/extensions/ql-vscode/package-lock.json @@ -20,12 +20,11 @@ "classnames": "~2.2.6", "d3": "^7.6.1", "d3-graphviz": "^5.0.2", - "fs-extra": "^10.0.1", - "glob-promise": "^6.0.2", + "fs-extra": "^11.1.1", "immutable": "^4.0.0", "js-yaml": "^4.1.0", "minimist": "~1.2.6", - "msw": "^0.49.0", + "msw": "^1.2.0", "nanoid": "^3.2.0", "node-fetch": "~2.6.7", "p-queue": "^6.0.0", @@ -71,8 +70,7 @@ "@types/d3": "^7.4.0", "@types/d3-graphviz": "^2.6.6", "@types/del": "^4.0.0", - "@types/fs-extra": "^9.0.6", - "@types/glob": "^7.1.1", + "@types/fs-extra": "^11.0.1", "@types/google-protobuf": "^3.2.7", "@types/gulp": "^4.0.9", "@types/gulp-replace": "^1.1.0", @@ -118,7 +116,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.6.4", "file-loader": "^6.2.0", - "glob": "^7.1.4", + "glob": "^9.3.2", "gulp": "^4.0.2", "gulp-esbuild": "^0.10.5", "gulp-replace": "^1.1.3", @@ -4193,6 +4191,26 @@ "node": ">=8" } }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@jest/reporters/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5373,6 +5391,26 @@ "node": ">=10" } }, + "node_modules/@npmcli/move-file/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@npmcli/move-file/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -6932,6 +6970,26 @@ "semver": "bin/semver" } }, + "node_modules/@storybook/builder-webpack4/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@storybook/builder-webpack4/node_modules/glob-promise": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", @@ -7767,6 +7825,26 @@ "webpack": "^4.27.0 || ^5.0.0" } }, + "node_modules/@storybook/builder-webpack5/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@storybook/builder-webpack5/node_modules/glob-promise": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", @@ -8549,6 +8627,26 @@ "node": ">=10" } }, + "node_modules/@storybook/core-common/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@storybook/core-common/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -9276,6 +9374,26 @@ "node": ">=10" } }, + "node_modules/@storybook/core-server/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@storybook/core-server/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -10040,6 +10158,26 @@ "node": ">=10" } }, + "node_modules/@storybook/manager-webpack4/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@storybook/manager-webpack4/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -13223,11 +13361,12 @@ "dev": true }, "node_modules/@types/fs-extra": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.6.tgz", - "integrity": "sha512-ecNRHw4clCkowNOBJH1e77nvbPxHYnWIXMv1IAoG/9+MYGkgoyr3Ppxr7XYFNL41V422EDhyV4/4SSK8L2mlig==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", + "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", "dev": true, "dependencies": { + "@types/jsonfile": "*", "@types/node": "*" } }, @@ -13457,6 +13596,15 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/jsonfile": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", + "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/jszip": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@types/jszip/-/jszip-3.1.7.tgz", @@ -13484,7 +13632,8 @@ "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true }, "node_modules/@types/ms": { "version": "0.7.31", @@ -14340,6 +14489,26 @@ "node": ">=8.9.3" } }, + "node_modules/@vscode/test-electron/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vscode/test-electron/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -14398,6 +14567,26 @@ "node": ">= 6" } }, + "node_modules/@vscode/vsce/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vscode/vsce/node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -15251,6 +15440,25 @@ "node": ">= 6" } }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/archiver/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -17315,6 +17523,26 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/c8/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/c8/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -17475,6 +17703,26 @@ "node": ">=10" } }, + "node_modules/cacache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/cacache/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -18799,6 +19047,26 @@ "node": ">=4.0.0" } }, + "node_modules/cpy/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/cpy/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -19993,6 +20261,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/del/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/del/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -23500,6 +23788,26 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/flat-cache/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -23747,6 +24055,26 @@ "node": ">=10" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -23828,16 +24156,16 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.14" } }, "node_modules/fs-minipass": { @@ -24114,19 +24442,18 @@ "dev": true }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", + "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^7.4.1", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -24143,33 +24470,6 @@ "node": ">= 6" } }, - "node_modules/glob-promise": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.2.tgz", - "integrity": "sha512-Ni2aDyD1ekD6x8/+K4hDriRDbzzfuK4yKpqSymJ4P7IxbtARiOOuU+k40kbHM0sLIlbf1Qh0qdMkAHMZYE6XJQ==", - "dependencies": { - "@types/glob": "^8.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/ahmadnassri" - }, - "peerDependencies": { - "glob": "^8.0.3" - } - }, - "node_modules/glob-promise/node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, "node_modules/glob-stream": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", @@ -24191,6 +24491,26 @@ "node": ">= 0.10" } }, + "node_modules/glob-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-stream/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -24336,6 +24656,39 @@ "node": ">=0.10" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", + "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minipass": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", + "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/global": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", @@ -27162,6 +27515,26 @@ "node": ">=8" } }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest-config/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -29567,6 +29940,26 @@ "node": ">=8" } }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest-runtime/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -32589,9 +32982,9 @@ "dev": true }, "node_modules/msw": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-0.49.0.tgz", - "integrity": "sha512-xX5RMSMjN58j8G/V26Uaf5LP464VltuWyd66TQimLueVYfG47RKydGsd4JW165Jb/gjoaQxh5Tdvv31wdZAOlA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-1.2.0.tgz", + "integrity": "sha512-2nbGxmG8Zk/eCiWCAFtgz5kwPE5YnTlOcRBggFkykqkBSPQE+kvlHJElOzrTG21Tb0ZGXWgn0PI5kyANnOpgug==", "hasInstallScript": true, "dependencies": { "@mswjs/cookies": "^0.2.2", @@ -32610,7 +33003,7 @@ "node-fetch": "^2.6.7", "outvariant": "^1.3.0", "path-to-regexp": "^6.2.0", - "strict-event-emitter": "^0.2.6", + "strict-event-emitter": "^0.4.3", "type-fest": "^2.19.0", "yargs": "^17.3.1" }, @@ -32625,7 +33018,7 @@ "url": "https://opencollective.com/mswjs" }, "peerDependencies": { - "typescript": ">= 4.4.x <= 4.9.x" + "typescript": ">= 4.4.x <= 5.0.x" }, "peerDependenciesMeta": { "typescript": { @@ -32745,6 +33138,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, + "node_modules/msw/node_modules/strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==" + }, "node_modules/msw/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -34319,6 +34717,40 @@ "node": ">=0.10.0" } }, + "node_modules/path-scurry": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.3.tgz", + "integrity": "sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g==", + "dev": true, + "dependencies": { + "lru-cache": "^7.14.1", + "minipass": "^4.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", + "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -36190,6 +36622,25 @@ "rimraf": "bin.js" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -38014,6 +38465,26 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -38130,6 +38601,25 @@ "tmp": "^0.2.0" } }, + "node_modules/tmp-promise/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tmp-promise/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -44270,6 +44760,20 @@ "to-regex-range": "^5.0.1" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -45179,6 +45683,20 @@ "rimraf": "^3.0.2" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -46288,6 +46806,20 @@ } } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-promise": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", @@ -46917,6 +47449,20 @@ "semver": "^7.3.5" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-promise": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", @@ -47500,6 +48046,20 @@ "universalify": "^2.0.0" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -48080,6 +48640,20 @@ "universalify": "^2.0.0" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -48701,6 +49275,20 @@ "universalify": "^2.0.0" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -51135,11 +51723,12 @@ "dev": true }, "@types/fs-extra": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.6.tgz", - "integrity": "sha512-ecNRHw4clCkowNOBJH1e77nvbPxHYnWIXMv1IAoG/9+MYGkgoyr3Ppxr7XYFNL41V422EDhyV4/4SSK8L2mlig==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", + "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", "dev": true, "requires": { + "@types/jsonfile": "*", "@types/node": "*" } }, @@ -51354,6 +51943,15 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/jsonfile": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", + "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/jszip": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@types/jszip/-/jszip-3.1.7.tgz", @@ -51381,7 +51979,8 @@ "@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true }, "@types/ms": { "version": "0.7.31", @@ -52046,6 +52645,20 @@ "unzipper": "^0.10.11" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -52091,6 +52704,20 @@ "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -52819,6 +53446,21 @@ "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "archy": { @@ -54416,6 +55058,20 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -54530,6 +55186,20 @@ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -55551,6 +56221,20 @@ "micromatch": "^3.1.10" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -56482,6 +57166,20 @@ "slash": "^3.0.0" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -59103,6 +59801,20 @@ "rimraf": "^3.0.2" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -59282,6 +59994,20 @@ "universalify": "^2.0.0" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -59347,9 +60073,9 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", + "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -59570,16 +60296,41 @@ "dev": true }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", + "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^7.4.1", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", + "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", + "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "dev": true + } } }, "glob-parent": { @@ -59590,25 +60341,6 @@ "is-glob": "^4.0.1" } }, - "glob-promise": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.2.tgz", - "integrity": "sha512-Ni2aDyD1ekD6x8/+K4hDriRDbzzfuK4yKpqSymJ4P7IxbtARiOOuU+k40kbHM0sLIlbf1Qh0qdMkAHMZYE6XJQ==", - "requires": { - "@types/glob": "^8.0.0" - }, - "dependencies": { - "@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "requires": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - } - } - }, "glob-stream": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", @@ -59627,6 +60359,20 @@ "unique-stream": "^2.0.2" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -61908,6 +62654,20 @@ "to-regex-range": "^5.0.1" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -63763,6 +64523,20 @@ "to-regex-range": "^5.0.1" } }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -66050,9 +66824,9 @@ "dev": true }, "msw": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-0.49.0.tgz", - "integrity": "sha512-xX5RMSMjN58j8G/V26Uaf5LP464VltuWyd66TQimLueVYfG47RKydGsd4JW165Jb/gjoaQxh5Tdvv31wdZAOlA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-1.2.0.tgz", + "integrity": "sha512-2nbGxmG8Zk/eCiWCAFtgz5kwPE5YnTlOcRBggFkykqkBSPQE+kvlHJElOzrTG21Tb0ZGXWgn0PI5kyANnOpgug==", "requires": { "@mswjs/cookies": "^0.2.2", "@mswjs/interceptors": "^0.17.5", @@ -66070,7 +66844,7 @@ "node-fetch": "^2.6.7", "outvariant": "^1.3.0", "path-to-regexp": "^6.2.0", - "strict-event-emitter": "^0.2.6", + "strict-event-emitter": "^0.4.3", "type-fest": "^2.19.0", "yargs": "^17.3.1" }, @@ -66157,6 +66931,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, + "strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -67387,6 +68166,30 @@ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, + "path-scurry": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.3.tgz", + "integrity": "sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g==", + "dev": true, + "requires": { + "lru-cache": "^7.14.1", + "minipass": "^4.0.2" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "minipass": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", + "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "dev": true + } + } + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -68821,6 +69624,21 @@ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "requires": { "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "ripemd160": { @@ -70262,6 +71080,22 @@ "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "text-table": { @@ -70366,6 +71200,19 @@ "tmp": "^0.2.0" }, "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index 21863ca86..6318901d0 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -1441,12 +1441,11 @@ "classnames": "~2.2.6", "d3": "^7.6.1", "d3-graphviz": "^5.0.2", - "fs-extra": "^10.0.1", - "glob-promise": "^6.0.2", + "fs-extra": "^11.1.1", "immutable": "^4.0.0", "js-yaml": "^4.1.0", "minimist": "~1.2.6", - "msw": "^0.49.0", + "msw": "^1.2.0", "nanoid": "^3.2.0", "node-fetch": "~2.6.7", "p-queue": "^6.0.0", @@ -1492,8 +1491,7 @@ "@types/d3": "^7.4.0", "@types/d3-graphviz": "^2.6.6", "@types/del": "^4.0.0", - "@types/fs-extra": "^9.0.6", - "@types/glob": "^7.1.1", + "@types/fs-extra": "^11.0.1", "@types/google-protobuf": "^3.2.7", "@types/gulp": "^4.0.9", "@types/gulp-replace": "^1.1.0", @@ -1539,7 +1537,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.6.4", "file-loader": "^6.2.0", - "glob": "^7.1.4", + "glob": "^9.3.2", "gulp": "^4.0.2", "gulp-esbuild": "^0.10.5", "gulp-replace": "^1.1.3", @@ -1573,8 +1571,5 @@ "prettier --write", "eslint --fix" ] - }, - "resolutions": { - "glob-parent": "6.0.0" } } diff --git a/extensions/ql-vscode/src/ast-cfg-commands.ts b/extensions/ql-vscode/src/ast-cfg-commands.ts index 49749be22..353671818 100644 --- a/extensions/ql-vscode/src/ast-cfg-commands.ts +++ b/extensions/ql-vscode/src/ast-cfg-commands.ts @@ -5,31 +5,18 @@ import { TemplatePrintAstProvider, TemplatePrintCfgProvider, } from "./contextual/templateProvider"; -import { compileAndRunQuery } from "./local-queries"; -import { QueryRunner } from "./queryRunner"; -import { QueryHistoryManager } from "./query-history/query-history-manager"; -import { DatabaseUI } from "./local-databases-ui"; -import { ResultsView } from "./interface"; import { AstCfgCommands } from "./common/commands"; +import { LocalQueries } from "./local-queries"; type AstCfgOptions = { - queryRunner: QueryRunner; - queryHistoryManager: QueryHistoryManager; - databaseUI: DatabaseUI; - localQueryResultsView: ResultsView; - queryStorageDir: string; - + localQueries: LocalQueries; astViewer: AstViewer; astTemplateProvider: TemplatePrintAstProvider; cfgTemplateProvider: TemplatePrintCfgProvider; }; export function getAstCfgCommands({ - queryRunner, - queryHistoryManager, - databaseUI, - localQueryResultsView, - queryStorageDir, + localQueries, astViewer, astTemplateProvider, cfgTemplateProvider, @@ -59,12 +46,7 @@ export function getAstCfgCommands({ window.activeTextEditor?.document, ); if (res) { - await compileAndRunQuery( - queryRunner, - queryHistoryManager, - databaseUI, - localQueryResultsView, - queryStorageDir, + await localQueries.compileAndRunQuery( false, res[0], progress, diff --git a/extensions/ql-vscode/src/common/logging/logger.ts b/extensions/ql-vscode/src/common/logging/logger.ts index 61187585a..a38d0fb25 100644 --- a/extensions/ql-vscode/src/common/logging/logger.ts +++ b/extensions/ql-vscode/src/common/logging/logger.ts @@ -3,7 +3,8 @@ export interface LogOptions { trailingNewline?: boolean; } -export interface Logger { +/** Minimal logger interface. */ +export interface BaseLogger { /** * Writes the given log message, optionally followed by a newline. * This function is asynchronous and will only resolve once the message is written @@ -15,7 +16,10 @@ export interface Logger { * @param options Optional settings. */ log(message: string, options?: LogOptions): Promise; +} +/** Full logger interface, including a function to show the log in the UI. */ +export interface Logger extends BaseLogger { /** * Reveal the logger channel in the UI. * diff --git a/extensions/ql-vscode/src/contextual/astBuilder.ts b/extensions/ql-vscode/src/contextual/astBuilder.ts index 16416f07a..7e3617761 100644 --- a/extensions/ql-vscode/src/contextual/astBuilder.ts +++ b/extensions/ql-vscode/src/contextual/astBuilder.ts @@ -4,7 +4,7 @@ import { DatabaseItem } from "../local-databases"; import { ChildAstItem, AstItem } from "../astViewer"; import fileRangeFromURI from "./fileRangeFromURI"; import { Uri } from "vscode"; -import { QueryWithResults } from "../run-queries-shared"; +import { QueryOutputDir } from "../run-queries-shared"; /** * A class that wraps a tree of QL results from a query that @@ -14,12 +14,12 @@ export default class AstBuilder { private roots: AstItem[] | undefined; private bqrsPath: string; constructor( - queryResults: QueryWithResults, + outputDir: QueryOutputDir, private cli: CodeQLCliServer, public db: DatabaseItem, public fileName: Uri, ) { - this.bqrsPath = queryResults.query.resultsPaths.resultsPath; + this.bqrsPath = outputDir.bqrsPath; } async getRoots(): Promise { diff --git a/extensions/ql-vscode/src/contextual/locationFinder.ts b/extensions/ql-vscode/src/contextual/locationFinder.ts index 317861613..c6e07c369 100644 --- a/extensions/ql-vscode/src/contextual/locationFinder.ts +++ b/extensions/ql-vscode/src/contextual/locationFinder.ts @@ -19,8 +19,9 @@ import { runContextualQuery, } from "./queryResolver"; import { CancellationToken, LocationLink, Uri } from "vscode"; -import { QueryWithResults } from "../run-queries-shared"; +import { QueryOutputDir } from "../run-queries-shared"; import { QueryRunner } from "../queryRunner"; +import { QueryResultType } from "../pure/new-messages"; export const SELECT_QUERY_NAME = "#select"; export const TEMPLATE_NAME = "selectedSourceFile"; @@ -78,21 +79,23 @@ export async function getLocationsForUriString( token, templates, ); - if (results.successful) { - links.push(...(await getLinksFromResults(results, cli, db, filter))); + if (results.resultType === QueryResultType.SUCCESS) { + links.push( + ...(await getLinksFromResults(results.outputDir, cli, db, filter)), + ); } } return links; } async function getLinksFromResults( - results: QueryWithResults, + outputDir: QueryOutputDir, cli: CodeQLCliServer, db: DatabaseItem, filter: (srcFile: string, destFile: string) => boolean, ): Promise { const localLinks: FullLocationLink[] = []; - const bqrsPath = results.query.resultsPaths.resultsPath; + const bqrsPath = outputDir.bqrsPath; const info = await cli.bqrsInfo(bqrsPath); const selectInfo = getResultSetSchema(SELECT_QUERY_NAME, info); if (isValidSelect(selectInfo)) { diff --git a/extensions/ql-vscode/src/contextual/queryResolver.ts b/extensions/ql-vscode/src/contextual/queryResolver.ts index 40cf87cc6..7738971d0 100644 --- a/extensions/ql-vscode/src/contextual/queryResolver.ts +++ b/extensions/ql-vscode/src/contextual/queryResolver.ts @@ -13,11 +13,10 @@ import { import { KeyType, kindOfKeyType, nameOfKeyType, tagOfKeyType } from "./keyType"; import { CodeQLCliServer } from "../cli"; import { DatabaseItem } from "../local-databases"; -import { extLogger } from "../common"; -import { createInitialQueryInfo } from "../run-queries-shared"; -import { CancellationToken, Uri } from "vscode"; +import { extLogger, TeeLogger } from "../common"; +import { CancellationToken } from "vscode"; import { ProgressCallback } from "../progress"; -import { QueryRunner } from "../queryRunner"; +import { CoreCompletedQuery, QueryRunner } from "../queryRunner"; import { redactableError } from "../pure/errors"; import { QLPACK_FILENAMES } from "../pure/ql"; @@ -169,32 +168,31 @@ export async function runContextualQuery( progress: ProgressCallback, token: CancellationToken, templates: Record, -) { +): Promise { const { packPath, createdTempLockFile } = await resolveContextualQuery( cli, query, ); - const initialInfo = await createInitialQueryInfo( - Uri.file(query), - { - name: db.name, - databaseUri: db.databaseUri.toString(), - }, + const queryRun = qs.createQueryRun( + db.databaseUri.fsPath, + { queryPath: query, quickEvalPosition: undefined }, false, + getOnDiskWorkspaceFolders(), + undefined, + queryStorageDir, + undefined, + templates, ); void extLogger.log( - `Running contextual query ${query}; results will be stored in ${queryStorageDir}`, + `Running contextual query ${query}; results will be stored in ${queryRun.outputDir.querySaveDir}`, ); - const queryResult = await qs.compileAndRunQueryAgainstDatabase( - db, - initialInfo, - queryStorageDir, + const results = await queryRun.evaluate( progress, token, - templates, + new TeeLogger(qs.logger, queryRun.outputDir.logPath), ); if (createdTempLockFile) { await removeTemporaryLockFile(packPath); } - return queryResult; + return results; } diff --git a/extensions/ql-vscode/src/contextual/templateProvider.ts b/extensions/ql-vscode/src/contextual/templateProvider.ts index ddc23346c..0026e5e27 100644 --- a/extensions/ql-vscode/src/contextual/templateProvider.ts +++ b/extensions/ql-vscode/src/contextual/templateProvider.ts @@ -32,8 +32,7 @@ import { runContextualQuery, } from "./queryResolver"; import { isCanary, NO_CACHE_AST_VIEWER } from "../config"; -import { QueryWithResults } from "../run-queries-shared"; -import { QueryRunner } from "../queryRunner"; +import { CoreCompletedQuery, QueryRunner } from "../queryRunner"; /** * Runs templated CodeQL queries to find definitions in @@ -155,17 +154,12 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider { } } -type QueryWithDb = { - query: QueryWithResults; - dbUri: Uri; -}; - /** * Run templated CodeQL queries to produce AST information for * source-language files. */ export class TemplatePrintAstProvider { - private cache: CachedOperation; + private cache: CachedOperation; constructor( private cli: CodeQLCliServer, @@ -173,7 +167,9 @@ export class TemplatePrintAstProvider { private dbm: DatabaseManager, private queryStorageDir: string, ) { - this.cache = new CachedOperation(this.getAst.bind(this)); + this.cache = new CachedOperation( + this.getAst.bind(this), + ); } async provideAst( @@ -186,14 +182,14 @@ export class TemplatePrintAstProvider { "Cannot view the AST. Please select a valid source file inside a CodeQL database.", ); } - const { query, dbUri } = this.shouldCache() + const completedQuery = this.shouldCache() ? await this.cache.get(fileUri.toString(), progress, token) : await this.getAst(fileUri.toString(), progress, token); return new AstBuilder( - query, + completedQuery.outputDir, this.cli, - this.dbm.findDatabaseItem(dbUri)!, + this.dbm.findDatabaseItem(Uri.file(completedQuery.dbPath))!, fileUri, ); } @@ -206,7 +202,7 @@ export class TemplatePrintAstProvider { uriString: string, progress: ProgressCallback, token: CancellationToken, - ): Promise { + ): Promise { const uri = Uri.parse(uriString, true); if (uri.scheme !== zipArchiveScheme) { throw new Error( @@ -242,7 +238,7 @@ export class TemplatePrintAstProvider { [TEMPLATE_NAME]: zippedArchive.pathWithinSourceArchive, }; - const queryResult = await runContextualQuery( + const results = await runContextualQuery( query, db, this.queryStorageDir, @@ -252,10 +248,7 @@ export class TemplatePrintAstProvider { token, templates, ); - return { - query: queryResult, - dbUri: db.databaseUri, - }; + return results; } } diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index a3fc19032..a91663be6 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -115,10 +115,7 @@ import { QueryServerCommands, TestUICommands, } from "./common/commands"; -import { - getLocalQueryCommands, - showResultsForCompletedQuery, -} from "./local-queries"; +import { LocalQueries } from "./local-queries"; import { getAstCfgCommands } from "./ast-cfg-commands"; import { getQueryEditorCommands } from "./query-editor"; import { App } from "./common/app"; @@ -161,6 +158,7 @@ function getCommands( app: App, cliServer: CodeQLCliServer, queryRunner: QueryRunner, + ideServer: LanguageClient, ): BaseCommands { const getCliVersion = async () => { try { @@ -177,9 +175,12 @@ function getCommands( "codeQL.restartQueryServer": async () => withProgress( async (progress: ProgressCallback, token: CancellationToken) => { - // We restart the CLI server too, to ensure they are the same version + // Restart all of the spawned servers: cli, query, and language. cliServer.restartCliServer(); - await queryRunner.restartQueryServer(progress, token); + await Promise.all([ + queryRunner.restartQueryServer(progress, token), + ideServer.restart(), + ]); void showAndLogInformationMessage("CodeQL Query Server restarted.", { outputLogger: queryServerLogger, }); @@ -256,6 +257,7 @@ export interface CodeQLExtensionInterface { readonly distributionManager: DistributionManager; readonly databaseManager: DatabaseManager; readonly databaseUI: DatabaseUI; + readonly localQueries: LocalQueries; readonly variantAnalysisManager: VariantAnalysisManager; readonly dispose: () => void; } @@ -289,7 +291,7 @@ const MIN_VERSION = "1.67.0"; */ export async function activate( ctx: ExtensionContext, -): Promise> { +): Promise { void extLogger.log(`Starting ${extensionId} extension`); if (extension === undefined) { throw new Error(`Can't find extension ${extensionId}`); @@ -358,7 +360,10 @@ export async function activate( ), ); - const variantAnalysisViewSerializer = new VariantAnalysisViewSerializer(ctx); + const variantAnalysisViewSerializer = new VariantAnalysisViewSerializer( + ctx, + app, + ); Window.registerWebviewPanelSerializer( VariantAnalysisView.viewType, variantAnalysisViewSerializer, @@ -379,9 +384,11 @@ export async function activate( }, ); - variantAnalysisViewSerializer.onExtensionLoaded( - codeQlExtension.variantAnalysisManager, - ); + if (codeQlExtension !== undefined) { + variantAnalysisViewSerializer.onExtensionLoaded( + codeQlExtension.variantAnalysisManager, + ); + } return codeQlExtension; } @@ -571,7 +578,7 @@ async function installOrUpdateThenTryActivate( distributionManager: DistributionManager, distributionConfigListener: DistributionConfigListener, config: DistributionUpdateConfig, -): Promise> { +): Promise { await installOrUpdateDistribution(ctx, app, distributionManager, config); try { @@ -585,20 +592,19 @@ async function installOrUpdateThenTryActivate( // Display the warnings even if the extension has already activated. const distributionResult = await getDistributionDisplayingDistributionWarnings(distributionManager); - let extensionInterface: CodeQLExtensionInterface | Record = {}; if ( !beganMainExtensionActivation && distributionResult.kind !== FindDistributionResultKind.NoDistribution ) { - extensionInterface = await activateWithInstalledDistribution( + return await activateWithInstalledDistribution( ctx, app, distributionManager, distributionConfigListener, ); - } else if ( - distributionResult.kind === FindDistributionResultKind.NoDistribution - ) { + } + + if (distributionResult.kind === FindDistributionResultKind.NoDistribution) { registerErrorStubs([checkForUpdatesCommand], (command) => async () => { const installActionName = "Install CodeQL CLI"; const chosenAction = await showAndLogErrorMessage( @@ -622,7 +628,7 @@ async function installOrUpdateThenTryActivate( } }); } - return extensionInterface; + return undefined; } const PACK_GLOBS = [ @@ -708,12 +714,6 @@ async function activateWithInstalledDistribution( void extLogger.log("Initializing query history manager."); const queryHistoryConfigurationListener = new QueryHistoryConfigListener(); ctx.subscriptions.push(queryHistoryConfigurationListener); - const showResults = async (item: CompletedLocalQueryInfo) => - showResultsForCompletedQuery( - localQueryResultsView, - item, - WebviewReveal.Forced, - ); const queryStorageDir = join(ctx.globalStorageUri.fsPath, "queries"); await ensureDir(queryStorageDir); @@ -787,8 +787,10 @@ async function activateWithInstalledDistribution( ctx, queryHistoryConfigurationListener, labelProvider, - async (from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo) => - showResultsForComparison(compareView, from, to), + async ( + from: CompletedLocalQueryInfo, + to: CompletedLocalQueryInfo, + ): Promise => showResultsForComparison(compareView, from, to), ); ctx.subscriptions.push(qhm); @@ -809,7 +811,8 @@ async function activateWithInstalledDistribution( cliServer, queryServerLogger, labelProvider, - showResults, + async (item: CompletedLocalQueryInfo) => + localQueries.showResultsForCompletedQuery(item, WebviewReveal.Forced), ); ctx.subscriptions.push(compareView); @@ -845,6 +848,18 @@ async function activateWithInstalledDistribution( true, ); + const localQueries = new LocalQueries( + app, + qs, + qhm, + dbm, + cliServer, + databaseUI, + localQueryResultsView, + queryStorageDir, + ); + ctx.subscriptions.push(localQueries); + void extLogger.log("Initializing QLTest interface."); const testExplorerExtension = extensions.getExtension( testExplorerExtensionId, @@ -876,16 +891,16 @@ async function activateWithInstalledDistribution( ctx.subscriptions.push(astViewer); - const summaryLanguageSupport = new SummaryLanguageSupport(); + const summaryLanguageSupport = new SummaryLanguageSupport(app); ctx.subscriptions.push(summaryLanguageSupport); - const mockServer = new VSCodeMockGitHubApiServer(ctx); + const mockServer = new VSCodeMockGitHubApiServer(app); ctx.subscriptions.push(mockServer); void extLogger.log("Registering top-level command palette commands."); const allCommands: AllExtensionCommands = { - ...getCommands(app, cliServer, qs), + ...getCommands(app, cliServer, qs, client), ...getQueryEditorCommands({ commandManager: app.commands, queryRunner: qs, @@ -898,11 +913,7 @@ async function activateWithInstalledDistribution( ...databaseUI.getCommands(), ...dbModule.getCommands(), ...getAstCfgCommands({ - queryRunner: qs, - queryHistoryManager: qhm, - databaseUI, - localQueryResultsView, - queryStorageDir, + localQueries, astViewer, astTemplateProvider, cfgTemplateProvider, @@ -922,16 +933,7 @@ async function activateWithInstalledDistribution( } const queryServerCommands: QueryServerCommands = { - ...getLocalQueryCommands({ - app, - queryRunner: qs, - queryHistoryManager: qhm, - databaseManager: dbm, - cliServer, - databaseUI, - localQueryResultsView, - queryStorageDir, - }), + ...localQueries.getCommands(), }; for (const [commandName, command] of Object.entries(queryServerCommands)) { @@ -981,6 +983,7 @@ async function activateWithInstalledDistribution( return { ctx, cliServer, + localQueries, qs, distributionManager, databaseManager: dbm, @@ -1074,6 +1077,7 @@ async function createQueryServer( ); if (await cliServer.cliConstraints.supportsNewQueryServer()) { const qs = new QueryServerClient( + app, qlConfigurationListener, cliServer, qsOpts, diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index 2576f6b48..62f8d0d8d 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -6,7 +6,7 @@ import { writeFile, opendir, } from "fs-extra"; -import { promise as glob } from "glob-promise"; +import { glob } from "glob"; import { load } from "js-yaml"; import { join, basename } from "path"; import { dirSync } from "tmp-promise"; @@ -304,7 +304,7 @@ export async function prepareCodeTour( return; } - const tutorialWorkspaceUri = Uri.parse(tutorialWorkspacePath); + const tutorialWorkspaceUri = Uri.file(tutorialWorkspacePath); void extLogger.log( `In prepareCodeTour() method, going to open the tutorial workspace file: ${tutorialWorkspacePath}`, @@ -515,7 +515,9 @@ export async function getQlPackForDbscheme( export async function getPrimaryDbscheme( datasetFolder: string, ): Promise { - const dbschemes = await glob(join(datasetFolder, "*.dbscheme")); + const dbschemes = await glob("*.dbscheme", { + cwd: datasetFolder, + }); if (dbschemes.length < 1) { throw new Error( @@ -752,7 +754,7 @@ export async function tryGetQueryMetadata( * Creates a file in the query directory that indicates when this query was created. * This is important for keeping track of when queries should be removed. * - * @param queryPath The directory that will containt all files relevant to a query result. + * @param queryPath The directory that will contain all files relevant to a query result. * It does not need to exist. */ export async function createTimestampFile(storagePath: string) { diff --git a/extensions/ql-vscode/src/legacy-query-server/legacyRunner.ts b/extensions/ql-vscode/src/legacy-query-server/legacyRunner.ts index f12434822..4c47d592d 100644 --- a/extensions/ql-vscode/src/legacy-query-server/legacyRunner.ts +++ b/extensions/ql-vscode/src/legacy-query-server/legacyRunner.ts @@ -1,18 +1,19 @@ import { CancellationToken } from "vscode"; +import { CodeQLCliServer } from "../cli"; import { ProgressCallback } from "../progress"; +import { Logger } from "../common"; import { DatabaseItem } from "../local-databases"; import { Dataset, deregisterDatabases, registerDatabases, } from "../pure/legacy-messages"; -import { InitialQueryInfo, LocalQueryInfo } from "../query-results"; -import { QueryRunner } from "../queryRunner"; -import { QueryWithResults } from "../run-queries-shared"; +import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner"; +import { QueryOutputDir } from "../run-queries-shared"; import { QueryServerClient } from "./queryserver-client"; import { clearCacheInDatabase, - compileAndRunQueryAgainstDatabase, + compileAndRunQueryAgainstDatabaseCore, } from "./run-queries"; import { upgradeDatabaseExplicit } from "./upgrades"; @@ -21,10 +22,18 @@ export class LegacyQueryRunner extends QueryRunner { super(); } - get cliServer() { + get cliServer(): CodeQLCliServer { return this.qs.cliServer; } + get customLogDirectory(): string | undefined { + return undefined; + } + + get logger(): Logger { + return this.qs.logger; + } + async restartQueryServer( progress: ProgressCallback, token: CancellationToken, @@ -47,25 +56,31 @@ export class LegacyQueryRunner extends QueryRunner { ): Promise { await clearCacheInDatabase(this.qs, dbItem, progress, token); } - async compileAndRunQueryAgainstDatabase( - dbItem: DatabaseItem, - initialInfo: InitialQueryInfo, - queryStorageDir: string, + + public async compileAndRunQueryAgainstDatabaseCore( + dbPath: string, + query: CoreQueryTarget, + additionalPacks: string[], + extensionPacks: string[] | undefined, + generateEvalLog: boolean, + outputDir: QueryOutputDir, progress: ProgressCallback, token: CancellationToken, - templates?: Record, - queryInfo?: LocalQueryInfo, - ): Promise { - return await compileAndRunQueryAgainstDatabase( - this.qs.cliServer, + templates: Record | undefined, + logger: Logger, + ): Promise { + return await compileAndRunQueryAgainstDatabaseCore( this.qs, - dbItem, - initialInfo, - queryStorageDir, + dbPath, + query, + generateEvalLog, + additionalPacks, + extensionPacks, + outputDir, progress, token, templates, - queryInfo, + logger, ); } diff --git a/extensions/ql-vscode/src/legacy-query-server/run-queries.ts b/extensions/ql-vscode/src/legacy-query-server/run-queries.ts index e2f3fb5f5..b756712ec 100644 --- a/extensions/ql-vscode/src/legacy-query-server/run-queries.ts +++ b/extensions/ql-vscode/src/legacy-query-server/run-queries.ts @@ -1,28 +1,168 @@ import * as tmp from "tmp-promise"; -import { basename, join } from "path"; +import { basename } from "path"; import { CancellationToken, Uri } from "vscode"; import { LSPErrorCodes, ResponseError } from "vscode-languageclient"; import * as cli from "../cli"; -import { DatabaseItem } from "../local-databases"; import { - getOnDiskWorkspaceFolders, - showAndLogErrorMessage, + DatabaseContentsWithDbScheme, + DatabaseItem, + DatabaseResolver, +} from "../local-databases"; +import { showAndLogExceptionWithTelemetry, showAndLogWarningMessage, - tryGetQueryMetadata, upgradesTmpDir, } from "../helpers"; import { ProgressCallback } from "../progress"; import { QueryMetadata } from "../pure/interface-types"; -import { extLogger, Logger, TeeLogger } from "../common"; +import { extLogger, Logger } from "../common"; import * as messages from "../pure/legacy-messages"; -import { InitialQueryInfo, LocalQueryInfo } from "../query-results"; +import * as newMessages from "../pure/new-messages"; import * as qsClient from "./queryserver-client"; import { asError, getErrorMessage } from "../pure/helpers-pure"; import { compileDatabaseUpgradeSequence } from "./upgrades"; -import { QueryEvaluationInfo, QueryWithResults } from "../run-queries-shared"; +import { QueryEvaluationInfo, QueryOutputDir } from "../run-queries-shared"; import { redactableError } from "../pure/errors"; +import { CoreQueryResults, CoreQueryTarget } from "../queryRunner"; +import { Position } from "../pure/messages-shared"; + +export async function compileQuery( + qs: qsClient.QueryServerClient, + program: messages.QlProgram, + quickEvalPosition: Position | undefined, + outputDir: QueryOutputDir, + progress: ProgressCallback, + token: CancellationToken, + logger: Logger, +): Promise { + let compiled: messages.CheckQueryResult | undefined; + try { + const target: messages.CompilationTarget = quickEvalPosition + ? { + quickEval: { quickEvalPos: quickEvalPosition }, + } + : { query: {} }; + const params: messages.CompileQueryParams = { + compilationOptions: { + computeNoLocationUrls: true, + failOnWarnings: false, + fastCompilation: false, + includeDilInQlo: true, + localChecking: false, + noComputeGetUrl: false, + noComputeToString: false, + computeDefaultStrings: true, + emitDebugInfo: true, + }, + extraOptions: { + timeoutSecs: qs.config.timeoutSecs, + }, + queryToCheck: program, + resultPath: outputDir.compileQueryPath, + target, + }; + + // Update the active query logger every time there is a new request to compile. + // This isn't ideal because in situations where there are queries running + // in parallel, each query's log messages are interleaved. Fixing this + // properly will require a change in the query server. + qs.activeQueryLogger = logger; + compiled = await qs.sendRequest( + messages.compileQuery, + params, + token, + progress, + ); + } finally { + void logger.log(" - - - COMPILATION DONE - - - "); + } + return (compiled?.messages || []).filter( + (msg) => msg.severity === messages.Severity.ERROR, + ); +} + +async function runQuery( + qs: qsClient.QueryServerClient, + upgradeQlo: string | undefined, + availableMlModels: cli.MlModelInfo[], + dbContents: DatabaseContentsWithDbScheme, + templates: Record | undefined, + generateEvalLog: boolean, + outputDir: QueryOutputDir, + progress: ProgressCallback, + token: CancellationToken, +): Promise { + let result: messages.EvaluationResult | null = null; + + const logPath = outputDir.logPath; + + const callbackId = qs.registerCallback((res) => { + result = { + ...res, + logFileLocation: logPath, + }; + }); + + const availableMlModelUris: messages.MlModel[] = availableMlModels.map( + (model) => ({ uri: Uri.file(model.path).toString(true) }), + ); + + const queryToRun: messages.QueryToRun = { + resultsPath: outputDir.bqrsPath, + qlo: Uri.file(outputDir.compileQueryPath).toString(), + compiledUpgrade: upgradeQlo && Uri.file(upgradeQlo).toString(), + allowUnknownTemplates: true, + templateValues: createSimpleTemplates(templates), + availableMlModels: availableMlModelUris, + id: callbackId, + timeoutSecs: qs.config.timeoutSecs, + }; + + const dataset: messages.Dataset = { + dbDir: dbContents.datasetUri.fsPath, + workingSet: "default", + }; + if ( + generateEvalLog && + (await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) + ) { + await qs.sendRequest(messages.startLog, { + db: dataset, + logPath: outputDir.evalLogPath, + }); + } + const params: messages.EvaluateQueriesParams = { + db: dataset, + evaluateId: callbackId, + queries: [queryToRun], + stopOnError: false, + useSequenceHint: false, + }; + try { + await qs.sendRequest(messages.runQueries, params, token, progress); + } finally { + qs.unRegisterCallback(callbackId); + if ( + generateEvalLog && + (await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) + ) { + await qs.sendRequest(messages.endLog, { + db: dataset, + logPath: outputDir.evalLogPath, + }); + } + } + return ( + result || { + evaluationTime: 0, + message: "No result from server", + queryId: -1, + runId: callbackId, + resultType: messages.QueryResultType.OTHER_ERROR, + } + ); +} /** * A collection of evaluation-time information about a query, @@ -33,14 +173,14 @@ import { redactableError } from "../pure/errors"; export class QueryInProgress { public queryEvalInfo: QueryEvaluationInfo; /** - * Note that in the {@link deserializeQueryHistory} method, we create a QueryEvaluationInfo instance + * Note that in the {@link readQueryHistoryFromFile} method, we create a QueryEvaluationInfo instance * by explicitly setting the prototype in order to avoid calling this constructor. */ constructor( readonly querySaveDir: string, readonly dbItemPath: string, databaseHasMetadataFile: boolean, - readonly queryDbscheme: string, // the dbscheme file the query expects, based on library path resolution + readonly queryDbscheme: string, // the dbscheme file the query expects, ba`sed on library path resolution readonly quickEvalPosition?: messages.Position, readonly metadata?: QueryMetadata, readonly templates?: Record, @@ -54,162 +194,6 @@ export class QueryInProgress { ); /**/ } - - get compiledQueryPath() { - return this.queryEvalInfo.compileQueryPath; - } - - async run( - qs: qsClient.QueryServerClient, - upgradeQlo: string | undefined, - availableMlModels: cli.MlModelInfo[], - dbItem: DatabaseItem, - progress: ProgressCallback, - token: CancellationToken, - logger: Logger, - queryInfo: LocalQueryInfo | undefined, - ): Promise { - if (!dbItem.contents || dbItem.error) { - throw new Error("Can't run query on invalid database."); - } - - let result: messages.EvaluationResult | null = null; - - const callbackId = qs.registerCallback((res) => { - result = { - ...res, - logFileLocation: this.queryEvalInfo.logPath, - }; - }); - - const availableMlModelUris: messages.MlModel[] = availableMlModels.map( - (model) => ({ uri: Uri.file(model.path).toString(true) }), - ); - - const queryToRun: messages.QueryToRun = { - resultsPath: this.queryEvalInfo.resultsPaths.resultsPath, - qlo: Uri.file(this.compiledQueryPath).toString(), - compiledUpgrade: upgradeQlo && Uri.file(upgradeQlo).toString(), - allowUnknownTemplates: true, - templateValues: createSimpleTemplates(this.templates), - availableMlModels: availableMlModelUris, - id: callbackId, - timeoutSecs: qs.config.timeoutSecs, - }; - - const dataset: messages.Dataset = { - dbDir: dbItem.contents.datasetUri.fsPath, - workingSet: "default", - }; - if ( - queryInfo && - (await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) - ) { - await qs.sendRequest(messages.startLog, { - db: dataset, - logPath: this.queryEvalInfo.evalLogPath, - }); - } - const params: messages.EvaluateQueriesParams = { - db: dataset, - evaluateId: callbackId, - queries: [queryToRun], - stopOnError: false, - useSequenceHint: false, - }; - try { - await qs.sendRequest(messages.runQueries, params, token, progress); - if (qs.config.customLogDirectory) { - void showAndLogWarningMessage( - `Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${this.queryEvalInfo.logPath}.`, - ); - } - } finally { - qs.unRegisterCallback(callbackId); - if ( - queryInfo && - (await qs.cliServer.cliConstraints.supportsPerQueryEvalLog()) - ) { - await qs.sendRequest(messages.endLog, { - db: dataset, - logPath: this.queryEvalInfo.evalLogPath, - }); - if (await this.queryEvalInfo.hasEvalLog()) { - await this.queryEvalInfo.addQueryLogs( - queryInfo, - qs.cliServer, - logger, - ); - } else { - void showAndLogWarningMessage( - `Failed to write structured evaluator log to ${this.queryEvalInfo.evalLogPath}.`, - ); - } - } - } - return ( - result || { - evaluationTime: 0, - message: "No result from server", - queryId: -1, - runId: callbackId, - resultType: messages.QueryResultType.OTHER_ERROR, - } - ); - } - - async compile( - qs: qsClient.QueryServerClient, - program: messages.QlProgram, - progress: ProgressCallback, - token: CancellationToken, - logger: Logger, - ): Promise { - let compiled: messages.CheckQueryResult | undefined; - try { - const target = this.quickEvalPosition - ? { - quickEval: { quickEvalPos: this.quickEvalPosition }, - } - : { query: {} }; - const params: messages.CompileQueryParams = { - compilationOptions: { - computeNoLocationUrls: true, - failOnWarnings: false, - fastCompilation: false, - includeDilInQlo: true, - localChecking: false, - noComputeGetUrl: false, - noComputeToString: false, - computeDefaultStrings: true, - emitDebugInfo: true, - }, - extraOptions: { - timeoutSecs: qs.config.timeoutSecs, - }, - queryToCheck: program, - resultPath: this.compiledQueryPath, - target, - }; - - // Update the active query logger every time there is a new request to compile. - // This isn't ideal because in situations where there are queries running - // in parallel, each query's log messages are interleaved. Fixing this - // properly will require a change in the query server. - qs.activeQueryLogger = logger; - compiled = await qs.sendRequest( - messages.compileQuery, - params, - token, - progress, - ); - } finally { - void logger.log(" - - - COMPILATION DONE - - - "); - } - return (compiled?.messages || []).filter( - (msg) => msg.severity === messages.Severity.ERROR, - ); - } } export async function clearCacheInDatabase( @@ -237,10 +221,10 @@ export async function clearCacheInDatabase( function reportNoUpgradePath( qlProgram: messages.QlProgram, - query: QueryInProgress, + queryDbscheme: string, ): void { throw new Error( - `Query ${qlProgram.queryPath} expects database scheme ${query.queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`, + `Query ${qlProgram.queryPath} expects database scheme ${queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`, ); } @@ -250,32 +234,27 @@ function reportNoUpgradePath( async function compileNonDestructiveUpgrade( qs: qsClient.QueryServerClient, upgradeTemp: tmp.DirectoryResult, - query: QueryInProgress, + queryDbscheme: string, qlProgram: messages.QlProgram, - dbItem: DatabaseItem, + dbContents: DatabaseContentsWithDbScheme, progress: ProgressCallback, token: CancellationToken, ): Promise { - if (!dbItem?.contents?.dbSchemeUri) { - throw new Error("Database is invalid, and cannot be upgraded."); - } - // Dependencies may exist outside of the workspace and they are always on the resolved search path. const upgradesPath = qlProgram.libraryPath; const { scripts, matchesTarget } = await qs.cliServer.resolveUpgrades( - dbItem.contents.dbSchemeUri.fsPath, + dbContents.dbSchemeUri.fsPath, upgradesPath, true, - query.queryDbscheme, + queryDbscheme, ); if (!matchesTarget) { - reportNoUpgradePath(qlProgram, query); + reportNoUpgradePath(qlProgram, queryDbscheme); } const result = await compileDatabaseUpgradeSequence( qs, - dbItem, scripts, upgradeTemp, progress, @@ -286,34 +265,74 @@ async function compileNonDestructiveUpgrade( throw new Error(error); } // We can upgrade to the actual target - qlProgram.dbschemePath = query.queryDbscheme; + qlProgram.dbschemePath = queryDbscheme; // We are new enough that we will always support single file upgrades. return result.compiledUpgrade; } -export async function compileAndRunQueryAgainstDatabase( - cliServer: cli.CodeQLCliServer, +function translateLegacyResult( + legacyResult: messages.EvaluationResult, +): Omit { + let newResultType: newMessages.QueryResultType; + let newMessage = legacyResult.message; + switch (legacyResult.resultType) { + case messages.QueryResultType.SUCCESS: + newResultType = newMessages.QueryResultType.SUCCESS; + break; + case messages.QueryResultType.CANCELLATION: + newResultType = newMessages.QueryResultType.CANCELLATION; + break; + case messages.QueryResultType.OOM: + newResultType = newMessages.QueryResultType.OOM; + break; + case messages.QueryResultType.TIMEOUT: + // This is the only legacy result type that doesn't exist for the new query server. Format the + // message here, and let the later code treat it as `OTHER_ERROR`. + newResultType = newMessages.QueryResultType.OTHER_ERROR; + newMessage = `timed out after ${Math.round( + legacyResult.evaluationTime / 1000, + )} seconds`; + break; + case messages.QueryResultType.OTHER_ERROR: + default: + newResultType = newMessages.QueryResultType.OTHER_ERROR; + break; + } + + return { + resultType: newResultType, + message: newMessage, + evaluationTime: legacyResult.evaluationTime, + }; +} + +export async function compileAndRunQueryAgainstDatabaseCore( qs: qsClient.QueryServerClient, - dbItem: DatabaseItem, - initialInfo: InitialQueryInfo, - queryStorageDir: string, + dbPath: string, + query: CoreQueryTarget, + generateEvalLog: boolean, + additionalPacks: string[], + extensionPacks: string[] | undefined, + outputDir: QueryOutputDir, progress: ProgressCallback, token: CancellationToken, - templates?: Record, - queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query. -): Promise { - if (!dbItem.contents || !dbItem.contents.dbSchemeUri) { - throw new Error( - `Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`, + templates: Record | undefined, + logger: Logger, +): Promise { + if (extensionPacks !== undefined && extensionPacks.length > 0) { + await showAndLogWarningMessage( + "Legacy query server does not support extension packs.", ); } - // Get the workspace folder paths. - const diskWorkspaceFolders = getOnDiskWorkspaceFolders(); + const dbContents = await DatabaseResolver.resolveDatabaseContents( + Uri.file(dbPath), + ); + // Figure out the library path for the query. - const packConfig = await cliServer.resolveLibraryPath( - diskWorkspaceFolders, - initialInfo.queryPath, + const packConfig = await qs.cliServer.resolveLibraryPath( + additionalPacks, + query.queryPath, ); if (!packConfig.dbscheme) { @@ -327,17 +346,15 @@ export async function compileAndRunQueryAgainstDatabase( // won't trigger this check) // This test will produce confusing results if we ever change the name of the database schema files. const querySchemaName = basename(packConfig.dbscheme); - const dbSchemaName = basename(dbItem.contents.dbSchemeUri.fsPath); + const dbSchemaName = basename(dbContents.dbSchemeUri?.fsPath); if (querySchemaName !== dbSchemaName) { void extLogger.log( `Query schema was ${querySchemaName}, but database schema was ${dbSchemaName}.`, ); throw new Error( `The query ${basename( - initialInfo.queryPath, - )} cannot be run against the selected database (${ - dbItem.name - }): their target languages are different. Please select a different database and try again.`, + query.queryPath, + )} cannot be run against the selected database: their target languages are different. Please select a different database and try again.`, ); } @@ -349,20 +366,14 @@ export async function compileAndRunQueryAgainstDatabase( // Since we are compiling and running a query against a database, // we use the database's DB scheme here instead of the DB scheme // from the current document's project. - dbschemePath: dbItem.contents.dbSchemeUri.fsPath, - queryPath: initialInfo.queryPath, + dbschemePath: dbContents.dbSchemeUri.fsPath, + queryPath: query.queryPath, }; - // Read the query metadata if possible, to use in the UI. - const metadata = await tryGetQueryMetadata(cliServer, qlProgram.queryPath); - let availableMlModels: cli.MlModelInfo[] = []; try { availableMlModels = ( - await cliServer.resolveMlModels( - diskWorkspaceFolders, - initialInfo.queryPath, - ) + await qs.cliServer.resolveMlModels(additionalPacks, query.queryPath) ).models; if (availableMlModels.length) { void extLogger.log( @@ -381,57 +392,53 @@ export async function compileAndRunQueryAgainstDatabase( ); } - const hasMetadataFile = await dbItem.hasMetadataFile(); - const query = new QueryInProgress( - join(queryStorageDir, initialInfo.id), - dbItem.databaseUri.fsPath, - hasMetadataFile, - packConfig.dbscheme, - initialInfo.quickEvalPosition, - metadata, - templates, - ); - const logger = new TeeLogger(qs.logger, query.queryEvalInfo.logPath); - - await query.queryEvalInfo.createTimestampFile(); - let upgradeDir: tmp.DirectoryResult | undefined; try { upgradeDir = await tmp.dir({ dir: upgradesTmpDir, unsafeCleanup: true }); const upgradeQlo = await compileNonDestructiveUpgrade( qs, upgradeDir, - query, + packConfig.dbscheme, qlProgram, - dbItem, + dbContents, progress, token, ); let errors; try { - errors = await query.compile(qs, qlProgram, progress, token, logger); + errors = await compileQuery( + qs, + qlProgram, + query.quickEvalPosition, + outputDir, + progress, + token, + logger, + ); } catch (e) { if ( e instanceof ResponseError && e.code === LSPErrorCodes.RequestCancelled ) { - return createSyntheticResult(query, "Query cancelled"); + return createSyntheticResult("Query cancelled"); } else { throw e; } } if (errors.length === 0) { - const result = await query.run( + const result = await runQuery( qs, upgradeQlo, availableMlModels, - dbItem, + dbContents, + templates, + generateEvalLog, + outputDir, progress, token, - logger, - queryInfo, ); + if (result.resultType !== messages.QueryResultType.SUCCESS) { const error = result.message ? redactableError`${result.message}` @@ -439,22 +446,15 @@ export async function compileAndRunQueryAgainstDatabase( void extLogger.log(error.fullMessage); void showAndLogExceptionWithTelemetry(error); } - const message = formatLegacyMessage(result); - return { - query: query.queryEvalInfo, - message, - result, - successful: result.resultType === messages.QueryResultType.SUCCESS, - logFileLocation: result.logFileLocation, - }; + return translateLegacyResult(result); } else { // Error dialogs are limited in size and scrollability, // so we include a general description of the problem, // and direct the user to the output window for the detailed compilation messages. // However we don't show quick eval errors there so we need to display them anyway. void logger.log( - `Failed to compile query ${initialInfo.queryPath} against database scheme ${qlProgram.dbschemePath}:`, + `Failed to compile query ${query.queryPath} against database scheme ${qlProgram.dbschemePath}:`, ); const formattedMessages: string[] = []; @@ -465,22 +465,12 @@ export async function compileAndRunQueryAgainstDatabase( formattedMessages.push(formatted); void logger.log(formatted); } - if (initialInfo.isQuickEval && formattedMessages.length <= 2) { - // If there are more than 2 error messages, they will not be displayed well in a popup - // and will be trimmed by the function displaying the error popup. Accordingly, we only - // try to show the errors if there are 2 or less, otherwise we direct the user to the log. - void showAndLogErrorMessage( - `Quick evaluation compilation failed: ${formattedMessages.join( - "\n", - )}`, - ); - } else { - void showAndLogErrorMessage( - (initialInfo.isQuickEval ? "Quick evaluation" : "Query") + - compilationFailedErrorTail, - ); - } - return createSyntheticResult(query, "Query had compilation errors"); + + return { + evaluationTime: 0, + resultType: newMessages.QueryResultType.COMPILATION_ERROR, + message: formattedMessages[0], + }; } } finally { try { @@ -493,11 +483,6 @@ export async function compileAndRunQueryAgainstDatabase( } } -const compilationFailedErrorTail = - " compilation failed. Please make sure there are no errors in the query, the database is up to date," + - " and the query and database use the same target language. For more details on the error, go to View > Output," + - " and choose CodeQL Query Server from the dropdown."; - export function formatLegacyMessage(result: messages.EvaluationResult) { switch (result.resultType) { case messages.QueryResultType.CANCELLATION: @@ -521,21 +506,11 @@ export function formatLegacyMessage(result: messages.EvaluationResult) { /** * Create a synthetic result for a query that failed to compile. */ -function createSyntheticResult( - query: QueryInProgress, - message: string, -): QueryWithResults { +function createSyntheticResult(message: string): CoreQueryResults { return { - query: query.queryEvalInfo, + evaluationTime: 0, + resultType: newMessages.QueryResultType.OTHER_ERROR, message, - result: { - evaluationTime: 0, - queryId: 0, - resultType: messages.QueryResultType.OTHER_ERROR, - message, - runId: 0, - }, - successful: false, }; } diff --git a/extensions/ql-vscode/src/legacy-query-server/upgrades.ts b/extensions/ql-vscode/src/legacy-query-server/upgrades.ts index 6b70bf58b..a23df1ab2 100644 --- a/extensions/ql-vscode/src/legacy-query-server/upgrades.ts +++ b/extensions/ql-vscode/src/legacy-query-server/upgrades.ts @@ -27,18 +27,11 @@ const MAX_UPGRADE_MESSAGE_LINES = 10; */ export async function compileDatabaseUpgradeSequence( qs: qsClient.QueryServerClient, - dbItem: DatabaseItem, resolvedSequence: string[], currentUpgradeTmp: tmp.DirectoryResult, progress: ProgressCallback, token: vscode.CancellationToken, ): Promise { - if ( - dbItem.contents === undefined || - dbItem.contents.dbSchemeUri === undefined - ) { - throw new Error("Database is invalid, and cannot be upgraded."); - } // If possible just compile the upgrade sequence return await qs.sendRequest( messages.compileUpgradeSequence, diff --git a/extensions/ql-vscode/src/local-databases.ts b/extensions/ql-vscode/src/local-databases.ts index 918ddd5fc..a4e32a5d0 100644 --- a/extensions/ql-vscode/src/local-databases.ts +++ b/extensions/ql-vscode/src/local-databases.ts @@ -1,5 +1,5 @@ import { pathExists, stat, remove } from "fs-extra"; -import { promise as glob } from "glob-promise"; +import { glob } from "glob"; import { join, basename, resolve, relative, dirname, extname } from "path"; import * as vscode from "vscode"; import * as cli from "./cli"; @@ -95,6 +95,10 @@ export interface DatabaseContents { dbSchemeUri?: vscode.Uri; } +export interface DatabaseContentsWithDbScheme extends DatabaseContents { + dbSchemeUri: vscode.Uri; // Always present +} + /** * An error thrown when we cannot find a valid database in a putative * database directory. @@ -165,7 +169,7 @@ async function getDbSchemeFiles(dbDirectory: string): Promise { export class DatabaseResolver { public static async resolveDatabaseContents( uri: vscode.Uri, - ): Promise { + ): Promise { if (uri.scheme !== "file") { throw new Error( `Database URI scheme '${uri.scheme}' not supported; only 'file' URIs are supported.`, @@ -199,9 +203,12 @@ export class DatabaseResolver { `Database '${databasePath}' contains multiple CodeQL dbschemes under '${dbPath}'.`, ); } else { - contents.dbSchemeUri = vscode.Uri.file(resolve(dbPath, dbSchemeFiles[0])); + const dbSchemeUri = vscode.Uri.file(resolve(dbPath, dbSchemeFiles[0])); + return { + ...contents, + dbSchemeUri, + }; } - return contents; } public static async resolveDatabase( @@ -1001,6 +1008,15 @@ export class DatabaseManager extends DisposableObject { }); } + public async removeAllDatabases( + progress: ProgressCallback, + token: vscode.CancellationToken, + ) { + for (const item of this.databaseItems) { + await this.removeDatabaseItem(progress, token, item); + } + } + private async deregisterDatabase( progress: ProgressCallback, token: vscode.CancellationToken, diff --git a/extensions/ql-vscode/src/local-queries.ts b/extensions/ql-vscode/src/local-queries.ts index 380c4d21d..01e0d886a 100644 --- a/extensions/ql-vscode/src/local-queries.ts +++ b/extensions/ql-vscode/src/local-queries.ts @@ -7,96 +7,266 @@ import { Uri, window, } from "vscode"; -import { extLogger } from "./common"; +import { BaseLogger, extLogger, Logger, TeeLogger } from "./common"; import { MAX_QUERIES } from "./config"; import { gatherQlFiles } from "./pure/files"; import { basename } from "path"; import { + createTimestampFile, findLanguage, + getOnDiskWorkspaceFolders, showAndLogErrorMessage, + showAndLogExceptionWithTelemetry, showAndLogWarningMessage, showBinaryChoiceDialog, + tryGetQueryMetadata, } from "./helpers"; import { displayQuickQuery } from "./quick-query"; -import { QueryRunner } from "./queryRunner"; +import { + CoreCompletedQuery, + CoreQueryResults, + QueryRunner, +} from "./queryRunner"; import { QueryHistoryManager } from "./query-history/query-history-manager"; import { DatabaseUI } from "./local-databases-ui"; import { ResultsView } from "./interface"; import { DatabaseItem, DatabaseManager } from "./local-databases"; -import { createInitialQueryInfo } from "./run-queries-shared"; +import { + createInitialQueryInfo, + determineSelectedQuery, + EvaluatorLogPaths, + generateEvalLogSummaries, + logEndSummary, + QueryEvaluationInfo, + QueryOutputDir, + QueryWithResults, + SelectedQuery, +} from "./run-queries-shared"; import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results"; import { WebviewReveal } from "./interface-utils"; import { asError, getErrorMessage } from "./pure/helpers-pure"; import { CodeQLCliServer } from "./cli"; import { LocalQueryCommands } from "./common/commands"; import { App } from "./common/app"; +import { DisposableObject } from "./pure/disposable-object"; +import { QueryResultType } from "./pure/new-messages"; +import { redactableError } from "./pure/errors"; -type LocalQueryOptions = { - app: App; - queryRunner: QueryRunner; - queryHistoryManager: QueryHistoryManager; - databaseManager: DatabaseManager; - cliServer: CodeQLCliServer; - databaseUI: DatabaseUI; - localQueryResultsView: ResultsView; - queryStorageDir: string; -}; +interface DatabaseQuickPickItem extends QuickPickItem { + databaseItem: DatabaseItem; +} -export function getLocalQueryCommands({ - app, - queryRunner, - queryHistoryManager, - databaseManager, - cliServer, - databaseUI, - localQueryResultsView, - queryStorageDir, -}: LocalQueryOptions): LocalQueryCommands { - const runQuery = async (uri: Uri | undefined) => - withProgress( - async (progress, token) => { - await compileAndRunQuery( - queryRunner, - queryHistoryManager, - databaseUI, - localQueryResultsView, - queryStorageDir, - false, - uri, - progress, - token, - undefined, +function formatResultMessage(result: CoreQueryResults): string { + switch (result.resultType) { + case QueryResultType.CANCELLATION: + return `cancelled after ${Math.round( + result.evaluationTime / 1000, + )} seconds`; + case QueryResultType.OOM: + return "out of memory"; + case QueryResultType.SUCCESS: + return `finished in ${Math.round(result.evaluationTime / 1000)} seconds`; + case QueryResultType.COMPILATION_ERROR: + return `compilation failed: ${result.message}`; + case QueryResultType.OTHER_ERROR: + default: + return result.message ? `failed: ${result.message}` : "failed"; + } +} + +/** + * Tracks the evaluation of a local query, including its interactions with the UI. + * + * The client creates an instance of `LocalQueryRun` when the evaluation starts, and then invokes + * the `complete()` function once the query has completed (successfully or otherwise). + * + * Having the client tell the `LocalQueryRun` when the evaluation is complete, rather than having + * the `LocalQueryRun` manage the evaluation itself, may seem a bit clunky. It's done this way + * because once we move query evaluation into a Debug Adapter, the debugging UI drives the + * evaluation, and we can only respond to events from the debug adapter. + */ +export class LocalQueryRun { + public constructor( + private readonly outputDir: QueryOutputDir, + private readonly localQueries: LocalQueries, + private readonly queryInfo: LocalQueryInfo, + private readonly dbItem: DatabaseItem, + public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output + private readonly queryHistoryManager: QueryHistoryManager, + private readonly cliServer: CodeQLCliServer, + ) {} + + /** + * Updates the UI based on the results of the query evaluation. This creates the evaluator log + * summaries, updates the query history item for the evaluation with the results and evaluation + * time, and displays the results view. + * + * This function must be called when the evaluation completes, whether the evaluation was + * successful or not. + * */ + public async complete(results: CoreQueryResults): Promise { + const evalLogPaths = await this.summarizeEvalLog( + results.resultType, + this.outputDir, + this.logger, + ); + if (evalLogPaths !== undefined) { + this.queryInfo.setEvaluatorLogPaths(evalLogPaths); + } + const queryWithResults = await this.getCompletedQueryInfo(results); + this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults); + await this.localQueries.showResultsForCompletedQuery( + this.queryInfo as CompletedLocalQueryInfo, + WebviewReveal.Forced, + ); + // Note we must update the query history view after showing results as the + // display and sorting might depend on the number of results + await this.queryHistoryManager.refreshTreeView(); + } + + /** + * Updates the UI in the case where query evaluation throws an exception. + */ + public async fail(err: Error): Promise { + err.message = `Error running query: ${err.message}`; + this.queryInfo.failureReason = err.message; + await this.queryHistoryManager.refreshTreeView(); + } + + /** + * Generate summaries of the structured evaluator log. + */ + private async summarizeEvalLog( + resultType: QueryResultType, + outputDir: QueryOutputDir, + logger: BaseLogger, + ): Promise { + const evalLogPaths = await generateEvalLogSummaries( + this.cliServer, + outputDir, + ); + if (evalLogPaths !== undefined) { + if (evalLogPaths.endSummary !== undefined) { + void logEndSummary(evalLogPaths.endSummary, logger); // Logged asynchrnously + } + } else { + // Raw evaluator log was not found. Notify the user, unless we know why it wasn't found. + if (resultType === QueryResultType.SUCCESS) { + void showAndLogWarningMessage( + `Failed to write structured evaluator log to ${outputDir.evalLogPath}.`, ); + } else { + // Don't bother notifying the user if there's no log. For some errors, like compilation + // errors, we don't expect a log. For cancellations and OOM errors, whether or not we have + // a log depends on how far execution got before termination. + } + } + + return evalLogPaths; + } + + /** + * Gets a `QueryWithResults` containing information about the evaluation of the query and its + * result, in the form expected by the query history UI. + */ + private async getCompletedQueryInfo( + results: CoreQueryResults, + ): Promise { + // Read the query metadata if possible, to use in the UI. + const metadata = await tryGetQueryMetadata( + this.cliServer, + this.queryInfo.initialInfo.queryPath, + ); + const query = new QueryEvaluationInfo( + this.outputDir.querySaveDir, + this.dbItem.databaseUri.fsPath, + await this.dbItem.hasMetadataFile(), + this.queryInfo.initialInfo.quickEvalPosition, + metadata, + ); + + if (results.resultType !== QueryResultType.SUCCESS) { + const message = results.message + ? redactableError`Failed to run query: ${results.message}` + : redactableError`Failed to run query`; + void showAndLogExceptionWithTelemetry(message); + } + const message = formatResultMessage(results); + const successful = results.resultType === QueryResultType.SUCCESS; + return { + query, + result: { + evaluationTime: results.evaluationTime, + queryId: 0, + resultType: successful + ? QueryResultType.SUCCESS + : QueryResultType.OTHER_ERROR, + runId: 0, + message, + }, + message, + successful, + }; + } +} + +export class LocalQueries extends DisposableObject { + public constructor( + private readonly app: App, + private readonly queryRunner: QueryRunner, + private readonly queryHistoryManager: QueryHistoryManager, + private readonly databaseManager: DatabaseManager, + private readonly cliServer: CodeQLCliServer, + private readonly databaseUI: DatabaseUI, + private readonly localQueryResultsView: ResultsView, + private readonly queryStorageDir: string, + ) { + super(); + } + + public getCommands(): LocalQueryCommands { + return { + "codeQL.runQuery": this.runQuery.bind(this), + "codeQL.runQueryContextEditor": this.runQuery.bind(this), + "codeQL.runQueryOnMultipleDatabases": + this.runQueryOnMultipleDatabases.bind(this), + "codeQL.runQueryOnMultipleDatabasesContextEditor": + this.runQueryOnMultipleDatabases.bind(this), + "codeQL.runQueries": this.runQueries.bind(this), + "codeQL.quickEval": this.quickEval.bind(this), + "codeQL.quickEvalContextEditor": this.quickEval.bind(this), + "codeQL.codeLensQuickEval": this.codeLensQuickEval.bind(this), + "codeQL.quickQuery": this.quickQuery.bind(this), + }; + } + + private async runQuery(uri: Uri | undefined): Promise { + await withProgress( + async (progress, token) => { + await this.compileAndRunQuery(false, uri, progress, token, undefined); }, { title: "Running query", cancellable: true, }, ); + } - const runQueryOnMultipleDatabases = async (uri: Uri | undefined) => - withProgress( + private async runQueryOnMultipleDatabases( + uri: Uri | undefined, + ): Promise { + await withProgress( async (progress, token) => - await compileAndRunQueryOnMultipleDatabases( - cliServer, - queryRunner, - queryHistoryManager, - databaseManager, - databaseUI, - localQueryResultsView, - queryStorageDir, - progress, - token, - uri, - ), + await this.compileAndRunQueryOnMultipleDatabases(progress, token, uri), { title: "Running query on selected databases", cancellable: true, }, ); + } - const runQueries = async (_: Uri | undefined, multi: Uri[]) => - withProgress( + private async runQueries(_: Uri | undefined, multi: Uri[]): Promise { + await withProgress( async (progress, token) => { const maxQueryCount = MAX_QUERIES.getValue() as number; const [files, dirFound] = await gatherQlFiles( @@ -142,12 +312,7 @@ export function getLocalQueryCommands({ await Promise.all( queryUris.map(async (uri) => - compileAndRunQuery( - queryRunner, - queryHistoryManager, - databaseUI, - localQueryResultsView, - queryStorageDir, + this.compileAndRunQuery( false, uri, wrappedProgress, @@ -162,38 +327,24 @@ export function getLocalQueryCommands({ cancellable: true, }, ); + } - const quickEval = async (uri: Uri) => - withProgress( + private async quickEval(uri: Uri): Promise { + await withProgress( async (progress, token) => { - await compileAndRunQuery( - queryRunner, - queryHistoryManager, - databaseUI, - localQueryResultsView, - queryStorageDir, - true, - uri, - progress, - token, - undefined, - ); + await this.compileAndRunQuery(true, uri, progress, token, undefined); }, { title: "Running query", cancellable: true, }, ); + } - const codeLensQuickEval = async (uri: Uri, range: Range) => - withProgress( + private async codeLensQuickEval(uri: Uri, range: Range): Promise { + await withProgress( async (progress, token) => - await compileAndRunQuery( - queryRunner, - queryHistoryManager, - databaseUI, - localQueryResultsView, - queryStorageDir, + await this.compileAndRunQuery( true, uri, progress, @@ -206,185 +357,234 @@ export function getLocalQueryCommands({ cancellable: true, }, ); + } - const quickQuery = async () => - withProgress( + private async quickQuery(): Promise { + await withProgress( async (progress, token) => - displayQuickQuery(app, cliServer, databaseUI, progress, token), + displayQuickQuery( + this.app, + this.cliServer, + this.databaseUI, + progress, + token, + ), { title: "Run Quick Query", }, ); + } - return { - "codeQL.runQuery": runQuery, - "codeQL.runQueryContextEditor": runQuery, - "codeQL.runQueryOnMultipleDatabases": runQueryOnMultipleDatabases, - "codeQL.runQueryOnMultipleDatabasesContextEditor": - runQueryOnMultipleDatabases, - "codeQL.runQueries": runQueries, - "codeQL.quickEval": quickEval, - "codeQL.quickEvalContextEditor": quickEval, - "codeQL.codeLensQuickEval": codeLensQuickEval, - "codeQL.quickQuery": quickQuery, - }; -} + /** + * Creates a new `LocalQueryRun` object to track a query evaluation. This creates a timestamp + * file in the query's output directory, creates a `LocalQueryInfo` object, and registers that + * object with the query history manager. + * + * Once the evaluation is complete, the client must call `complete()` on the `LocalQueryRun` + * object to update the UI based on the results of the query. + */ + public async createLocalQueryRun( + selectedQuery: SelectedQuery, + dbItem: DatabaseItem, + outputDir: QueryOutputDir, + tokenSource: CancellationTokenSource, + ): Promise { + await createTimestampFile(outputDir.querySaveDir); -export async function compileAndRunQuery( - qs: QueryRunner, - qhm: QueryHistoryManager, - databaseUI: DatabaseUI, - localQueryResultsView: ResultsView, - queryStorageDir: string, - quickEval: boolean, - selectedQuery: Uri | undefined, - progress: ProgressCallback, - token: CancellationToken, - databaseItem: DatabaseItem | undefined, - range?: Range, -): Promise { - if (qs !== undefined) { - // If no databaseItem is specified, use the database currently selected in the Databases UI - databaseItem = - databaseItem || (await databaseUI.getDatabaseItem(progress, token)); - if (databaseItem === undefined) { - throw new Error("Can't run query without a selected database"); + if (this.queryRunner.customLogDirectory) { + void showAndLogWarningMessage( + `Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${outputDir.logPath}`, + ); } - const databaseInfo = { - name: databaseItem.name, - databaseUri: databaseItem.databaseUri.toString(), - }; - // handle cancellation from the history view. - const source = new CancellationTokenSource(); - token.onCancellationRequested(() => source.cancel()); + const initialInfo = await createInitialQueryInfo(selectedQuery, { + databaseUri: dbItem.databaseUri.toString(), + name: dbItem.name, + }); - const initialInfo = await createInitialQueryInfo( - selectedQuery, - databaseInfo, + // When cancellation is requested from the query history view, we just stop the debug session. + const queryInfo = new LocalQueryInfo(initialInfo, tokenSource); + this.queryHistoryManager.addQuery(queryInfo); + + const logger = new TeeLogger(this.queryRunner.logger, outputDir.logPath); + return new LocalQueryRun( + outputDir, + this, + queryInfo, + dbItem, + logger, + this.queryHistoryManager, + this.cliServer, + ); + } + + public async compileAndRunQuery( + quickEval: boolean, + queryUri: Uri | undefined, + progress: ProgressCallback, + token: CancellationToken, + databaseItem: DatabaseItem | undefined, + range?: Range, + ): Promise { + await this.compileAndRunQueryInternal( + quickEval, + queryUri, + progress, + token, + databaseItem, + range, + ); + } + + /** Used by tests */ + public async compileAndRunQueryInternal( + quickEval: boolean, + queryUri: Uri | undefined, + progress: ProgressCallback, + token: CancellationToken, + databaseItem: DatabaseItem | undefined, + range?: Range, + ): Promise { + const selectedQuery = await determineSelectedQuery( + queryUri, quickEval, range, ); - const item = new LocalQueryInfo(initialInfo, source); - qhm.addQuery(item); + + // If no databaseItem is specified, use the database currently selected in the Databases UI + databaseItem = + databaseItem || (await this.databaseUI.getDatabaseItem(progress, token)); + if (databaseItem === undefined) { + throw new Error("Can't run query without a selected database"); + } + + const additionalPacks = getOnDiskWorkspaceFolders(); + const extensionPacks = (await this.cliServer.useExtensionPacks()) + ? Object.keys(await this.cliServer.resolveQlpacks(additionalPacks, true)) + : undefined; + + const coreQueryRun = this.queryRunner.createQueryRun( + databaseItem.databaseUri.fsPath, + { + queryPath: selectedQuery.queryPath, + quickEvalPosition: selectedQuery.quickEvalPosition, + }, + true, + additionalPacks, + extensionPacks, + this.queryStorageDir, + undefined, + undefined, + ); + + // handle cancellation from the history view. + const source = new CancellationTokenSource(); try { - const completedQueryInfo = await qs.compileAndRunQueryAgainstDatabase( + token.onCancellationRequested(() => source.cancel()); + + const localQueryRun = await this.createLocalQueryRun( + selectedQuery, databaseItem, - initialInfo, - queryStorageDir, - progress, - source.token, - undefined, - item, + coreQueryRun.outputDir, + source, ); - qhm.completeQuery(item, completedQueryInfo); - await showResultsForCompletedQuery( - localQueryResultsView, - item as CompletedLocalQueryInfo, - WebviewReveal.Forced, - ); - // Note we must update the query history view after showing results as the - // display and sorting might depend on the number of results - } catch (e) { - const err = asError(e); - err.message = `Error running query: ${err.message}`; - item.failureReason = err.message; - throw e; + + try { + const results = await coreQueryRun.evaluate( + progress, + source.token, + localQueryRun.logger, + ); + + await localQueryRun.complete(results); + + return results; + } catch (e) { + // It's odd that we have two different ways for a query evaluation to fail: by throwing an + // exception, and by returning a result with a failure code. This is how the code worked + // before the refactoring, so it's been preserved, but we should probably figure out how + // to unify both error handling paths. + const err = asError(e); + await localQueryRun.fail(err); + throw e; + } } finally { - await qhm.refreshTreeView(); source.dispose(); } } -} -interface DatabaseQuickPickItem extends QuickPickItem { - databaseItem: DatabaseItem; -} - -async function compileAndRunQueryOnMultipleDatabases( - cliServer: CodeQLCliServer, - qs: QueryRunner, - qhm: QueryHistoryManager, - dbm: DatabaseManager, - databaseUI: DatabaseUI, - localQueryResultsView: ResultsView, - queryStorageDir: string, - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, -): Promise { - let filteredDBs = dbm.databaseItems; - if (filteredDBs.length === 0) { - void showAndLogErrorMessage( - "No databases found. Please add a suitable database to your workspace.", - ); - return; - } - // If possible, only show databases with the right language (otherwise show all databases). - const queryLanguage = await findLanguage(cliServer, uri); - if (queryLanguage) { - filteredDBs = dbm.databaseItems.filter( - (db) => db.language === queryLanguage, - ); + private async compileAndRunQueryOnMultipleDatabases( + progress: ProgressCallback, + token: CancellationToken, + uri: Uri | undefined, + ): Promise { + let filteredDBs = this.databaseManager.databaseItems; if (filteredDBs.length === 0) { void showAndLogErrorMessage( - `No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`, + "No databases found. Please add a suitable database to your workspace.", ); return; } - } - const quickPickItems = filteredDBs.map((dbItem) => ({ - databaseItem: dbItem, - label: dbItem.name, - description: dbItem.language, - })); - /** - * Databases that were selected in the quick pick menu. - */ - const quickpick = await window.showQuickPick( - quickPickItems, - { canPickMany: true, ignoreFocusOut: true }, - ); - if (quickpick !== undefined) { - // Collect all skipped databases and display them at the end (instead of popping up individual errors) - const skippedDatabases = []; - const errors = []; - for (const item of quickpick) { - try { - await compileAndRunQuery( - qs, - qhm, - databaseUI, - localQueryResultsView, - queryStorageDir, - false, - uri, - progress, - token, - item.databaseItem, + // If possible, only show databases with the right language (otherwise show all databases). + const queryLanguage = await findLanguage(this.cliServer, uri); + if (queryLanguage) { + filteredDBs = this.databaseManager.databaseItems.filter( + (db) => db.language === queryLanguage, + ); + if (filteredDBs.length === 0) { + void showAndLogErrorMessage( + `No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`, ); - } catch (e) { - skippedDatabases.push(item.label); - errors.push(getErrorMessage(e)); + return; } } - if (skippedDatabases.length > 0) { - void extLogger.log(`Errors:\n${errors.join("\n")}`); - void showAndLogWarningMessage( - `The following databases were skipped:\n${skippedDatabases.join( - "\n", - )}.\nFor details about the errors, see the logs.`, - ); + const quickPickItems = filteredDBs.map((dbItem) => ({ + databaseItem: dbItem, + label: dbItem.name, + description: dbItem.language, + })); + /** + * Databases that were selected in the quick pick menu. + */ + const quickpick = await window.showQuickPick( + quickPickItems, + { canPickMany: true, ignoreFocusOut: true }, + ); + if (quickpick !== undefined) { + // Collect all skipped databases and display them at the end (instead of popping up individual errors) + const skippedDatabases = []; + const errors = []; + for (const item of quickpick) { + try { + await this.compileAndRunQuery( + false, + uri, + progress, + token, + item.databaseItem, + ); + } catch (e) { + skippedDatabases.push(item.label); + errors.push(getErrorMessage(e)); + } + } + if (skippedDatabases.length > 0) { + void extLogger.log(`Errors:\n${errors.join("\n")}`); + void showAndLogWarningMessage( + `The following databases were skipped:\n${skippedDatabases.join( + "\n", + )}.\nFor details about the errors, see the logs.`, + ); + } + } else { + void showAndLogErrorMessage("No databases selected."); } - } else { - void showAndLogErrorMessage("No databases selected."); + } + + public async showResultsForCompletedQuery( + query: CompletedLocalQueryInfo, + forceReveal: WebviewReveal, + ): Promise { + await this.localQueryResultsView.showResults(query, forceReveal, false); } } - -export async function showResultsForCompletedQuery( - localQueryResultsView: ResultsView, - query: CompletedLocalQueryInfo, - forceReveal: WebviewReveal, -): Promise { - await localQueryResultsView.showResults(query, forceReveal, false); -} diff --git a/extensions/ql-vscode/src/log-insights/summary-language-support.ts b/extensions/ql-vscode/src/log-insights/summary-language-support.ts index b6920fa30..187413c4b 100644 --- a/extensions/ql-vscode/src/log-insights/summary-language-support.ts +++ b/extensions/ql-vscode/src/log-insights/summary-language-support.ts @@ -1,7 +1,6 @@ import { readFile } from "fs-extra"; import { RawSourceMap, SourceMapConsumer } from "source-map"; import { - commands, Position, Selection, TextDocument, @@ -16,6 +15,7 @@ import { DisposableObject } from "../pure/disposable-object"; import { extLogger } from "../common"; import { getErrorMessage } from "../pure/helpers-pure"; import { SummaryLanguageSupportCommands } from "../common/commands"; +import { App } from "../common/app"; /** A `Position` within a specified file on disk. */ interface PositionInFile { @@ -55,7 +55,7 @@ export class SummaryLanguageSupport extends DisposableObject { */ private sourceMap: SourceMapConsumer | undefined = undefined; - constructor() { + constructor(private readonly app: App) { super(); this.push( @@ -160,7 +160,7 @@ export class SummaryLanguageSupport extends DisposableObject { private async updateContext(): Promise { const position = await this.getQLSourceLocation(); - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeql.hasQLSource", position !== undefined, diff --git a/extensions/ql-vscode/src/mocks/mock-gh-api-server.ts b/extensions/ql-vscode/src/mocks/mock-gh-api-server.ts index 1d6bbd3ae..4a3cd8c8c 100644 --- a/extensions/ql-vscode/src/mocks/mock-gh-api-server.ts +++ b/extensions/ql-vscode/src/mocks/mock-gh-api-server.ts @@ -1,6 +1,6 @@ import { join, resolve } from "path"; import { pathExists } from "fs-extra"; -import { setupServer, SetupServerApi } from "msw/node"; +import { setupServer, SetupServer } from "msw/node"; import { DisposableObject } from "../pure/disposable-object"; @@ -14,7 +14,7 @@ import { getDirectoryNamesInsidePath } from "../pure/files"; export class MockGitHubApiServer extends DisposableObject { private _isListening: boolean; - private readonly server: SetupServerApi; + private readonly server: SetupServer; private readonly recorder: Recorder; constructor() { diff --git a/extensions/ql-vscode/src/mocks/recorder.ts b/extensions/ql-vscode/src/mocks/recorder.ts index 6253b9d11..feb6ff32b 100644 --- a/extensions/ql-vscode/src/mocks/recorder.ts +++ b/extensions/ql-vscode/src/mocks/recorder.ts @@ -2,7 +2,7 @@ import { ensureDir, writeFile } from "fs-extra"; import { join } from "path"; import { MockedRequest } from "msw"; -import { SetupServerApi } from "msw/node"; +import { SetupServer } from "msw/node"; import { IsomorphicResponse } from "@mswjs/interceptors"; import { Headers } from "headers-polyfill"; @@ -22,7 +22,7 @@ export class Recorder extends DisposableObject { private _isRecording = false; - constructor(private readonly server: SetupServerApi) { + constructor(private readonly server: SetupServer) { super(); this.onRequestStart = this.onRequestStart.bind(this); this.onResponseBypass = this.onResponseBypass.bind(this); @@ -88,13 +88,18 @@ export class Recorder extends DisposableObject { const bodyFileName = `${i}-${writtenRequest.request.kind}.body.${extension}`; const bodyFilePath = join(scenarioDirectory, bodyFileName); - await writeFile(bodyFilePath, writtenRequest.response.body); + + let bodyFileLink = undefined; + if (writtenRequest.response.body) { + await writeFile(bodyFilePath, writtenRequest.response.body || ""); + bodyFileLink = `file:${bodyFileName}`; + } writtenRequest = { ...writtenRequest, response: { ...writtenRequest.response, - body: `file:${bodyFileName}`, + body: bodyFileLink, }, }; } diff --git a/extensions/ql-vscode/src/mocks/vscode-mock-gh-api-server.ts b/extensions/ql-vscode/src/mocks/vscode-mock-gh-api-server.ts index 50c821b56..4a7f8aa80 100644 --- a/extensions/ql-vscode/src/mocks/vscode-mock-gh-api-server.ts +++ b/extensions/ql-vscode/src/mocks/vscode-mock-gh-api-server.ts @@ -1,13 +1,5 @@ import { pathExists } from "fs-extra"; -import { - commands, - env, - ExtensionContext, - ExtensionMode, - QuickPickItem, - Uri, - window, -} from "vscode"; +import { env, QuickPickItem, Uri, window } from "vscode"; import { getMockGitHubApiServerScenariosPath, @@ -16,6 +8,8 @@ import { import { DisposableObject } from "../pure/disposable-object"; import { MockGitHubApiServer } from "./mock-gh-api-server"; import { MockGitHubApiServerCommands } from "../common/commands"; +import { App, AppMode } from "../common/app"; +import path from "path"; /** * "Interface" to the mock GitHub API server which implements VSCode interactions, such as @@ -27,7 +21,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { private readonly server: MockGitHubApiServer; private readonly config: MockGitHubApiConfigListener; - constructor(private readonly ctx: ExtensionContext) { + constructor(private readonly app: App) { super(); this.server = new MockGitHubApiServer(); this.config = new MockGitHubApiConfigListener(); @@ -55,12 +49,12 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { public async stopServer(): Promise { this.server.stopServer(); - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.mockGitHubApiServer.scenarioLoaded", false, ); - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.mockGitHubApiServer.recording", false, @@ -92,7 +86,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { // Set a value in the context to track whether we have a scenario loaded. // This allows us to use this to show/hide commands (see package.json) - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.mockGitHubApiServer.scenarioLoaded", true, @@ -106,7 +100,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { await window.showInformationMessage("No scenario currently loaded"); } else { await this.server.unloadScenario(); - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.mockGitHubApiServer.scenarioLoaded", false, @@ -125,7 +119,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { if (this.server.isScenarioLoaded) { await this.server.unloadScenario(); - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.mockGitHubApiServer.scenarioLoaded", false, @@ -137,7 +131,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { await this.server.startRecording(); // Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json) - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.mockGitHubApiServer.recording", true, @@ -155,7 +149,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { } // Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json) - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.mockGitHubApiServer.recording", false, @@ -210,7 +204,7 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { private async stopRecording(): Promise { // Set a value in the context to track whether we are recording. This allows us to use this to show/hide commands (see package.json) - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.mockGitHubApiServer.recording", false, @@ -225,11 +219,11 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { return scenariosPath; } - if (this.ctx.extensionMode === ExtensionMode.Development) { - const developmentScenariosPath = Uri.joinPath( - this.ctx.extensionUri, + if (this.app.mode === AppMode.Development) { + const developmentScenariosPath = path.join( + this.app.extensionPath, "src/mocks/scenarios", - ).fsPath.toString(); + ); if (await pathExists(developmentScenariosPath)) { return developmentScenariosPath; } diff --git a/extensions/ql-vscode/src/query-history/query-history-manager.ts b/extensions/ql-vscode/src/query-history/query-history-manager.ts index ca7ab3fd9..50f9174c9 100644 --- a/extensions/ql-vscode/src/query-history/query-history-manager.ts +++ b/extensions/ql-vscode/src/query-history/query-history-manager.ts @@ -40,8 +40,8 @@ import { variantAnalysisStatusToQueryStatus, } from "../query-status"; import { - deserializeQueryHistory, - serializeQueryHistory, + readQueryHistoryFromFile, + writeQueryHistoryToFile, } from "./store/query-history-store"; import { pathExists } from "fs-extra"; import { CliVersionConstraint } from "../cli"; @@ -379,7 +379,7 @@ export class QueryHistoryManager extends DisposableObject { void extLogger.log( `Reading cached query history from '${this.queryMetadataStorageLocation}'.`, ); - const history = await deserializeQueryHistory( + const history = await readQueryHistoryFromFile( this.queryMetadataStorageLocation, ); this.treeDataProvider.allHistory = history; @@ -395,7 +395,7 @@ export class QueryHistoryManager extends DisposableObject { } async writeQueryHistory(): Promise { - await serializeQueryHistory( + await writeQueryHistoryToFile( this.treeDataProvider.allHistory, this.queryMetadataStorageLocation, ); diff --git a/extensions/ql-vscode/src/query-history/store/query-history-store.ts b/extensions/ql-vscode/src/query-history/store/query-history-store.ts index bad4a4677..b0abf7fab 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-store.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-store.ts @@ -14,7 +14,7 @@ import { QueryEvaluationInfo } from "../../run-queries-shared"; import { QueryResultType } from "../../pure/legacy-messages"; import { redactableError } from "../../pure/errors"; -export async function deserializeQueryHistory( +export async function readQueryHistoryFromFile( fsPath: string, ): Promise { try { @@ -109,7 +109,7 @@ export async function deserializeQueryHistory( * @param queries the list of queries to save. * @param fsPath the path to save the queries to. */ -export async function serializeQueryHistory( +export async function writeQueryHistoryToFile( queries: QueryHistoryInfo[], fsPath: string, ): Promise { diff --git a/extensions/ql-vscode/src/query-results.ts b/extensions/ql-vscode/src/query-results.ts index 33100bde8..331e7dd55 100644 --- a/extensions/ql-vscode/src/query-results.ts +++ b/extensions/ql-vscode/src/query-results.ts @@ -16,7 +16,11 @@ import { DatabaseInfo, } from "./pure/interface-types"; import { QueryStatus } from "./query-status"; -import { QueryEvaluationInfo, QueryWithResults } from "./run-queries-shared"; +import { + EvaluatorLogPaths, + QueryEvaluationInfo, + QueryWithResults, +} from "./run-queries-shared"; import { formatLegacyMessage } from "./legacy-query-server/run-queries"; import { sarifParser } from "./sarif-parser"; @@ -70,7 +74,7 @@ export class CompletedQueryInfo implements QueryWithResults { interpretedResultsSortState: InterpretedResultsSortState | undefined; /** - * Note that in the {@link deserializeQueryHistory} method, we create a CompletedQueryInfo instance + * Note that in the {@link readQueryHistoryFromFile} method, we create a CompletedQueryInfo instance * by explicitly setting the prototype in order to avoid calling this constructor. */ constructor(evaluation: QueryWithResults) { @@ -224,7 +228,7 @@ export class LocalQueryInfo { public evalLogSummarySymbolsLocation: string | undefined; /** - * Note that in the {@link deserializeQueryHistory} method, we create a FullQueryInfo instance + * Note that in the {@link readQueryHistoryFromFile} method, we create a FullQueryInfo instance * by explicitly setting the prototype in order to avoid calling this constructor. */ constructor( @@ -253,6 +257,14 @@ export class LocalQueryInfo { this.initialInfo.userSpecifiedLabel = label; } + /** Sets the paths to the various structured evaluator logs. */ + public setEvaluatorLogPaths(logPaths: EvaluatorLogPaths): void { + this.evalLogLocation = logPaths.log; + this.evalLogSummaryLocation = logPaths.humanReadableSummary; + this.jsonEvalLogSummaryLocation = logPaths.jsonSummary; + this.evalLogSummarySymbolsLocation = logPaths.summarySymbols; + } + /** * The query's file name, unless it is a quick eval. * Queries run through quick evaluation are not usually the entire query file. diff --git a/extensions/ql-vscode/src/query-server/query-runner.ts b/extensions/ql-vscode/src/query-server/query-runner.ts index ebb90907b..73fa0887b 100644 --- a/extensions/ql-vscode/src/query-server/query-runner.ts +++ b/extensions/ql-vscode/src/query-server/query-runner.ts @@ -9,22 +9,32 @@ import { registerDatabases, upgradeDatabase, } from "../pure/new-messages"; -import { InitialQueryInfo, LocalQueryInfo } from "../query-results"; -import { QueryRunner } from "../queryRunner"; -import { QueryWithResults } from "../run-queries-shared"; +import { CoreQueryResults, CoreQueryTarget, QueryRunner } from "../queryRunner"; import { QueryServerClient } from "./queryserver-client"; -import { compileAndRunQueryAgainstDatabase } from "./run-queries"; +import { compileAndRunQueryAgainstDatabaseCore } from "./run-queries"; import * as vscode from "vscode"; import { getOnDiskWorkspaceFolders } from "../helpers"; +import { CodeQLCliServer } from "../cli"; +import { Logger } from "../common"; +import { QueryOutputDir } from "../run-queries-shared"; + export class NewQueryRunner extends QueryRunner { constructor(public readonly qs: QueryServerClient) { super(); } - get cliServer() { + get cliServer(): CodeQLCliServer { return this.qs.cliServer; } + get customLogDirectory(): string | undefined { + return this.qs.config.customLogDirectory; + } + + get logger(): Logger { + return this.qs.logger; + } + async restartQueryServer( progress: ProgressCallback, token: CancellationToken, @@ -57,25 +67,31 @@ export class NewQueryRunner extends QueryRunner { }; await this.qs.sendRequest(clearCache, params, token, progress); } - async compileAndRunQueryAgainstDatabase( - dbItem: DatabaseItem, - initialInfo: InitialQueryInfo, - queryStorageDir: string, + + public async compileAndRunQueryAgainstDatabaseCore( + dbPath: string, + query: CoreQueryTarget, + additionalPacks: string[], + extensionPacks: string[] | undefined, + generateEvalLog: boolean, + outputDir: QueryOutputDir, progress: ProgressCallback, token: CancellationToken, - templates?: Record, - queryInfo?: LocalQueryInfo, - ): Promise { - return await compileAndRunQueryAgainstDatabase( - this.qs.cliServer, + templates: Record | undefined, + logger: Logger, + ): Promise { + return await compileAndRunQueryAgainstDatabaseCore( this.qs, - dbItem, - initialInfo, - queryStorageDir, + dbPath, + query, + generateEvalLog, + additionalPacks, + extensionPacks, + outputDir, progress, token, templates, - queryInfo, + logger, ); } diff --git a/extensions/ql-vscode/src/query-server/queryserver-client.ts b/extensions/ql-vscode/src/query-server/queryserver-client.ts index 428526299..f1451ea06 100644 --- a/extensions/ql-vscode/src/query-server/queryserver-client.ts +++ b/extensions/ql-vscode/src/query-server/queryserver-client.ts @@ -1,7 +1,7 @@ import { ensureFile } from "fs-extra"; import { DisposableObject } from "../pure/disposable-object"; -import { CancellationToken, commands } from "vscode"; +import { CancellationToken } from "vscode"; import { createMessageConnection, RequestType } from "vscode-jsonrpc/node"; import * as cli from "../cli"; import { QueryServerConfig } from "../config"; @@ -13,6 +13,7 @@ import { } from "../pure/new-messages"; import { ProgressCallback, ProgressTask } from "../progress"; import { ServerProcess } from "../json-rpc-server"; +import { App } from "../common/app"; type ServerOpts = { logger: Logger; @@ -53,6 +54,7 @@ export class QueryServerClient extends DisposableObject { public activeQueryLogger: Logger; constructor( + app: App, readonly config: QueryServerConfig, readonly cliServer: cli.CodeQLCliServer, readonly opts: ServerOpts, @@ -66,7 +68,7 @@ export class QueryServerClient extends DisposableObject { if (config.onDidChangeConfiguration !== undefined) { this.push( config.onDidChangeConfiguration(() => - commands.executeCommand("codeQL.restartQueryServer"), + app.commands.execute("codeQL.restartQueryServer"), ), ); } diff --git a/extensions/ql-vscode/src/query-server/run-queries.ts b/extensions/ql-vscode/src/query-server/run-queries.ts index a08680cc4..b8a81cb4e 100644 --- a/extensions/ql-vscode/src/query-server/run-queries.ts +++ b/extensions/ql-vscode/src/query-server/run-queries.ts @@ -1,21 +1,10 @@ -import { join } from "path"; import { CancellationToken } from "vscode"; -import * as cli from "../cli"; import { ProgressCallback } from "../progress"; -import { DatabaseItem } from "../local-databases"; -import { - getOnDiskWorkspaceFolders, - showAndLogExceptionWithTelemetry, - showAndLogWarningMessage, - tryGetQueryMetadata, -} from "../helpers"; -import { extLogger, TeeLogger } from "../common"; import * as messages from "../pure/new-messages"; -import { QueryResultType } from "../pure/legacy-messages"; -import { InitialQueryInfo, LocalQueryInfo } from "../query-results"; -import { QueryEvaluationInfo, QueryWithResults } from "../run-queries-shared"; +import { QueryOutputDir } from "../run-queries-shared"; import * as qsClient from "./queryserver-client"; -import { redactableError } from "../pure/errors"; +import { CoreQueryResults, CoreQueryTarget } from "../queryRunner"; +import { Logger } from "../common"; /** * run-queries.ts @@ -31,140 +20,55 @@ import { redactableError } from "../pure/errors"; * output and results. */ -export async function compileAndRunQueryAgainstDatabase( - cliServer: cli.CodeQLCliServer, +export async function compileAndRunQueryAgainstDatabaseCore( qs: qsClient.QueryServerClient, - dbItem: DatabaseItem, - initialInfo: InitialQueryInfo, - queryStorageDir: string, + dbPath: string, + query: CoreQueryTarget, + generateEvalLog: boolean, + additionalPacks: string[], + extensionPacks: string[] | undefined, + outputDir: QueryOutputDir, progress: ProgressCallback, token: CancellationToken, - templates?: Record, - queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query. -): Promise { - if (!dbItem.contents || !dbItem.contents.dbSchemeUri) { - throw new Error( - `Database ${dbItem.databaseUri} does not have a CodeQL database scheme.`, - ); - } + templates: Record | undefined, + logger: Logger, +): Promise { + const target = + query.quickEvalPosition !== undefined + ? { + quickEval: { quickEvalPos: query.quickEvalPosition }, + } + : { query: {} }; - // Read the query metadata if possible, to use in the UI. - const metadata = await tryGetQueryMetadata(cliServer, initialInfo.queryPath); - - const hasMetadataFile = await dbItem.hasMetadataFile(); - const query = new QueryEvaluationInfo( - join(queryStorageDir, initialInfo.id), - dbItem.databaseUri.fsPath, - hasMetadataFile, - initialInfo.quickEvalPosition, - metadata, - ); - - if (!dbItem.contents || dbItem.error) { - throw new Error("Can't run query on invalid database."); - } - const target = query.quickEvalPosition - ? { - quickEval: { quickEvalPos: query.quickEvalPosition }, - } - : { query: {} }; - - const diskWorkspaceFolders = getOnDiskWorkspaceFolders(); - const extensionPacks = (await qs.cliServer.useExtensionPacks()) - ? Object.keys(await qs.cliServer.resolveQlpacks(diskWorkspaceFolders, true)) - : undefined; - - const db = dbItem.databaseUri.fsPath; - const logPath = queryInfo ? query.evalLogPath : undefined; + const evalLogPath = generateEvalLog ? outputDir.evalLogPath : undefined; const queryToRun: messages.RunQueryParams = { - db, - additionalPacks: diskWorkspaceFolders, + db: dbPath, + additionalPacks, externalInputs: {}, singletonExternalInputs: templates || {}, - outputPath: query.resultsPaths.resultsPath, - queryPath: initialInfo.queryPath, - dilPath: query.dilPath, - logPath, + outputPath: outputDir.bqrsPath, + queryPath: query.queryPath, + dilPath: outputDir.dilPath, + logPath: evalLogPath, target, extensionPacks, }; - const logger = new TeeLogger(qs.logger, query.logPath); - await query.createTimestampFile(); - let result: messages.RunQueryResult | undefined; - try { - // Update the active query logger every time there is a new request to compile. - // This isn't ideal because in situations where there are queries running - // in parallel, each query's log messages are interleaved. Fixing this - // properly will require a change in the query server. - qs.activeQueryLogger = logger; - result = await qs.sendRequest( - messages.runQuery, - queryToRun, - token, - progress, - ); - if (qs.config.customLogDirectory) { - void showAndLogWarningMessage( - `Custom log directories are no longer supported. The "codeQL.runningQueries.customLogDirectory" setting is deprecated. Unset the setting to stop seeing this message. Query logs saved to ${query.logPath}.`, - ); - } - } finally { - if (queryInfo) { - if (await query.hasEvalLog()) { - await query.addQueryLogs(queryInfo, qs.cliServer, logger); - } else { - void showAndLogWarningMessage( - `Failed to write structured evaluator log to ${query.evalLogPath}.`, - ); - } - } - } - if (result.resultType !== messages.QueryResultType.SUCCESS) { - const message = result?.message - ? redactableError`${result.message}` - : redactableError`Failed to run query`; - void extLogger.log(message.fullMessage); - void showAndLogExceptionWithTelemetry( - redactableError`Failed to run query: ${message}`, - ); - } - let message; - switch (result.resultType) { - case messages.QueryResultType.CANCELLATION: - message = `cancelled after ${Math.round( - result.evaluationTime / 1000, - )} seconds`; - break; - case messages.QueryResultType.OOM: - message = "out of memory"; - break; - case messages.QueryResultType.SUCCESS: - message = `finished in ${Math.round( - result.evaluationTime / 1000, - )} seconds`; - break; - case messages.QueryResultType.COMPILATION_ERROR: - message = `compilation failed: ${result.message}`; - break; - case messages.QueryResultType.OTHER_ERROR: - default: - message = result.message ? `failed: ${result.message}` : "failed"; - break; - } - const successful = result.resultType === messages.QueryResultType.SUCCESS; + // Update the active query logger every time there is a new request to compile. + // This isn't ideal because in situations where there are queries running + // in parallel, each query's log messages are interleaved. Fixing this + // properly will require a change in the query server. + qs.activeQueryLogger = logger; + const result = await qs.sendRequest( + messages.runQuery, + queryToRun, + token, + progress, + ); + return { - query, - result: { - evaluationTime: result.evaluationTime, - queryId: 0, - resultType: successful - ? QueryResultType.SUCCESS - : QueryResultType.OTHER_ERROR, - runId: 0, - message, - }, - message, - successful, + resultType: result.resultType, + message: result.message, + evaluationTime: result.evaluationTime, }; } diff --git a/extensions/ql-vscode/src/queryRunner.ts b/extensions/ql-vscode/src/queryRunner.ts index 0ef8058e5..8c4d79bfd 100644 --- a/extensions/ql-vscode/src/queryRunner.ts +++ b/extensions/ql-vscode/src/queryRunner.ts @@ -2,8 +2,44 @@ import { CancellationToken } from "vscode"; import { CodeQLCliServer } from "./cli"; import { ProgressCallback } from "./progress"; import { DatabaseItem } from "./local-databases"; -import { InitialQueryInfo, LocalQueryInfo } from "./query-results"; -import { QueryWithResults } from "./run-queries-shared"; +import { QueryOutputDir } from "./run-queries-shared"; +import { Position, QueryResultType } from "./pure/new-messages"; +import { BaseLogger, Logger } from "./common"; +import { basename, join } from "path"; +import { nanoid } from "nanoid"; + +export interface CoreQueryTarget { + /** The full path to the query. */ + queryPath: string; + /** + * Optional position of text to be used as QuickEval target. This need not be in the same file as + * `query`. + */ + quickEvalPosition?: Position; +} + +export interface CoreQueryResults { + readonly resultType: QueryResultType; + readonly message: string | undefined; + readonly evaluationTime: number; +} + +export interface CoreQueryRun { + readonly queryTarget: CoreQueryTarget; + readonly dbPath: string; + readonly id: string; + readonly outputDir: QueryOutputDir; + + evaluate( + progress: ProgressCallback, + token: CancellationToken, + logger: BaseLogger, + ): Promise; +} + +/** Includes both the results of the query and the initial information from `CoreQueryRun`. */ +export type CoreCompletedQuery = CoreQueryResults & + Omit; export abstract class QueryRunner { abstract restartQueryServer( @@ -12,6 +48,8 @@ export abstract class QueryRunner { ): Promise; abstract cliServer: CodeQLCliServer; + abstract customLogDirectory: string | undefined; + abstract logger: Logger; abstract onStart( arg0: ( @@ -25,15 +63,21 @@ export abstract class QueryRunner { token: CancellationToken, ): Promise; - abstract compileAndRunQueryAgainstDatabase( - dbItem: DatabaseItem, - initialInfo: InitialQueryInfo, - queryStorageDir: string, + /** + * Overridden in subclasses to evaluate the query via the query server and return the results. + */ + public abstract compileAndRunQueryAgainstDatabaseCore( + dbPath: string, + query: CoreQueryTarget, + additionalPacks: string[], + extensionPacks: string[] | undefined, + generateEvalLog: boolean, + outputDir: QueryOutputDir, progress: ProgressCallback, token: CancellationToken, - templates?: Record, - queryInfo?: LocalQueryInfo, // May be omitted for queries not initiated by the user. If omitted we won't create a structured log for the query. - ): Promise; + templates: Record | undefined, + logger: BaseLogger, + ): Promise; abstract deregisterDatabase( progress: ProgressCallback, @@ -54,4 +98,54 @@ export abstract class QueryRunner { ): Promise; abstract clearPackCache(): Promise; + + /** + * Create a `CoreQueryRun` object. This creates an object whose `evaluate()` function can be + * called to actually evaluate the query. The returned object also contains information about the + * query evaluation that is known even before evaluation starts, including the unique ID of the + * evaluation and the path to its output directory. + */ + public createQueryRun( + dbPath: string, + query: CoreQueryTarget, + generateEvalLog: boolean, + additionalPacks: string[], + extensionPacks: string[] | undefined, + queryStorageDir: string, + id = `${basename(query.queryPath)}-${nanoid()}`, + templates: Record | undefined, + ): CoreQueryRun { + const outputDir = new QueryOutputDir(join(queryStorageDir, id)); + + return { + queryTarget: query, + dbPath, + id, + outputDir, + evaluate: async ( + progress: ProgressCallback, + token: CancellationToken, + logger: BaseLogger, + ): Promise => { + return { + id, + outputDir, + dbPath, + queryTarget: query, + ...(await this.compileAndRunQueryAgainstDatabaseCore( + dbPath, + query, + additionalPacks, + extensionPacks, + generateEvalLog, + outputDir, + progress, + token, + templates, + logger, + )), + }; + }, + }; + } } diff --git a/extensions/ql-vscode/src/run-queries-shared.ts b/extensions/ql-vscode/src/run-queries-shared.ts index da4b7e86c..27fbd20ee 100644 --- a/extensions/ql-vscode/src/run-queries-shared.ts +++ b/extensions/ql-vscode/src/run-queries-shared.ts @@ -20,18 +20,14 @@ import { remove, readdir, } from "fs-extra"; -import { - ensureMetadataIsComplete, - InitialQueryInfo, - LocalQueryInfo, -} from "./query-results"; +import { ensureMetadataIsComplete, InitialQueryInfo } from "./query-results"; import { isQuickQueryPath } from "./quick-query"; import { nanoid } from "nanoid"; import { CodeQLCliServer } from "./cli"; import { SELECT_QUERY_NAME } from "./contextual/locationFinder"; import { DatabaseManager } from "./local-databases"; import { DecodedBqrsChunk, EntityValue } from "./pure/bqrs-cli-types"; -import { extLogger, Logger } from "./common"; +import { BaseLogger, extLogger } from "./common"; import { generateSummarySymbolsFile } from "./log-insights/summary-parser"; import { getErrorMessage } from "./pure/helpers-pure"; @@ -42,7 +38,18 @@ import { getErrorMessage } from "./pure/helpers-pure"; * Compiling and running QL queries. */ -export function findQueryLogFile(resultPath: string): string { +/** + * Holds the paths to the various structured log summary files generated for a query evaluation. + */ +export interface EvaluatorLogPaths { + log: string; + humanReadableSummary: string | undefined; + endSummary: string | undefined; + jsonSummary: string | undefined; + summarySymbols: string | undefined; +} + +function findQueryLogFile(resultPath: string): string { return join(resultPath, "query.log"); } @@ -66,20 +73,11 @@ function findQueryEvalLogEndSummaryFile(resultPath: string): string { return join(resultPath, "evaluator-log-end.summary"); } -export class QueryEvaluationInfo { - /** - * Note that in the {@link deserializeQueryHistory} method, we create a QueryEvaluationInfo instance - * by explicitly setting the prototype in order to avoid calling this constructor. - */ - constructor( - public readonly querySaveDir: string, - public readonly dbItemPath: string, - private readonly databaseHasMetadataFile: boolean, - public readonly quickEvalPosition?: messages.Position, - public readonly metadata?: QueryMetadata, - ) { - /**/ - } +/** + * Provides paths to the files that can be generated in the output directory for a query evaluation. + */ +export class QueryOutputDir { + constructor(public readonly querySaveDir: string) {} get dilPath() { return join(this.querySaveDir, "results.dil"); @@ -120,9 +118,35 @@ export class QueryEvaluationInfo { return findQueryEvalLogEndSummaryFile(this.querySaveDir); } + get bqrsPath() { + return join(this.querySaveDir, "results.bqrs"); + } +} + +export class QueryEvaluationInfo extends QueryOutputDir { + // We extend `QueryOutputDir`, rather than having it as a property, because we need + // `QueryOutputDir`'s `querySaveDir` property to be a property of `QueryEvaluationInfo`. This is + // because `QueryEvaluationInfo` is serialized directly as JSON, and before we hoisted + // `QueryOutputDir` out into a base class, `querySaveDir` was a property on `QueryEvaluationInfo` + // itself. + + /** + * Note that in the {@link readQueryHistoryFromFile} method, we create a QueryEvaluationInfo instance + * by explicitly setting the prototype in order to avoid calling this constructor. + */ + constructor( + querySaveDir: string, + public readonly dbItemPath: string, + private readonly databaseHasMetadataFile: boolean, + public readonly quickEvalPosition?: messages.Position, + public readonly metadata?: QueryMetadata, + ) { + super(querySaveDir); + } + get resultsPaths() { return { - resultsPath: join(this.querySaveDir, "results.bqrs"), + resultsPath: this.bqrsPath, interpretedResultsPath: join( this.querySaveDir, this.metadata?.kind === "graph" @@ -228,85 +252,6 @@ export class QueryEvaluationInfo { return pathExists(this.evalLogPath); } - /** - * Add the structured evaluator log to the query evaluation info. - */ - async addQueryLogs( - queryInfo: LocalQueryInfo, - cliServer: CodeQLCliServer, - logger: Logger, - ) { - queryInfo.evalLogLocation = this.evalLogPath; - queryInfo.evalLogSummaryLocation = - await this.generateHumanReadableLogSummary(cliServer); - void this.logEndSummary(queryInfo.evalLogSummaryLocation, logger); // Logged asynchrnously - if (isCanary()) { - // Generate JSON summary for viewer. - await cliServer.generateJsonLogSummary( - this.evalLogPath, - this.jsonEvalLogSummaryPath, - ); - queryInfo.jsonEvalLogSummaryLocation = this.jsonEvalLogSummaryPath; - await generateSummarySymbolsFile( - this.evalLogSummaryPath, - this.evalLogSummarySymbolsPath, - ); - queryInfo.evalLogSummarySymbolsLocation = this.evalLogSummarySymbolsPath; - } - } - - /** - * Calls the appropriate CLI command to generate a human-readable log summary. - * @param qs The query server client. - * @returns The path to the log summary, or `undefined` if the summary could not be generated. */ - private async generateHumanReadableLogSummary( - cliServer: CodeQLCliServer, - ): Promise { - try { - await cliServer.generateLogSummary( - this.evalLogPath, - this.evalLogSummaryPath, - this.evalLogEndSummaryPath, - ); - return this.evalLogSummaryPath; - } catch (e) { - void showAndLogWarningMessage( - `Failed to generate human-readable structured evaluator log summary. Reason: ${getErrorMessage( - e, - )}`, - ); - return undefined; - } - } - - /** - * Logs the end summary to the Output window and log file. - * @param logSummaryPath Path to the human-readable log summary - * @param qs The query server client. - */ - private async logEndSummary( - logSummaryPath: string | undefined, - logger: Logger, - ): Promise { - if (logSummaryPath === undefined) { - // Failed to generate the log, so we don't expect an end summary either. - return; - } - - try { - const endSummaryContent = await readFile( - this.evalLogEndSummaryPath, - "utf-8", - ); - void logger.log(" --- Evaluator Log Summary --- "); - void logger.log(endSummaryContent); - } catch (e) { - void showAndLogWarningMessage( - `Could not read structured evaluator log end of summary file at ${this.evalLogEndSummaryPath}.`, - ); - } - } - /** * Creates the CSV file containing the results of this query. This will only be called if the query * does not have interpreted results and the CSV file does not already exist. @@ -447,7 +392,7 @@ export interface QueryWithResults { * Information about which query will be to be run. `quickEvalPosition` and `quickEvalText` * is only filled in if the query is a quick query. */ -interface SelectedQuery { +export interface SelectedQuery { queryPath: string; quickEvalPosition?: messages.Position; quickEvalText?: string; @@ -642,36 +587,113 @@ async function convertToQlPath(filePath: string): Promise { * Determines the initial information for a query. This is everything of interest * we know about this query that is available before it is run. * - * @param selectedQueryUri The Uri of the document containing the query to be run. + * @param selectedQuery The query to run, including any quickeval info. * @param databaseInfo The database to run the query against. - * @param isQuickEval true if this is a quick evaluation. - * @param range the selection range of the query to be run. Only used if isQuickEval is true. * @returns The initial information for the query to be run. */ export async function createInitialQueryInfo( - selectedQueryUri: Uri | undefined, + selectedQuery: SelectedQuery, databaseInfo: DatabaseInfo, - isQuickEval: boolean, - range?: Range, ): Promise { - // Determine which query to run, based on the selection and the active editor. - const { queryPath, quickEvalPosition, quickEvalText } = - await determineSelectedQuery(selectedQueryUri, isQuickEval, range); - + const isQuickEval = selectedQuery.quickEvalPosition !== undefined; return { - queryPath, + queryPath: selectedQuery.queryPath, isQuickEval, - isQuickQuery: isQuickQueryPath(queryPath), + isQuickQuery: isQuickQueryPath(selectedQuery.queryPath), databaseInfo, - id: `${basename(queryPath)}-${nanoid()}`, + id: `${basename(selectedQuery.queryPath)}-${nanoid()}`, start: new Date(), ...(isQuickEval ? { - queryText: quickEvalText!, // if this query is quick eval, it must have quick eval text - quickEvalPosition, + queryText: selectedQuery.quickEvalText!, // if this query is quick eval, it must have quick eval text + quickEvalPosition: selectedQuery.quickEvalPosition, } : { - queryText: await readFile(queryPath, "utf8"), + queryText: await readFile(selectedQuery.queryPath, "utf8"), }), }; } + +export async function generateEvalLogSummaries( + cliServer: CodeQLCliServer, + outputDir: QueryOutputDir, +): Promise { + const log = outputDir.evalLogPath; + if (!(await pathExists(log))) { + // No raw JSON log, so we can't generate any summaries. + return undefined; + } + let humanReadableSummary: string | undefined = undefined; + let endSummary: string | undefined = undefined; + if (await generateHumanReadableLogSummary(cliServer, outputDir)) { + humanReadableSummary = outputDir.evalLogSummaryPath; + endSummary = outputDir.evalLogEndSummaryPath; + } + let jsonSummary: string | undefined = undefined; + let summarySymbols: string | undefined = undefined; + if (isCanary()) { + // Generate JSON summary for viewer. + jsonSummary = outputDir.jsonEvalLogSummaryPath; + await cliServer.generateJsonLogSummary(log, jsonSummary); + + if (humanReadableSummary !== undefined) { + summarySymbols = outputDir.evalLogSummarySymbolsPath; + await generateSummarySymbolsFile(humanReadableSummary, summarySymbols); + } + } + + return { + log, + humanReadableSummary, + endSummary, + jsonSummary, + summarySymbols, + }; +} + +/** + * Calls the appropriate CLI command to generate a human-readable log summary. + * @param cliServer The cli server client. + * @param outputDir The query's output directory, where all of the logs are located. + * @returns True if the summary and end summary were generated, or false if not. + */ +async function generateHumanReadableLogSummary( + cliServer: CodeQLCliServer, + outputDir: QueryOutputDir, +): Promise { + try { + await cliServer.generateLogSummary( + outputDir.evalLogPath, + outputDir.evalLogSummaryPath, + outputDir.evalLogEndSummaryPath, + ); + return true; + } catch (e) { + void showAndLogWarningMessage( + `Failed to generate human-readable structured evaluator log summary. Reason: ${getErrorMessage( + e, + )}`, + ); + return false; + } +} + +/** + * Logs the end summary to the Output window and log file. + * @param logSummaryPath Path to the human-readable log summary + * @param qs The query server client. + */ +export async function logEndSummary( + endSummary: string, + logger: BaseLogger, +): Promise { + try { + const endSummaryContent = await readFile(endSummary, "utf-8"); + void logger.log(" --- Evaluator Log Summary --- "); + void logger.log(endSummaryContent); + } catch (e) { + void showAndLogWarningMessage( + `Could not read structured evaluator log end of summary file at ${endSummary}.`, + ); + } +} diff --git a/extensions/ql-vscode/src/variant-analysis/export-results.ts b/extensions/ql-vscode/src/variant-analysis/export-results.ts index a866436b7..bab80c731 100644 --- a/extensions/ql-vscode/src/variant-analysis/export-results.ts +++ b/extensions/ql-vscode/src/variant-analysis/export-results.ts @@ -1,14 +1,7 @@ import { join } from "path"; import { ensureDir, writeFile } from "fs-extra"; -import { - CancellationToken, - commands, - Uri, - ViewColumn, - window, - workspace, -} from "vscode"; +import { CancellationToken, Uri, ViewColumn, window, workspace } from "vscode"; import { ProgressCallback, UserCancellationException, @@ -35,6 +28,7 @@ import { RepositoriesFilterSortStateWithIds, } from "../pure/variant-analysis-filter-sort"; import { Credentials } from "../common/authentication"; +import { AppCommandManager } from "../common/commands"; const MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS = 2; @@ -46,6 +40,7 @@ export async function exportVariantAnalysisResults( variantAnalysisManager: VariantAnalysisManager, variantAnalysisId: number, filterSort: RepositoriesFilterSortStateWithIds | undefined, + commandManager: AppCommandManager, credentials: Credentials, ): Promise { await withProgress( @@ -149,6 +144,7 @@ export async function exportVariantAnalysisResults( getAnalysesResults(), repositories?.length ?? 0, exportFormat, + commandManager, credentials, progress, token, @@ -169,6 +165,7 @@ export async function exportVariantAnalysisAnalysisResults( >, expectedAnalysesResultsCount: number, exportFormat: "gist" | "local", + commandManager: AppCommandManager, credentials: Credentials, progress: ProgressCallback, token: CancellationToken, @@ -199,6 +196,7 @@ export async function exportVariantAnalysisAnalysisResults( description, markdownFiles, exportFormat, + commandManager, credentials, progress, token, @@ -243,6 +241,7 @@ export async function exportResults( description: string, markdownFiles: MarkdownFile[], exportFormat: "gist" | "local", + commandManager: AppCommandManager, credentials: Credentials, progress?: ProgressCallback, token?: CancellationToken, @@ -255,6 +254,7 @@ export async function exportResults( await exportToGist( description, markdownFiles, + commandManager, credentials, progress, token, @@ -263,6 +263,7 @@ export async function exportResults( await exportToLocalMarkdown( exportedResultsPath, markdownFiles, + commandManager, progress, token, ); @@ -272,6 +273,7 @@ export async function exportResults( export async function exportToGist( description: string, markdownFiles: MarkdownFile[], + commandManager: AppCommandManager, credentials: Credentials, progress?: ProgressCallback, token?: CancellationToken, @@ -303,7 +305,7 @@ export async function exportToGist( if (!shouldOpenGist) { return; } - return commands.executeCommand("vscode.open", Uri.parse(gistUrl)); + return commandManager.execute("vscode.open", Uri.parse(gistUrl)); }); } } @@ -334,6 +336,7 @@ const buildVariantAnalysisGistDescription = ( async function exportToLocalMarkdown( exportedResultsPath: string, markdownFiles: MarkdownFile[], + commandManager: AppCommandManager, progress?: ProgressCallback, token?: CancellationToken, ) { @@ -366,6 +369,6 @@ async function exportToLocalMarkdown( const summaryFilePath = join(exportedResultsPath, "_summary.md"); const summaryFile = await workspace.openTextDocument(summaryFilePath); await window.showTextDocument(summaryFile, ViewColumn.One); - await commands.executeCommand("revealFileInOS", Uri.file(summaryFilePath)); + await commandManager.execute("revealFileInOS", Uri.file(summaryFilePath)); }); } diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts index 276a3af36..4fe17cb56 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts @@ -6,7 +6,6 @@ import { } from "./gh-api/gh-api-client"; import { CancellationToken, - commands, env, EventEmitter, ExtensionContext, @@ -116,6 +115,7 @@ export class VariantAnalysisManager super(); this.variantAnalysisMonitor = this.push( new VariantAnalysisMonitor( + app, this.shouldCancelMonitorVariantAnalysis.bind(this), ), ); @@ -239,11 +239,11 @@ export class VariantAnalysisManager `Variant analysis ${processedVariantAnalysis.query.name} submitted for processing`, ); - void commands.executeCommand( + void this.app.commands.execute( "codeQL.openVariantAnalysisView", processedVariantAnalysis.id, ); - void commands.executeCommand( + void this.app.commands.execute( "codeQL.monitorVariantAnalysis", processedVariantAnalysis, ); @@ -273,7 +273,7 @@ export class VariantAnalysisManager this.makeResultDownloadChecker(variantAnalysis), )) ) { - void commands.executeCommand( + void this.app.commands.execute( "codeQL.monitorVariantAnalysis", variantAnalysis, ); @@ -317,7 +317,9 @@ export class VariantAnalysisManager } if (!this.views.has(variantAnalysisId)) { // The view will register itself with the manager, so we don't need to do anything here. - this.track(new VariantAnalysisView(this.ctx, variantAnalysisId, this)); + this.track( + new VariantAnalysisView(this.ctx, this.app, variantAnalysisId, this), + ); } const variantAnalysisView = this.views.get(variantAnalysisId)!; @@ -502,10 +504,7 @@ export class VariantAnalysisManager public async monitorVariantAnalysis( variantAnalysis: VariantAnalysis, ): Promise { - await this.variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - this.app.credentials, - ); + await this.variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis); } public async autoDownloadVariantAnalysisResult( @@ -641,7 +640,7 @@ export class VariantAnalysisManager const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysis); - await commands.executeCommand( + await this.app.commands.execute( "vscode.open", Uri.parse(actionsWorkflowRunUrl), ); @@ -689,6 +688,7 @@ export class VariantAnalysisManager this, variantAnalysisId, filterSort, + this.app.commands, this.app.credentials, ); } diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-monitor.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-monitor.ts index 7fa8f3b14..671ddb95e 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-monitor.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-monitor.ts @@ -1,4 +1,4 @@ -import { commands, EventEmitter } from "vscode"; +import { env, EventEmitter } from "vscode"; import { getVariantAnalysis } from "./gh-api/gh-api-client"; import { @@ -13,7 +13,7 @@ import { DisposableObject } from "../pure/disposable-object"; import { sleep } from "../pure/time"; import { getErrorMessage } from "../pure/helpers-pure"; import { showAndLogWarningMessage } from "../helpers"; -import { Credentials } from "../common/authentication"; +import { App } from "../common/app"; export class VariantAnalysisMonitor extends DisposableObject { // With a sleep of 5 seconds, the maximum number of attempts takes @@ -27,6 +27,7 @@ export class VariantAnalysisMonitor extends DisposableObject { readonly onVariantAnalysisChange = this._onVariantAnalysisChange.event; constructor( + private readonly app: App, private readonly shouldCancelMonitor: ( variantAnalysisId: number, ) => Promise, @@ -36,7 +37,6 @@ export class VariantAnalysisMonitor extends DisposableObject { public async monitorVariantAnalysis( variantAnalysis: VariantAnalysis, - credentials: Credentials, ): Promise { let attemptCount = 0; const scannedReposDownloaded: number[] = []; @@ -51,13 +51,17 @@ export class VariantAnalysisMonitor extends DisposableObject { let variantAnalysisSummary: ApiVariantAnalysis; try { variantAnalysisSummary = await getVariantAnalysis( - credentials, + this.app.credentials, variantAnalysis.controllerRepo.id, variantAnalysis.id, ); } catch (e) { void showAndLogWarningMessage( - `Error while monitoring variant analysis: ${getErrorMessage(e)}`, + `Error while monitoring variant analysis ${ + variantAnalysis.query.name + } (${variantAnalysis.query.language}) [${new Date( + variantAnalysis.executionStartTime, + ).toLocaleString(env.language)}]: ${getErrorMessage(e)}`, ); continue; } @@ -87,7 +91,7 @@ export class VariantAnalysisMonitor extends DisposableObject { scannedRepo: VariantAnalysisScannedRepository, variantAnalysisSummary: VariantAnalysis, ) { - void commands.executeCommand( + void this.app.commands.execute( "codeQL.autoDownloadVariantAnalysisResult", scannedRepo, variantAnalysisSummary, diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view-serializer.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view-serializer.ts index 2eeb81793..314a3e0ba 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view-serializer.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view-serializer.ts @@ -2,6 +2,7 @@ import { ExtensionContext, WebviewPanel, WebviewPanelSerializer } from "vscode"; import { VariantAnalysisView } from "./variant-analysis-view"; import { VariantAnalysisState } from "../pure/interface-types"; import { VariantAnalysisViewManager } from "./variant-analysis-view-manager"; +import { App } from "../common/app"; export class VariantAnalysisViewSerializer implements WebviewPanelSerializer { private resolvePromises: Array< @@ -10,7 +11,10 @@ export class VariantAnalysisViewSerializer implements WebviewPanelSerializer { private manager?: VariantAnalysisViewManager; - public constructor(private readonly ctx: ExtensionContext) {} + public constructor( + private readonly ctx: ExtensionContext, + private readonly app: App, + ) {} onExtensionLoaded( manager: VariantAnalysisViewManager, @@ -49,6 +53,7 @@ export class VariantAnalysisViewSerializer implements WebviewPanelSerializer { const view = new VariantAnalysisView( this.ctx, + this.app, variantAnalysisState.variantAnalysisId, manager, ); diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts index ffd07c146..6f2a81838 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts @@ -1,4 +1,4 @@ -import { commands, ExtensionContext, ViewColumn } from "vscode"; +import { ExtensionContext, ViewColumn } from "vscode"; import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview"; import { extLogger } from "../common"; import { @@ -23,6 +23,7 @@ import { telemetryListener } from "../telemetry"; import { redactableError } from "../pure/errors"; import { DataFlowPathsView } from "./data-flow-paths-view"; import { DataFlowPaths } from "./shared/data-flow-paths"; +import { App } from "../common/app"; export class VariantAnalysisView extends AbstractWebview @@ -33,6 +34,7 @@ export class VariantAnalysisView public constructor( ctx: ExtensionContext, + private readonly app: App, public readonly variantAnalysisId: number, private readonly manager: VariantAnalysisViewManager, ) { @@ -118,7 +120,7 @@ export class VariantAnalysisView await this.manager.cancelVariantAnalysis(this.variantAnalysisId); break; case "requestRepositoryResults": - void commands.executeCommand( + void this.app.commands.execute( "codeQL.loadVariantAnalysisRepoResults", this.variantAnalysisId, msg.repositoryFullName, @@ -131,7 +133,7 @@ export class VariantAnalysisView await this.manager.openQueryText(this.variantAnalysisId); break; case "copyRepositoryList": - void commands.executeCommand( + void this.app.commands.execute( "codeQL.copyVariantAnalysisRepoList", this.variantAnalysisId, msg.filterSort, diff --git a/extensions/ql-vscode/src/view/jest.config.ts b/extensions/ql-vscode/src/view/jest.config.ts index 500689302..441e0e970 100644 --- a/extensions/ql-vscode/src/view/jest.config.ts +++ b/extensions/ql-vscode/src/view/jest.config.ts @@ -83,8 +83,8 @@ const config: Config = { // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module moduleNameMapper: { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": - "/test/__mocks__/fileMock.ts", - "\\.(css|less)$": "/test/__mocks__/styleMock.ts", + "/../../test/__mocks__/fileMock.ts", + "\\.(css|less)$": "/../../test/__mocks__/styleMock.ts", }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader @@ -186,7 +186,7 @@ const config: Config = { // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation transformIgnorePatterns: [ // These use ES modules, so need to be transformed - "node_modules/(?!(?:@vscode/webview-ui-toolkit|@microsoft/.+|exenv-es6)/.*)", + "node_modules/(?!(?:@vscode/webview-ui-toolkit|@microsoft/.+|exenv-es6|d3|d3-(.*)|internmap|delaunator|robust-predicates)/.*)", ], // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them diff --git a/extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx b/extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx new file mode 100644 index 000000000..f34e63906 --- /dev/null +++ b/extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; +import { render as reactRender, screen } from "@testing-library/react"; +import { ResultsApp } from "../results"; +import { + Interpretation, + IntoResultsViewMsg, + SortDirection, +} from "../../../pure/interface-types"; +import * as fs from "fs-extra"; +import { resolve } from "path"; +import { ColumnKindCode } from "../../../pure/bqrs-cli-types"; + +const exampleSarif = fs.readJSONSync( + resolve( + __dirname, + "../../../../test/vscode-tests/no-workspace/data/sarif/validSarif.sarif", + ), +); + +describe(ResultsApp.name, () => { + const render = () => reactRender(); + const postMessage = async (msg: IntoResultsViewMsg) => { + // window.postMessage doesn't set the origin correctly, see + // https://github.com/jsdom/jsdom/issues/2745 + window.dispatchEvent( + new MessageEvent("message", { + source: window, + origin: window.location.origin, + data: msg, + }), + ); + + // The event is dispatched asynchronously, so we need to wait for it to be handled. + await new Promise((resolve) => setTimeout(resolve, 0)); + }; + + it("renders results", async () => { + render(); + + const interpretation: Interpretation = { + sourceLocationPrefix: "/a/b/c", + numTruncatedResults: 0, + numTotalResults: 1, + data: { + t: "SarifInterpretationData", + sortState: undefined, + ...exampleSarif, + }, + }; + const message: IntoResultsViewMsg = { + t: "setState", + resultsPath: "/a/b/c/results", + origResultsPaths: { + resultsPath: "/a/b/c/results.bqrs", + interpretedResultsPath: "/a/b/c/interpreted-results.sarif", + }, + sortedResultsMap: { + "1": { + resultsPath: "/a/b/c/results.bqrs", + sortState: { + columnIndex: 1, + sortDirection: SortDirection.asc, + }, + }, + }, + interpretation, + database: { + name: "test-db", + databaseUri: "test-db-uri", + }, + metadata: undefined, // TODO + queryName: "test-query", + queryPath: "/a/b/c/query.ql", + shouldKeepOldResultsWhileRendering: false, + parsedResultSets: { + pageNumber: 1, + pageSize: 1, + numPages: 1, + numInterpretedPages: 1, + resultSetNames: ["#select"], + resultSet: { + t: "InterpretedResultSet", + schema: { + name: "#select", + rows: 1, + columns: [ + { + name: "Path", + kind: ColumnKindCode.STRING, + }, + ], + }, + name: "#select", + interpretation, + }, + }, + }; + await postMessage(message); + + expect( + screen.getByText("'x' is assigned a value but never used."), + ).toBeInTheDocument(); + + await postMessage({ + ...message, + t: "showInterpretedPage", + pageNumber: 1, + numPages: 1, + pageSize: 1, + resultSetNames: ["#select"], + queryName: "test-query", + queryPath: "/a/b/c/query.ql", + interpretation, + }); + + expect( + screen.getByText("'x' is assigned a value but never used."), + ).toBeInTheDocument(); + }); +}); diff --git a/extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts b/extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts index b05ebdb17..a633ff0fc 100644 --- a/extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts +++ b/extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts @@ -1,7 +1,12 @@ import { readdirSync, readFileSync } from "fs-extra"; import { join } from "path"; import * as tmp from "tmp"; -import { Logger, OutputChannelLogger, TeeLogger } from "../../../src/common"; +import { + BaseLogger, + Logger, + OutputChannelLogger, + TeeLogger, +} from "../../../src/common"; jest.setTimeout(999999); @@ -88,7 +93,7 @@ describe("OutputChannelLogger tests", function () { function createSideLogger( logger: Logger, additionalLogLocation: string, - ): Logger { + ): BaseLogger { return new TeeLogger( logger, join(tempFolders.storagePath.name, additionalLogLocation), diff --git a/extensions/ql-vscode/test/vscode-tests/activated-extension/databases/db-panel.test.ts b/extensions/ql-vscode/test/vscode-tests/activated-extension/databases/db-panel.test.ts index 93381dd57..8dd3f19d4 100644 --- a/extensions/ql-vscode/test/vscode-tests/activated-extension/databases/db-panel.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/activated-extension/databases/db-panel.test.ts @@ -1,6 +1,5 @@ -import { commands, extensions, window } from "vscode"; +import { window } from "vscode"; -import { CodeQLExtensionInterface } from "../../../../src/extension"; import { readJson } from "fs-extra"; import * as path from "path"; import { @@ -15,19 +14,18 @@ import { DbListKind } from "../../../../src/databases/db-item"; import { createDbTreeViewItemSystemDefinedList } from "../../../../src/databases/ui/db-tree-view-item"; import { createRemoteSystemDefinedListDbItem } from "../../../factories/db-item-factories"; import { DbConfigStore } from "../../../../src/databases/config/db-config-store"; +import { getActivatedExtension } from "../../global.helper"; +import { createVSCodeCommandManager } from "../../../../src/common/vscode/commands"; +import { AllCommands } from "../../../../src/common/commands"; jest.setTimeout(60_000); describe("Db panel UI commands", () => { - let extension: CodeQLExtensionInterface | Record; let storagePath: string; + const commandManager = createVSCodeCommandManager(); beforeEach(async () => { - extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); + const extension = await getActivatedExtension(); storagePath = extension.ctx.storageUri?.fsPath || extension.ctx.globalStorageUri.fsPath; @@ -36,7 +34,7 @@ describe("Db panel UI commands", () => { it("should add new remote db list", async () => { // Add db list jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1"); - await commands.executeCommand( + await commandManager.execute( "codeQLVariantAnalysisRepositories.addNewList", ); @@ -58,7 +56,7 @@ describe("Db panel UI commands", () => { kind: DbListKind.Local, } as AddListQuickPickItem); jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1"); - await commands.executeCommand( + await commandManager.execute( "codeQLVariantAnalysisRepositories.addNewList", ); @@ -79,7 +77,7 @@ describe("Db panel UI commands", () => { } as RemoteDatabaseQuickPickItem); jest.spyOn(window, "showInputBox").mockResolvedValue("owner1/repo1"); - await commands.executeCommand( + await commandManager.execute( "codeQLVariantAnalysisRepositories.addNewDatabase", ); @@ -102,7 +100,7 @@ describe("Db panel UI commands", () => { } as RemoteDatabaseQuickPickItem); jest.spyOn(window, "showInputBox").mockResolvedValue("owner1"); - await commands.executeCommand( + await commandManager.execute( "codeQLVariantAnalysisRepositories.addNewDatabase", ); @@ -124,7 +122,7 @@ describe("Db panel UI commands", () => { "tooltip", ); - await commands.executeCommand( + await commandManager.execute( "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu", treeViewItem, ); diff --git a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-manager.test.ts index 05e9b5ed6..3405e206b 100644 --- a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-manager.test.ts @@ -1,14 +1,12 @@ import { commands, env, - extensions, TextDocument, TextEditor, Uri, window, workspace, } from "vscode"; -import { CodeQLExtensionInterface } from "../../../../src/extension"; import { extLogger } from "../../../../src/common"; import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client"; import * as ghActionsApiClient from "../../../../src/variant-analysis/gh-api/gh-actions-api-client"; @@ -20,7 +18,7 @@ import { Response } from "node-fetch"; import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager"; import { CodeQLCliServer } from "../../../../src/cli"; -import { storagePath } from "../../global.helper"; +import { getActivatedExtension, storagePath } from "../../global.helper"; import { VariantAnalysisResultsManager } from "../../../../src/variant-analysis/variant-analysis-results-manager"; import { createMockVariantAnalysis } from "../../../factories/variant-analysis/shared/variant-analysis"; import * as VariantAnalysisModule from "../../../../src/variant-analysis/shared/variant-analysis"; @@ -67,11 +65,7 @@ describe("Variant Analysis Manager", () => { scannedRepos, }); - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); + const extension = await getActivatedExtension(); const cli = mockedObject({}); app = new ExtensionApp(extension.ctx); const dbManager = new DbManager(app, new DbConfigStore(app)); diff --git a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-monitor.test.ts b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-monitor.test.ts index 329e85685..a8dd5d3e9 100644 --- a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-monitor.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-monitor.test.ts @@ -1,6 +1,3 @@ -import { commands, extensions } from "vscode"; -import { CodeQLExtensionInterface } from "../../../../src/extension"; - import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client"; import { VariantAnalysisMonitor } from "../../../../src/variant-analysis/variant-analysis-monitor"; import { @@ -23,44 +20,37 @@ import { processUpdatedVariantAnalysis, } from "../../../../src/variant-analysis/variant-analysis-processor"; import { createMockVariantAnalysis } from "../../../factories/variant-analysis/shared/variant-analysis"; -import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager"; -import { testCredentialsWithStub } from "../../../factories/authentication"; +import { createMockApp } from "../../../__mocks__/appMock"; +import { createMockCommandManager } from "../../../__mocks__/commandsMock"; jest.setTimeout(60_000); describe("Variant Analysis Monitor", () => { - let extension: CodeQLExtensionInterface | Record; let mockGetVariantAnalysis: jest.SpiedFunction< typeof ghApiClient.getVariantAnalysis >; let variantAnalysisMonitor: VariantAnalysisMonitor; let shouldCancelMonitor: jest.Mock, [number]>; let variantAnalysis: VariantAnalysis; - let variantAnalysisManager: VariantAnalysisManager; - let mockGetDownloadResult: jest.SpiedFunction< - typeof variantAnalysisManager.autoDownloadVariantAnalysisResult - >; const onVariantAnalysisChangeSpy = jest.fn(); + const mockEecuteCommand = jest.fn(); beforeEach(async () => { variantAnalysis = createMockVariantAnalysis({}); shouldCancelMonitor = jest.fn(); - extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - variantAnalysisMonitor = new VariantAnalysisMonitor(shouldCancelMonitor); + variantAnalysisMonitor = new VariantAnalysisMonitor( + createMockApp({ + commands: createMockCommandManager({ + executeCommand: mockEecuteCommand, + }), + }), + shouldCancelMonitor, + ); variantAnalysisMonitor.onVariantAnalysisChange(onVariantAnalysisChangeSpy); - variantAnalysisManager = extension.variantAnalysisManager; - mockGetDownloadResult = jest - .spyOn(variantAnalysisManager, "autoDownloadVariantAnalysisResult") - .mockResolvedValue(undefined); - mockGetVariantAnalysis = jest .spyOn(ghApiClient, "getVariantAnalysis") .mockRejectedValue(new Error("Not mocked")); @@ -71,10 +61,7 @@ describe("Variant Analysis Monitor", () => { it("should return early if variant analysis should be cancelled", async () => { shouldCancelMonitor.mockResolvedValue(true); - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - ); + await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis); expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled(); }); @@ -88,10 +75,7 @@ describe("Variant Analysis Monitor", () => { }); it("should mark as failed and stop monitoring", async () => { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - ); + await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis); expect(mockGetVariantAnalysis).toHaveBeenCalledTimes(1); @@ -109,7 +93,6 @@ describe("Variant Analysis Monitor", () => { describe("when the variant analysis is in progress", () => { let mockApiResponse: VariantAnalysisApiResponse; let scannedRepos: ApiVariantAnalysisScannedRepository[]; - let succeededRepos: ApiVariantAnalysisScannedRepository[]; describe("when there are successfully scanned repos", () => { beforeEach(async () => { @@ -124,28 +107,18 @@ describe("Variant Analysis Monitor", () => { ]); mockApiResponse = createMockApiResponse("succeeded", scannedRepos); mockGetVariantAnalysis.mockResolvedValue(mockApiResponse); - succeededRepos = scannedRepos.filter( - (r) => r.analysis_status === "succeeded", - ); }); it("should trigger a download extension command for each repo", async () => { const succeededRepos = scannedRepos.filter( (r) => r.analysis_status === "succeeded", ); - const commandSpy = jest - .spyOn(commands, "executeCommand") - .mockResolvedValue(undefined); + await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis); - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - ); - - expect(commandSpy).toBeCalledTimes(succeededRepos.length); + expect(mockEecuteCommand).toBeCalledTimes(succeededRepos.length); succeededRepos.forEach((succeededRepo, index) => { - expect(commandSpy).toHaveBeenNthCalledWith( + expect(mockEecuteCommand).toHaveBeenNthCalledWith( index + 1, "codeQL.autoDownloadVariantAnalysisResult", processScannedRepository(succeededRepo), @@ -153,23 +126,6 @@ describe("Variant Analysis Monitor", () => { ); }); }); - - it("should download all available results", async () => { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - ); - - expect(mockGetDownloadResult).toBeCalledTimes(succeededRepos.length); - - succeededRepos.forEach((succeededRepo, index) => { - expect(mockGetDownloadResult).toHaveBeenNthCalledWith( - index + 1, - processScannedRepository(succeededRepo), - processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse), - ); - }); - }); }); describe("when there are only in progress repos", () => { @@ -182,25 +138,9 @@ describe("Variant Analysis Monitor", () => { }); it("should succeed and not download any repos via a command", async () => { - const commandSpy = jest - .spyOn(commands, "executeCommand") - .mockResolvedValue(undefined); + await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis); - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - ); - - expect(commandSpy).not.toHaveBeenCalled(); - }); - - it("should not try to download any repos", async () => { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - ); - - expect(mockGetDownloadResult).not.toBeCalled(); + expect(mockEecuteCommand).not.toHaveBeenCalled(); }); }); @@ -249,17 +189,10 @@ describe("Variant Analysis Monitor", () => { }); it("should trigger a download extension command for each repo", async () => { - const commandSpy = jest - .spyOn(commands, "executeCommand") - .mockResolvedValue(undefined); - - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - ); + await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis); expect(mockGetVariantAnalysis).toBeCalledTimes(4); - expect(commandSpy).toBeCalledTimes(5); + expect(mockEecuteCommand).toBeCalledTimes(5); }); }); @@ -271,12 +204,9 @@ describe("Variant Analysis Monitor", () => { }); it("should not try to download any repos", async () => { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - ); + await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis); - expect(mockGetDownloadResult).not.toBeCalled(); + expect(mockEecuteCommand).not.toBeCalled(); }); }); }); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/data/codeql-pack.lock.yml b/extensions/ql-vscode/test/vscode-tests/cli-integration/data/codeql-pack.lock.yml index 06dd07fc7..ff4bf9895 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/data/codeql-pack.lock.yml +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/data/codeql-pack.lock.yml @@ -1,4 +1,10 @@ --- -dependencies: {} -compiled: false lockVersion: 1.0.0 +dependencies: + codeql-javascript: + version: 0.5.1 + codeql/regex: + version: 0.0.9 + codeql/tutorial: + version: 0.0.6 +compiled: false diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts index 3ba6d5806..f7efd3cc0 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts @@ -1,14 +1,19 @@ import { join } from "path"; -import { extensions, CancellationToken, Uri, window } from "vscode"; +import { CancellationToken, Uri, window } from "vscode"; -import { CodeQLExtensionInterface } from "../../../src/extension"; import { CodeQLCliServer } from "../../../src/cli"; import { DatabaseManager } from "../../../src/local-databases"; import { importArchiveDatabase, promptImportInternetDatabase, } from "../../../src/databaseFetcher"; -import { cleanDatabases, dbLoc, DB_URL, storagePath } from "../global.helper"; +import { + cleanDatabases, + dbLoc, + DB_URL, + getActivatedExtension, + storagePath, +} from "../global.helper"; import { createMockCommandManager } from "../../__mocks__/commandsMock"; jest.setTimeout(60_000); @@ -30,18 +35,8 @@ describe("DatabaseFetcher", () => { jest.spyOn(window, "showErrorMessage").mockResolvedValue(undefined); jest.spyOn(window, "showInformationMessage").mockResolvedValue(undefined); - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - if ("databaseManager" in extension) { - databaseManager = extension.databaseManager; - } else { - throw new Error( - "Extension not initialized. Make sure cli is downloaded and installed properly.", - ); - } + const extension = await getActivatedExtension(); + databaseManager = extension.databaseManager; await cleanDatabases(databaseManager); }); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts index 954d053db..7f8561bc6 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts @@ -1,9 +1,8 @@ import { join } from "path"; -import { extensions } from "vscode"; import { CodeQLCliServer } from "../../../src/cli"; -import { CodeQLExtensionInterface } from "../../../src/extension"; import { tryGetQueryMetadata } from "../../../src/helpers"; +import { getActivatedExtension } from "../global.helper"; // up to 3 minutes per test jest.setTimeout(3 * 60 * 1000); @@ -14,18 +13,8 @@ describe("helpers (with CLI)", () => { let cli: CodeQLCliServer; beforeEach(async () => { - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - if ("cliServer" in extension) { - cli = extension.cliServer; - } else { - throw new Error( - "Extension not initialized. Make sure cli is downloaded and installed properly.", - ); - } + const extension = await getActivatedExtension(); + cli = extension.cliServer; }); it("should get query metadata when available", async () => { diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts index faacb6db2..2b76797b4 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts @@ -7,12 +7,11 @@ import * as messages from "../../../src/pure/legacy-messages"; import * as qsClient from "../../../src/legacy-query-server/queryserver-client"; import * as cli from "../../../src/cli"; import { CellValue } from "../../../src/pure/bqrs-cli-types"; -import { extensions } from "vscode"; -import { CodeQLExtensionInterface } from "../../../src/extension"; import { describeWithCodeQL } from "../cli"; import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client"; import { extLogger, ProgressReporter } from "../../../src/common"; import { createMockApp } from "../../__mocks__/appMock"; +import { getActivatedExtension } from "../global.helper"; const baseDir = join(__dirname, "../../../test/data"); @@ -112,41 +111,30 @@ describeWithCodeQL()("using the legacy query server", () => { let cliServer: cli.CodeQLCliServer; beforeAll(async () => { - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - if ("cliServer" in extension) { - cliServer = extension.cliServer; - cliServer.quiet = true; + const extension = await getActivatedExtension(); + cliServer = extension.cliServer; + cliServer.quiet = true; - qs = new QueryServerClient( - createMockApp({}), - { - codeQlPath: - (await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) || - "", - debug: false, - cacheSize: 0, - numThreads: 1, - saveCache: false, - timeoutSecs: 0, - }, - cliServer, - { - contextStoragePath: tmpDir.name, - logger: extLogger, - }, - (task) => - task(nullProgressReporter, new CancellationTokenSource().token), - ); - await qs.startQueryServer(); - } else { - throw new Error( - "Extension not initialized. Make sure cli is downloaded and installed properly.", - ); - } + qs = new QueryServerClient( + createMockApp({}), + { + codeQlPath: + (await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) || + "", + debug: false, + cacheSize: 0, + numThreads: 1, + saveCache: false, + timeoutSecs: 0, + }, + cliServer, + { + contextStoragePath: tmpDir.name, + logger: extLogger, + }, + (task) => task(nullProgressReporter, new CancellationTokenSource().token), + ); + await qs.startQueryServer(); }); for (const queryTestCase of queryTestCases) { diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts index 4d47f3b02..dbc4ac848 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts @@ -5,15 +5,19 @@ import * as messages from "../../../src/pure/new-messages"; import * as qsClient from "../../../src/query-server/queryserver-client"; import * as cli from "../../../src/cli"; import { CellValue } from "../../../src/pure/bqrs-cli-types"; -import { extensions, Uri } from "vscode"; -import { CodeQLExtensionInterface } from "../../../src/extension"; +import { Uri } from "vscode"; import { describeWithCodeQL } from "../cli"; import { QueryServerClient } from "../../../src/query-server/queryserver-client"; import { extLogger, ProgressReporter } from "../../../src/common"; import { QueryResultType } from "../../../src/pure/new-messages"; -import { cleanDatabases, dbLoc, storagePath } from "../global.helper"; +import { + cleanDatabases, + dbLoc, + getActivatedExtension, + storagePath, +} from "../global.helper"; import { importArchiveDatabase } from "../../../src/databaseFetcher"; -import { createMockCommandManager } from "../../__mocks__/commandsMock"; +import { createMockApp } from "../../__mocks__/appMock"; const baseDir = join(__dirname, "../../../test/data"); @@ -110,63 +114,54 @@ describeWithCodeQL()("using the new query server", () => { let supportNewQueryServer = true; beforeAll(async () => { - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - if ("cliServer" in extension && "databaseManager" in extension) { - cliServer = extension.cliServer; + const app = createMockApp({}); + const extension = await getActivatedExtension(); + cliServer = extension.cliServer; - cliServer.quiet = true; - if (!(await cliServer.cliConstraints.supportsNewQueryServerForTests())) { - supportNewQueryServer = false; - } - qs = new QueryServerClient( - { - codeQlPath: - (await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) || - "", - debug: false, - cacheSize: 0, - numThreads: 1, - saveCache: false, - timeoutSecs: 0, - }, - cliServer, - { - contextStoragePath: tmpDir.name, - logger: extLogger, - }, - (task) => - task(nullProgressReporter, new CancellationTokenSource().token), - ); - await qs.startQueryServer(); - - // Unlike the old query sevre the new one wants a database and the empty direcrtory is not valid. - // Add a database, but make sure the database manager is empty first - await cleanDatabases(extension.databaseManager); - const uri = Uri.file(dbLoc); - const maybeDbItem = await importArchiveDatabase( - createMockCommandManager(), - uri.toString(true), - extension.databaseManager, - storagePath, - () => { - /**ignore progress */ - }, - token, - ); - - if (!maybeDbItem) { - throw new Error("Could not import database"); - } - db = maybeDbItem.databaseUri.fsPath; - } else { - throw new Error( - "Extension not initialized. Make sure cli is downloaded and installed properly.", - ); + cliServer.quiet = true; + if (!(await cliServer.cliConstraints.supportsNewQueryServerForTests())) { + supportNewQueryServer = false; } + qs = new QueryServerClient( + app, + { + codeQlPath: + (await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) || + "", + debug: false, + cacheSize: 0, + numThreads: 1, + saveCache: false, + timeoutSecs: 0, + }, + cliServer, + { + contextStoragePath: tmpDir.name, + logger: extLogger, + }, + (task) => task(nullProgressReporter, new CancellationTokenSource().token), + ); + await qs.startQueryServer(); + + // Unlike the old query sevre the new one wants a database and the empty direcrtory is not valid. + // Add a database, but make sure the database manager is empty first + await cleanDatabases(extension.databaseManager); + const uri = Uri.file(dbLoc); + const maybeDbItem = await importArchiveDatabase( + app.commands, + uri.toString(true), + extension.databaseManager, + storagePath, + () => { + /**ignore progress */ + }, + token, + ); + + if (!maybeDbItem) { + throw new Error("Could not import database"); + } + db = maybeDbItem.databaseUri.fsPath; }); for (const queryTestCase of queryTestCases) { diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts index eba712e00..cd6940ccd 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts @@ -1,8 +1,7 @@ -import { extensions, window } from "vscode"; +import { window } from "vscode"; import { join } from "path"; import { CodeQLCliServer } from "../../../src/cli"; -import { CodeQLExtensionInterface } from "../../../src/extension"; import { getErrorMessage } from "../../../src/pure/helpers-pure"; import * as helpers from "../../../src/helpers"; @@ -11,6 +10,7 @@ import { handleInstallPackDependencies, } from "../../../src/packaging"; import { mockedQuickPickItem } from "../utils/mocking.helpers"; +import { getActivatedExtension } from "../global.helper"; // up to 3 minutes per test jest.setTimeout(3 * 60 * 1000); @@ -41,18 +41,8 @@ describe("Packaging commands", () => { .spyOn(helpers, "showAndLogInformationMessage") .mockResolvedValue(undefined); - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - if ("cliServer" in extension) { - cli = extension.cliServer; - } else { - throw new Error( - "Extension not initialized. Make sure cli is downloaded and installed properly.", - ); - } + const extension = await getActivatedExtension(); + cli = extension.cliServer; }); it("should download all core query packs", async () => { diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts index b750bbae9..073e4c6ad 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts @@ -1,10 +1,4 @@ -import { - CancellationToken, - commands, - ExtensionContext, - extensions, - Uri, -} from "vscode"; +import { CancellationToken, ExtensionContext, Uri } from "vscode"; import { join, dirname } from "path"; import { pathExistsSync, @@ -16,17 +10,22 @@ import { import { load, dump } from "js-yaml"; import { DatabaseItem, DatabaseManager } from "../../../src/local-databases"; -import { CodeQLExtensionInterface } from "../../../src/extension"; -import { cleanDatabases, dbLoc, storagePath } from "../global.helper"; +import { + cleanDatabases, + dbLoc, + getActivatedExtension, + storagePath, +} from "../global.helper"; import { importArchiveDatabase } from "../../../src/databaseFetcher"; import { CliVersionConstraint, CodeQLCliServer } from "../../../src/cli"; import { describeWithCodeQL } from "../cli"; -import { tmpDir } from "../../../src/helpers"; -import { createInitialQueryInfo } from "../../../src/run-queries-shared"; import { QueryRunner } from "../../../src/queryRunner"; -import { CompletedQueryInfo } from "../../../src/query-results"; import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder"; import { createMockCommandManager } from "../../__mocks__/commandsMock"; +import { LocalQueries } from "../../../src/local-queries"; +import { QueryResultType } from "../../../src/pure/new-messages"; +import { createVSCodeCommandManager } from "../../../src/common/vscode/commands"; +import { AllCommands, QueryServerCommands } from "../../../src/common/commands"; jest.setTimeout(20_000); @@ -38,9 +37,13 @@ describeWithCodeQL()("Queries", () => { let databaseManager: DatabaseManager; let cli: CodeQLCliServer; let qs: QueryRunner; + let localQueries: LocalQueries; const progress = jest.fn(); let token: CancellationToken; let ctx: ExtensionContext; + const appCommandManager = createVSCodeCommandManager(); + const queryServerCommandManager = + createVSCodeCommandManager(); let qlpackFile: string; let qlpackLockFile: string; @@ -48,32 +51,27 @@ describeWithCodeQL()("Queries", () => { let qlFile: string; beforeEach(async () => { - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - if ("databaseManager" in extension) { - databaseManager = extension.databaseManager; - cli = extension.cliServer; - qs = extension.qs; - cli.quiet = true; - ctx = extension.ctx; - qlpackFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.yml`; - qlpackLockFile = `${ctx.storageUri?.fsPath}/quick-queries/codeql-pack.lock.yml`; - oldQlpackLockFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.lock.yml`; - qlFile = `${ctx.storageUri?.fsPath}/quick-queries/quick-query.ql`; - } else { - throw new Error( - "Extension not initialized. Make sure cli is downloaded and installed properly.", - ); - } + const extension = await getActivatedExtension(); + databaseManager = extension.databaseManager; + cli = extension.cliServer; + qs = extension.qs; + localQueries = extension.localQueries; + cli.quiet = true; + ctx = extension.ctx; + qlpackFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.yml`; + qlpackLockFile = `${ctx.storageUri?.fsPath}/quick-queries/codeql-pack.lock.yml`; + oldQlpackLockFile = `${ctx.storageUri?.fsPath}/quick-queries/qlpack.lock.yml`; + qlFile = `${ctx.storageUri?.fsPath}/quick-queries/quick-query.ql`; // Ensure we are starting from a clean slate. safeDel(qlFile); safeDel(qlpackFile); - token = {} as CancellationToken; + token = { + onCancellationRequested: (_) => { + void _; + }, + } as CancellationToken; // Add a database, but make sure the database manager is empty first await cleanDatabases(databaseManager); @@ -143,22 +141,21 @@ describeWithCodeQL()("Queries", () => { } async function runQueryWithExtensions() { - const result = new CompletedQueryInfo( - await qs.compileAndRunQueryAgainstDatabase( - dbItem, - await mockInitialQueryInfo(queryUsingExtensionPath), - join(tmpDir.name, "mock-storage-path"), - progress, - token, - ), + const result = await localQueries.compileAndRunQueryInternal( + false, + Uri.file(queryUsingExtensionPath), + progress, + token, + dbItem, + undefined, ); // Check that query was successful - expect(result.successful).toBe(true); + expect(result.resultType).toBe(QueryResultType.SUCCESS); // Load query results const chunk = await qs.cliServer.bqrsDecode( - result.getResultsPath(SELECT_QUERY_NAME, true), + result.outputDir.bqrsPath, SELECT_QUERY_NAME, { // there should only be one result @@ -174,35 +171,37 @@ describeWithCodeQL()("Queries", () => { it("should run a query", async () => { const queryPath = join(__dirname, "data", "simple-query.ql"); - const result = qs.compileAndRunQueryAgainstDatabase( - dbItem, - await mockInitialQueryInfo(queryPath), - join(tmpDir.name, "mock-storage-path"), + const result = await localQueries.compileAndRunQueryInternal( + false, + Uri.file(queryPath), progress, token, + dbItem, + undefined, ); // just check that the query was successful - expect((await result).successful).toBe(true); + expect(result.resultType).toBe(QueryResultType.SUCCESS); }); // Asserts a fix for bug https://github.com/github/vscode-codeql/issues/733 it("should restart the database and run a query", async () => { - await commands.executeCommand("codeQL.restartQueryServer"); + await appCommandManager.execute("codeQL.restartQueryServer"); const queryPath = join(__dirname, "data", "simple-query.ql"); - const result = await qs.compileAndRunQueryAgainstDatabase( - dbItem, - await mockInitialQueryInfo(queryPath), - join(tmpDir.name, "mock-storage-path"), + const result = await localQueries.compileAndRunQueryInternal( + false, + Uri.file(queryPath), progress, token, + dbItem, + undefined, ); - expect(result.successful).toBe(true); + expect(result.resultType).toBe(QueryResultType.SUCCESS); }); it("should create a quick query", async () => { - await commands.executeCommand("codeQL.quickQuery"); + await queryServerCommandManager.execute("codeQL.quickQuery"); // should have created the quick query file and query pack file expect(pathExistsSync(qlFile)).toBe(true); @@ -235,7 +234,7 @@ describeWithCodeQL()("Queries", () => { }), ); writeFileSync(qlFile, "xxx"); - await commands.executeCommand("codeQL.quickQuery"); + await queryServerCommandManager.execute("codeQL.quickQuery"); // should not have created the quick query file because database schema hasn't changed expect(readFileSync(qlFile, "utf8")).toBe("xxx"); @@ -248,15 +247,4 @@ describeWithCodeQL()("Queries", () => { // ignore } } - - async function mockInitialQueryInfo(queryPath: string) { - return await createInitialQueryInfo( - Uri.file(queryPath), - { - name: dbItem.name, - databaseUri: dbItem.databaseUri.toString(), - }, - false, - ); - } }); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts index d4686fae8..25adbd34a 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts @@ -1,9 +1,8 @@ -import { authentication, extensions, Uri } from "vscode"; +import { authentication, Uri } from "vscode"; import { join } from "path"; import { SemVer } from "semver"; import { CodeQLCliServer, QueryInfoByLanguage } from "../../../src/cli"; -import { CodeQLExtensionInterface } from "../../../src/extension"; import { itWithCodeQL } from "../cli"; import { getOnDiskWorkspaceFolders, @@ -13,6 +12,7 @@ import { import { resolveQueries } from "../../../src/contextual/queryResolver"; import { KeyType } from "../../../src/contextual/keyType"; import { faker } from "@faker-js/faker"; +import { getActivatedExtension } from "../global.helper"; jest.setTimeout(60_000); @@ -24,19 +24,9 @@ describe("Use cli", () => { let supportedLanguages: string[]; beforeEach(async () => { - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - if ("cliServer" in extension) { - cli = extension.cliServer; - supportedLanguages = await cli.getSupportedLanguages(); - } else { - throw new Error( - "Extension not initialized. Make sure cli is downloaded and installed properly.", - ); - } + const extension = await getActivatedExtension(); + cli = extension.cliServer; + supportedLanguages = await cli.getSupportedLanguages(); }); if (process.env.CLI_VERSION && process.env.CLI_VERSION !== "nightly") { diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts index e54d6b071..615f10b30 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts @@ -1,7 +1,9 @@ -import { commands, Selection, window, workspace } from "vscode"; +import { Selection, window, workspace } from "vscode"; import { join, basename } from "path"; import { tmpDir } from "../../../src/helpers"; import { readFile, writeFile, ensureDir, copy } from "fs-extra"; +import { createVSCodeCommandManager } from "../../../src/common/vscode/commands"; +import { AllCommands } from "../../../src/common/commands"; jest.setTimeout(20_000); @@ -9,6 +11,8 @@ jest.setTimeout(20_000); * Integration tests for queries */ describe("SourceMap", () => { + const commandManager = createVSCodeCommandManager(); + it("should jump to QL code", async () => { const root = workspace.workspaceFolders![0].uri.fsPath; const srcFiles = { @@ -32,7 +36,7 @@ describe("SourceMap", () => { expect(summaryDocument.languageId).toBe("ql-summary"); const summaryEditor = await window.showTextDocument(summaryDocument); summaryEditor.selection = new Selection(356, 10, 356, 10); - await commands.executeCommand("codeQL.gotoQL"); + await commandManager.execute("codeQL.gotoQL"); const newEditor = window.activeTextEditor; expect(newEditor).toBeDefined(); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts index dd02364a4..72758b313 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts @@ -1,11 +1,4 @@ -import { - CancellationTokenSource, - commands, - extensions, - Uri, - window, -} from "vscode"; -import { CodeQLExtensionInterface } from "../../../../src/extension"; +import { CancellationTokenSource, commands, Uri, window } from "vscode"; import { extLogger } from "../../../../src/common"; import { setRemoteControllerRepo } from "../../../../src/config"; import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client"; @@ -15,6 +8,7 @@ import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant import { CliVersionConstraint, CodeQLCliServer } from "../../../../src/cli"; import { fixWorkspaceReferences, + getActivatedExtension, restoreWorkspaceReferences, storagePath, } from "../../global.helper"; @@ -48,11 +42,7 @@ describe("Variant Analysis Manager", () => { cancellationTokenSource = new CancellationTokenSource(); - const extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); + const extension = await getActivatedExtension(); cli = extension.cliServer; const app = new ExtensionApp(extension.ctx); const dbManager = new DbManager(app, new DbConfigStore(app)); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts index 060245e12..70a6e93a9 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts @@ -3,16 +3,17 @@ import { resolve } from "path"; import { authentication, commands, - extensions, TextDocument, window, workspace, } from "vscode"; -import { CodeQLExtensionInterface } from "../../../../src/extension"; import { MockGitHubApiServer } from "../../../../src/mocks/mock-gh-api-server"; import { mockedQuickPickItem } from "../../utils/mocking.helpers"; import { setRemoteControllerRepo } from "../../../../src/config"; +import { getActivatedExtension } from "../../global.helper"; +import { createVSCodeCommandManager } from "../../../../src/common/vscode/commands"; +import { AllCommands } from "../../../../src/common/commands"; jest.setTimeout(30_000); @@ -30,6 +31,7 @@ async function showQlDocument(name: string): Promise { } describe("Variant Analysis Submission Integration", () => { + const commandManager = createVSCodeCommandManager(); let quickPickSpy: jest.SpiedFunction; let executeCommandSpy: jest.SpiedFunction; let showErrorMessageSpy: jest.SpiedFunction; @@ -55,11 +57,7 @@ describe("Variant Analysis Submission Integration", () => { .spyOn(window, "showErrorMessage") .mockResolvedValue(undefined); - await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); + await getActivatedExtension(); }); describe("Successful scenario", () => { @@ -73,7 +71,7 @@ describe("Variant Analysis Submission Integration", () => { // Select target language for your query quickPickSpy.mockResolvedValueOnce(mockedQuickPickItem("javascript")); - await commands.executeCommand("codeQL.runVariantAnalysis"); + await commandManager.execute("codeQL.runVariantAnalysis"); expect(executeCommandSpy).toHaveBeenCalledWith( "codeQL.openVariantAnalysisView", @@ -90,7 +88,7 @@ describe("Variant Analysis Submission Integration", () => { it("shows the error message", async () => { await showQlDocument("query.ql"); - await commands.executeCommand("codeQL.runVariantAnalysis"); + await commandManager.execute("codeQL.runVariantAnalysis"); expect(showErrorMessageSpy).toHaveBeenCalledWith( expect.stringContaining( @@ -112,7 +110,7 @@ describe("Variant Analysis Submission Integration", () => { // Select target language for your query quickPickSpy.mockResolvedValueOnce(mockedQuickPickItem("javascript")); - await commands.executeCommand("codeQL.runVariantAnalysis"); + await commandManager.execute("codeQL.runVariantAnalysis"); expect(showErrorMessageSpy).toHaveBeenCalledWith( expect.stringContaining( diff --git a/extensions/ql-vscode/test/vscode-tests/global.helper.ts b/extensions/ql-vscode/test/vscode-tests/global.helper.ts index c4c76cc3f..529533444 100644 --- a/extensions/ql-vscode/test/vscode-tests/global.helper.ts +++ b/extensions/ql-vscode/test/vscode-tests/global.helper.ts @@ -1,10 +1,12 @@ import { join } from "path"; import { load, dump } from "js-yaml"; import { realpathSync, readFileSync, writeFileSync } from "fs-extra"; -import { commands } from "vscode"; +import { CancellationToken, extensions } from "vscode"; import { DatabaseManager } from "../../src/local-databases"; import { CodeQLCliServer } from "../../src/cli"; import { removeWorkspaceRefs } from "../../src/variant-analysis/run-remote-query"; +import { CodeQLExtensionInterface } from "../../src/extension"; +import { ProgressCallback } from "../../src/progress"; // This file contains helpers shared between tests that work with an activated extension. @@ -23,10 +25,23 @@ export function setStoragePath(path: string) { storagePath = path; } -export async function cleanDatabases(databaseManager: DatabaseManager) { - for (const item of databaseManager.databaseItems) { - await commands.executeCommand("codeQLDatabases.removeDatabase", item); +export async function getActivatedExtension(): Promise { + const extension = await extensions + .getExtension("GitHub.vscode-codeql") + ?.activate(); + if (extension === undefined) { + throw new Error( + "Unable to active CodeQL extension. Make sure cli is downloaded and installed properly.", + ); } + return extension; +} + +export async function cleanDatabases(databaseManager: DatabaseManager) { + await databaseManager.removeAllDatabases( + {} as ProgressCallback, + {} as CancellationToken, + ); } /** diff --git a/extensions/ql-vscode/test/vscode-tests/jest.activated-extension.setup.ts b/extensions/ql-vscode/test/vscode-tests/jest.activated-extension.setup.ts index bf750a9ba..3c20ad5a4 100644 --- a/extensions/ql-vscode/test/vscode-tests/jest.activated-extension.setup.ts +++ b/extensions/ql-vscode/test/vscode-tests/jest.activated-extension.setup.ts @@ -1,13 +1,19 @@ import { CUSTOM_CODEQL_PATH_SETTING } from "../../src/config"; -import { ConfigurationTarget, env, extensions } from "vscode"; +import { ConfigurationTarget, env } from "vscode"; import { beforeEachAction as testConfigBeforeEachAction } from "./test-config"; import * as tmp from "tmp"; import { realpathSync } from "fs-extra"; -import { setStoragePath, storagePath } from "./global.helper"; +import { + getActivatedExtension, + setStoragePath, + storagePath, +} from "./global.helper"; -jest.retryTimes(3, { - logErrorsBeforeRetry: true, -}); +if (process.env.CI) { + jest.retryTimes(3, { + logErrorsBeforeRetry: true, + }); +} // create an extension storage location let removeStorage: tmp.DirResult["removeCallback"] | undefined; @@ -33,7 +39,7 @@ export async function beforeAllAction() { removeStorage = dir.removeCallback; // Activate the extension - await extensions.getExtension("GitHub.vscode-codeql")?.activate(); + await getActivatedExtension(); } export async function beforeEachAction() { diff --git a/extensions/ql-vscode/test/vscode-tests/jest.setup.ts b/extensions/ql-vscode/test/vscode-tests/jest.setup.ts index fe58b7f48..e9fd4f6c7 100644 --- a/extensions/ql-vscode/test/vscode-tests/jest.setup.ts +++ b/extensions/ql-vscode/test/vscode-tests/jest.setup.ts @@ -1,9 +1,11 @@ import { env } from "vscode"; import { beforeEachAction } from "./test-config"; -jest.retryTimes(3, { - logErrorsBeforeRetry: true, -}); +if (process.env.CI) { + jest.retryTimes(3, { + logErrorsBeforeRetry: true, + }); +} beforeEach(async () => { jest.spyOn(env, "openExternal").mockResolvedValue(false); diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts index c106110e8..b83573faf 100644 --- a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts @@ -5,6 +5,7 @@ import { CancellationToken, ExtensionContext, Uri, workspace } from "vscode"; import { DatabaseContents, + DatabaseContentsWithDbScheme, DatabaseEventKind, DatabaseItemImpl, DatabaseManager, @@ -687,7 +688,7 @@ describe("local databases", () => { resolveDatabaseContentsSpy = jest .spyOn(DatabaseResolver, "resolveDatabaseContents") - .mockResolvedValue({} as DatabaseContents); + .mockResolvedValue({} as DatabaseContentsWithDbScheme); addDatabaseSourceArchiveFolderSpy = jest.spyOn( databaseManager, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/astBuilder.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/astBuilder.test.ts index 02022aeba..be3b72795 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/astBuilder.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/astBuilder.test.ts @@ -3,8 +3,9 @@ import { readFileSync } from "fs-extra"; import AstBuilder from "../../../../src/contextual/astBuilder"; import { CodeQLCliServer } from "../../../../src/cli"; import { Uri } from "vscode"; -import { QueryWithResults } from "../../../../src/run-queries-shared"; +import { QueryOutputDir } from "../../../../src/run-queries-shared"; import { mockDatabaseItem, mockedObject } from "../../utils/mocking.helpers"; +import path from "path"; /** * @@ -52,11 +53,12 @@ describe("AstBuilder", () => { const astBuilder = createAstBuilder(); const roots = await astBuilder.getRoots(); + const bqrsPath = path.normalize("/a/b/c/results.bqrs"); const options = { entities: ["id", "url", "string"] }; - expect(mockCli.bqrsDecode).toBeCalledWith("/a/b/c", "nodes", options); - expect(mockCli.bqrsDecode).toBeCalledWith("/a/b/c", "edges", options); + expect(mockCli.bqrsDecode).toBeCalledWith(bqrsPath, "nodes", options); + expect(mockCli.bqrsDecode).toBeCalledWith(bqrsPath, "edges", options); expect(mockCli.bqrsDecode).toBeCalledWith( - "/a/b/c", + bqrsPath, "graphProperties", options, ); @@ -137,13 +139,7 @@ describe("AstBuilder", () => { function createAstBuilder() { return new AstBuilder( - { - query: { - resultsPaths: { - resultsPath: "/a/b/c", - }, - }, - } as QueryWithResults, + new QueryOutputDir("/a/b/c"), mockCli, mockDatabaseItem({ resolveSourceFile: undefined, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts index 49bb5117d..98919219d 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts @@ -619,7 +619,7 @@ describe("prepareCodeTour", () => { expect(executeCommand).toHaveBeenCalledWith( "vscode.openFolder", expect.objectContaining({ - path: Uri.parse(tutorialWorkspacePath).fsPath, + path: expect.stringMatching(/tutorial.code-workspace$/), }), ); }); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts index 9adb3d550..8126e93f1 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts @@ -1,6 +1,6 @@ import { - deserializeQueryHistory, - serializeQueryHistory, + readQueryHistoryFromFile, + writeQueryHistoryToFile, } from "../../../../../src/query-history/store/query-history-store"; import { join } from "path"; import { writeFileSync, mkdirpSync, writeFile } from "fs-extra"; @@ -19,7 +19,7 @@ import { QueryHistoryInfo } from "../../../../../src/query-history/query-history import { createMockVariantAnalysisHistoryItem } from "../../../../factories/query-history/variant-analysis-history-item"; import { nanoid } from "nanoid"; -describe("serialize and deserialize", () => { +describe("write and read", () => { let infoSuccessRaw: LocalQueryInfo; let infoSuccessInterpreted: LocalQueryInfo; let infoEarlyFailure: LocalQueryInfo; @@ -93,12 +93,12 @@ describe("serialize and deserialize", () => { ]; }); - it("should serialize and deserialize query history", async () => { + it("should write and read query history", async () => { const allHistoryPath = join(tmpDir.name, "workspace-query-history.json"); - // serialize and deserialize - await serializeQueryHistory(allHistory, allHistoryPath); - const allHistoryActual = await deserializeQueryHistory(allHistoryPath); + // write and read + await writeQueryHistoryToFile(allHistory, allHistoryPath); + const allHistoryActual = await readQueryHistoryFromFile(allHistoryPath); // the dispose methods will be different. Ignore them. allHistoryActual.forEach((info) => { @@ -106,7 +106,7 @@ describe("serialize and deserialize", () => { const completedQuery = info.completedQuery; (completedQuery as any).dispose = undefined; - // these fields should be missing on the deserialized value + // these fields should be missing on the read value // but they are undefined on the original value if (!("logFileLocation" in completedQuery)) { (completedQuery as any).logFileLocation = undefined; @@ -181,7 +181,7 @@ describe("serialize and deserialize", () => { "utf8", ); - const actual = await deserializeQueryHistory(path); + const actual = await readQueryHistoryFromFile(path); expect(actual.length).toEqual(expectedHistory.length); }); @@ -196,7 +196,7 @@ describe("serialize and deserialize", () => { "utf8", ); - const allHistoryActual = await deserializeQueryHistory(badPath); + const allHistoryActual = await readQueryHistoryFromFile(badPath); // version number is invalid. Should return an empty array. expect(allHistoryActual).toEqual([]); }); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts index d576b1004..37067b2bc 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts @@ -13,7 +13,10 @@ import { tmpDir } from "../../../src/helpers"; import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client"; import { CodeQLCliServer } from "../../../src/cli"; import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder"; -import { QueryInProgress } from "../../../src/legacy-query-server/run-queries"; +import { + QueryInProgress, + compileQuery as compileQueryLegacy, +} from "../../../src/legacy-query-server/run-queries"; import { LegacyQueryRunner } from "../../../src/legacy-query-server/legacyRunner"; import { DatabaseItem } from "../../../src/local-databases"; import { DeepPartial, mockedObject } from "../utils/mocking.helpers"; @@ -30,7 +33,6 @@ describe("run-queries", () => { const saveDir = "query-save-dir"; const info = createMockQueryInfo(true, saveDir); - expect(info.compiledQueryPath).toBe(join(saveDir, "compiledQuery.qlo")); expect(info.queryEvalInfo.dilPath).toBe(join(saveDir, "results.dil")); expect(info.queryEvalInfo.resultsPaths.resultsPath).toBe( join(saveDir, "results.bqrs"), @@ -185,14 +187,15 @@ describe("run-queries", () => { queryPath: "", }; - const results = await info.compile( + const results = await compileQueryLegacy( qs as any, mockQlProgram, + undefined, + info.queryEvalInfo, mockProgress as any, mockCancel as any, qs.logger, ); - expect(results).toEqual([{ message: "err", severity: Severity.ERROR }]); expect(qs.sendRequest).toHaveBeenCalledTimes(1); @@ -214,7 +217,7 @@ describe("run-queries", () => { timeoutSecs: 5, }, queryToCheck: mockQlProgram, - resultPath: info.compiledQueryPath, + resultPath: info.queryEvalInfo.compileQueryPath, target: { query: {} }, }, mockCancel, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/test-adapter.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/test-adapter.test.ts index 129efca7a..ccc528155 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/test-adapter.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/test-adapter.test.ts @@ -1,4 +1,3 @@ -import * as fs from "fs-extra"; import { Uri, WorkspaceFolder } from "vscode"; import { QLTestAdapter } from "../../../src/test-adapter"; @@ -19,8 +18,6 @@ jest.mock("fs-extra", () => { }; }); -const mockedFsExtra = jest.mocked(fs); - describe("test-adapter", () => { let adapter: QLTestAdapter; let fakeDatabaseManager: DatabaseManager; @@ -131,8 +128,6 @@ describe("test-adapter", () => { }); it("should reregister testproj databases around test run", async () => { - mockedFsExtra.access.mockResolvedValue(undefined); - currentDatabaseItem = preTestDatabaseItem; databaseItems = [preTestDatabaseItem]; await adapter.run(["/path/to/test/dir"]);