diff --git a/extensions/ql-vscode/.git-blame-ignore-revs b/.git-blame-ignore-revs similarity index 100% rename from extensions/ql-vscode/.git-blame-ignore-revs rename to .git-blame-ignore-revs diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 40995e963..759836537 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,7 +11,7 @@ updates: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "github-actions" - directory: ".github" + directory: "/" schedule: interval: "weekly" day: "thursday" # Thursday is arbitrary diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index aec00fb75..9e3df527d 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -13,4 +13,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v3 - name: 'Dependency Review' - uses: actions/dependency-review-action@v1 + uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 147a29202..77ae4dcdf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,7 +116,7 @@ jobs: perl -i -pe 's/^/## \[UNRELEASED\]\n\n/ if($.==3)' CHANGELOG.md - name: Create version bump PR - uses: peter-evans/create-pull-request@c7f493a8000b8aeb17a1332e326ba76b57cb83eb # v3.4.1 + uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v4.2.3 if: success() with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.vscode/launch.json b/.vscode/launch.json index 8111dd25f..d2479f051 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -38,7 +38,7 @@ }, "args": [ "--projects", - "test" + "test/unit-tests" ], "stopOnEntry": false, "sourceMaps": true, @@ -94,7 +94,7 @@ "cwd": "${workspaceFolder}/extensions/ql-vscode", "args": [ "--projects", - "src/vscode-tests/no-workspace" + "test/vscode-tests/no-workspace" ], "sourceMaps": true, "console": "integratedTerminal", @@ -110,7 +110,7 @@ "cwd": "${workspaceFolder}/extensions/ql-vscode", "args": [ "--projects", - "src/vscode-tests/minimal-workspace" + "test/vscode-tests/minimal-workspace" ], "sourceMaps": true, "console": "integratedTerminal", @@ -126,7 +126,7 @@ "cwd": "${workspaceFolder}/extensions/ql-vscode", "args": [ "--projects", - "src/vscode-tests/cli-integration" + "test/vscode-tests/cli-integration" ], "env": { // Optionally, set the version to use for the integration tests. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95d4e4005..d8854e269 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,14 +95,17 @@ More information about Storybook can be found inside the **Overview** page once We have several types of tests: -* Unit tests: these live in the `tests/pure-tests/` directory +* Unit tests: these live in the `tests/unit-tests/` directory * View tests: these live in `src/view/variant-analysis/__tests__/` -* VSCode integration tests: these live in `src/vscode-tests/no-workspace` and `src/vscode-tests/minimal-workspace` -* CLI integration tests: these live in `src/vscode-tests/cli-integration` +* VSCode integration tests: + * `test/vscode-tests/no-workspace` tests: These are intended to cover functionality that is meant to work before you even have a workspace open. + * `test/vscode-tests/minimal-workspace` tests: These are intended to cover functionality that need a workspace but don't require the full extension to be activated. +* CLI integration tests: these live in `test/vscode-tests/cli-integration` + * These tests are intendended to be cover functionality that is related to the integration between the CodeQL CLI and the extension. The CLI integration tests require an instance of the CodeQL CLI to run so they will require some extra setup steps. When adding new tests to our test suite, please be mindful of whether they need to be in the cli-integration folder. If the tests don't depend on the CLI, they are better suited to being a VSCode integration test. -Any test data you're using (sample projects, config files, etc.) must go in a `src/vscode-tests/*/data` directory. When you run the tests, the test runner will copy the data directory to `out/vscode-tests/*/data`. +Any test data you're using (sample projects, config files, etc.) must go in a `test/vscode-tests/*/data` directory. When you run the tests, the test runner will copy the data directory to `out/vscode-tests/*/data`. #### Running the tests @@ -155,16 +158,16 @@ The CLI integration tests require the CodeQL standard libraries in order to run ##### 1. From the terminal The easiest way to run a single test is to change the `it` of the test to `it.only` and then run the test command with some additional options -to only run tests for this specific file. For example, to run the test `src/vscode-tests/cli-integration/run-queries.test.ts`: +to only run tests for this specific file. For example, to run the test `test/vscode-tests/cli-integration/run-queries.test.ts`: ```shell -npm run cli-integration -- --runTestsByPath src/vscode-tests/cli-integration/run-queries.test.ts +npm run cli-integration -- --runTestsByPath test/vscode-tests/cli-integration/run-queries.test.ts ``` -You can also use the `--testNamePattern` option to run a specific test within a file. For example, to run the test `src/vscode-tests/cli-integration/run-queries.test.ts`: +You can also use the `--testNamePattern` option to run a specific test within a file. For example, to run the test `test/vscode-tests/cli-integration/run-queries.test.ts`: ```shell -npm run cli-integration -- --runTestsByPath src/vscode-tests/cli-integration/run-queries.test.ts --testNamePattern "should create a QueryEvaluationInfo" +npm run cli-integration -- --runTestsByPath test/vscode-tests/cli-integration/run-queries.test.ts --testNamePattern "should create a QueryEvaluationInfo" ``` ##### 2. From VSCode @@ -221,6 +224,7 @@ Pre-recorded scenarios are stored in `./src/mocks/scenarios`. However, it's poss ## Releasing (write access required) +1. Go through [our test plan](/extensions/ql-vscode/docs/test-plan.md) to ensure that the extension is working as expected. 1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top. * Go through all recent PRs and make sure they are properly accounted for. * Make sure all changelog entries have links back to their PR(s) if appropriate. diff --git a/extensions/ql-vscode/.eslintrc.js b/extensions/ql-vscode/.eslintrc.js index 21c97db7f..5aa5e1b9e 100644 --- a/extensions/ql-vscode/.eslintrc.js +++ b/extensions/ql-vscode/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { parserOptions: { ecmaVersion: 2018, sourceType: "module", - project: ["tsconfig.json", "./src/**/tsconfig.json", "./gulpfile.ts/tsconfig.json", "./scripts/tsconfig.json", "./.storybook/tsconfig.json"], + project: ["tsconfig.json", "./src/**/tsconfig.json", "./test/**/tsconfig.json", "./gulpfile.ts/tsconfig.json", "./scripts/tsconfig.json", "./.storybook/tsconfig.json"], }, plugins: [ "github", diff --git a/extensions/ql-vscode/.vscodeignore b/extensions/ql-vscode/.vscodeignore index 72f97ba45..313df6c95 100644 --- a/extensions/ql-vscode/.vscodeignore +++ b/extensions/ql-vscode/.vscodeignore @@ -14,3 +14,4 @@ gulpfile.js/** tsconfig.json .prettierrc vsc-extension-quickstart.md +node_modules/** diff --git a/extensions/ql-vscode/docs/images/highlighted-code-snippet.png b/extensions/ql-vscode/docs/images/highlighted-code-snippet.png new file mode 100644 index 000000000..748947129 Binary files /dev/null and b/extensions/ql-vscode/docs/images/highlighted-code-snippet.png differ diff --git a/extensions/ql-vscode/docs/images/results-table.png b/extensions/ql-vscode/docs/images/results-table.png new file mode 100644 index 000000000..d22f390c6 Binary files /dev/null and b/extensions/ql-vscode/docs/images/results-table.png differ diff --git a/extensions/ql-vscode/docs/test-plan.md b/extensions/ql-vscode/docs/test-plan.md new file mode 100644 index 000000000..5f0ae8c0c --- /dev/null +++ b/extensions/ql-vscode/docs/test-plan.md @@ -0,0 +1,280 @@ +# Test Plan + +This document describes the manual test plan for the QL extension for Visual Studio Code. + +The plan will be executed manually to start with but the goal is to eventually automate parts of the process (based on +effort vs value basis). + +#### What this doesn't cover +We don't need to test features (and permutations of features) that are covered by automated tests. + +### Before releasing the VS Code extension +- Go through the required test cases listed below +- Check major PRs since the previous release for specific one-off things to test. Based on that, you might want to +choose to go through some of the Optional Test Cases. +- Run a query using the existing version of the extension (to generate an "old" query history item) + +## Required Test Cases + +### Pre-requisites + +- Flip the `codeQL.canary` flag. This will enable MRVA in the extension. + +### Test Case 1: MRVA - Running a problem path query and viewing results + +1. Open the [UnsafeJQueryPlugin query](https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql). +2. Run a MRVA against the following repo list: +``` +"test-repo-list": [ + "angular-cn/ng-nice", + "apache/hadoop", + "apache/hive" +] +``` +3. Check that a notification message pops up and the results view is opened. +4. Check the query history. It should: + - Show that an item has been added to the query history + - The item should be marked as "in progress". +5. Once the query starts: + - Check the results view + - Check the code paths view, including the code paths drop down menu. + - Check that the repository filter box works + - Click links to files/locations on GitHub + - Check that the query history item is updated to show the number of results +6. Once the query completes: + - Check that the query history item is updated to show the query status as "complete" + +### Test Case 2: MRVA - Running a problem query and viewing results + +1. Open the [ReDoS query](https://github.com/github/codeql/blob/main/javascript/ql/src/Performance/ReDoS.ql). +2. Run a MRVA against the "Top 10" repositories. +3. Check the notification message. It should: + - Show the number of repos that are going to be queried + - Provide a link to the actions workflow +4. Check the query history. It should: + - Show that an item has been added to the query history + - The item should be marked as "in progress". +5. Once the query starts: + - Check that a notification is shown with a link to the results view + - Check that the results are rendered with an alert message and a highlighted code snippet: +![highlighted-code-snippet](images/highlighted-code-snippet.png) + +### Test Case 3: MRVA - Running a non-problem query and viewing results + +1. Open the [FunLinesOfCode query](https://github.com/github/codeql/blob/main/cpp/ql/src/Metrics/Functions/FunLinesOfCode.ql). +2. Run a MRVA against a single repository (e.g. `google/brotli`). +3. Once the query starts: + - Open the query results + - Check that the results show up in a table: +![results-table](images/results-table.png) + +### Test Case 4: MRVA - Interacting with query history + +1. Click a history item (for MRVA): + - Check that exporting results works + - Check that sorting results works + - Check that copying repo lists works +2. Open the query directory (containing results): + - Check that the correct directory is opened and there are results in it +3. Open variant analysis on GitHub + - Check that the correct workflow is opened + +### Test Case 5: MRVA - Canceling a variant analysis run + +Run one of the above MRVAs, but cancel it from within VS Code: +- Check that the query is canceled and the query history item is updated. +- Check that the workflow run is also canceled. +- Check that any available results are visible in VS Code. + +### Test Case 6: MRVA - Change to a different colour theme + +Open one of the above MRVAs, try changing to a different colour theme and check that everything looks sensible. +Are there any components that are not showing up? + +## Optional Test Cases + +These are mostly aimed at MRVA, but some of them are also applicable to non-MRVA queries. + +### Selecting repositories to run on + +#### Test case 1: Running a query on a single repository +1. When the repository exists and is public + 1. Has a CodeQL database for the correct language + 2. Has a CodeQL database for another language + 3. Does not have any CodeQL databases +2. When the repository exists and is private + 1. Is accessible and has a CodeQL database + 2. Is not accessible +3. When the repository does not exist + +#### Test case 2: Running a query on a custom repository list +1. The repository list is non-empty + 1. All repositories in the list have a CodeQL database + 2. Some but not all repositories in the list have a CodeQL database + 3. No repositories in the list have a CodeQL database +2. The repository list is empty + +#### Test case 3: Running a query on all repositories in an organization +1. The org exists + 1. The org contains repositories that have CodeQL databases + 2. The org contains repositories of the right language but without CodeQL databases + 3. The org contains repositories not of the right language + 4. The org contains private repositories that are inaccessible +2. The org does not exist + +### Using different types of controller repos + +#### Test case 1: Running a query when the controller repository is public +1. Can run queries on public repositories +2. Can not run queries on private repositories + +#### Test case 2: Running a query when the controller repository is private +1. Can run queries on public repositories +2. Can run queries on private repositories + +#### Test case 3: Running a query when the controller repo exists but you do not have write access +1. Cannot run queries + +#### Test case 4: Running a query when the controller repo doesn’t exist +1. Cannot run queries + +#### Test case 5: Running a query when the "config field" for the controller repo is not set +1. Cannot run queries + +### Query History + +This requires running a MRVA query and viewing the query history. + +The first test case specifies actions that you can do when the query is first run and is in "pending" state. We start +with this since it has quite a limited number of actions you can do. + +#### Test case 1: When variant analysis state is "pending" +1. Starts monitoring variant analysis +2. Cannot open query history item +3. Can delete a query history item + 1. Item is removed from list in UI + 2. Files on dist are deleted (can get to files using "open query directory") +4. Can sort query history items + 1. By name + 2. By query date + 3. By result count +5. Cannot open query directory +6. Can open query that produced these results + 1. When the file still exists and has not moved + 2. When the file does not exist +7. Cannot open variant analysis on github +8. Cannot copy repository list +9. Cannot export results +10. Cannot select to create a gist +11. Cannot select to save as markdown +12. Cannot cancel analysis + +#### Test case 2: When the variant analysis state is not "pending" +1. Query history is loaded when VSCode starts +2. Handles when action workflow was canceled while VSCode was closed +3. Can open query history item + 1. Manually by clicking on them + 2. Automatically when VSCode starts (if they were open when VSCode was last used) +4. Can delete a query history item + 1. Item is removed from list in UI + 2. Files on dist are deleted (can get to files using "open query directory") +5. Can sort query history items + 1. By name + 2. By query date + 3. By result count +6. Can open query directory +7. Can open query that produced these results + 1. When the file still exists and has not moved + 2. When the file does not exist +8. Can open variant analysis on github +9. Can copy repository list + 1. Text is copied to clipboard + 2. Text is a valid repository list +10. Can export results +11. Can select to create gist + 1. A gist is created + 2. The first thing in the gist is a summary + 3. Contains a file for each repository with results + 4. A popup links you to the gist +12. Can select to save as markdown + 1. A directory is created on disk + 2. Contains a summary file + 3. Contains a file for each repository with results + 4. A popup allows you to open the directory + +#### Test case 3: When variant analysis state is "in_progress" +1. Starts monitoring variant analysis + 1. Ready results are downloaded +2. Can cancel analysis + 1. Causes the actions run to be canceled + +#### Test case 4: When variant analysis state is in final state ("succeeded"/"failed"/"canceled") +1. Stops monitoring variant analysis + 1. All results are downloaded if state is succeeded + 2. Otherwise, ready results are downloaded, if any are available +2. Cannot cancel analysis + +### MRVA results view + +This requires running a MRVA query and seeing the results view. + +#### Test case 1: When variant analysis state is "pending" +1. Can open a results view +2. Results view opens automatically + - When starting variant analysis run + - When VSCode opens (if view was open when VSCode was closed) +3. Results view is empty + +#### Test case 2: When variant analysis state is not "pending" +1. Can open a results view +2. Results view opens automatically + 1. When starting variant analysis run + 2. When VSCode opens (if view was open when VSCode was closed) +3. Can copy repository list + 1. Text is copied to clipboard + 2. Text is a valid repository list +4. Can export results + 1. Only includes repos that you have selected (also see section from query history) +5. Can cancel analysis +6. Can open query file + 1. When the file still exists and has not moved + 2. When the file does not exist +7. Can open query text +8. Can sort repos + 1. By name + 2. By results + 3. By stars + 4. By last commit +9. Can filter repos +10. Shows correct statistics + 1. Total number of results + 2. Total number of repositories + 3. Duration +11. Can see live results + 1. Results appear in extension as soon as each query is completed +12. Can view interpreted results (i.e. for a "problem" query) + 1. Can view non-path results + 2. Can view code paths for "path-problem" queries +13. Can view raw results (i.e. for a non "problem" query) + 1. Renders a table +14. Can see skipped repositories + 1. Can see repos with no db in a tab + 1. Shown warning that explains the tab + 2. Can see repos with no access in a tab + 1. Shown warning that explains the tab + 3. Only shows tab when there are skipped repos +15. Result downloads + 1. All results are downloaded automatically + 2. Download status is indicated by a spinner (Not currently any indication of progress beyond "downloading" and "not downloading") + 3. Only 3 items are downloaded at a time + 4. Results for completed queries are still downloaded when + 1. Some but not all queries failed + 2. The variant analysis was canceled after some queries completed + +#### Test case 3: When variant analysis state is in "succeeded" state +1. Can view logs +2. All results are downloaded + +#### Test case 4: When variant analysis is in "failed" or "canceled" state +1. Can view logs +1. Results for finished queries are still downloaded. diff --git a/extensions/ql-vscode/gulpfile.ts/appInsights.ts b/extensions/ql-vscode/gulpfile.ts/appInsights.ts index 42afb7c70..9faf27193 100644 --- a/extensions/ql-vscode/gulpfile.ts/appInsights.ts +++ b/extensions/ql-vscode/gulpfile.ts/appInsights.ts @@ -13,7 +13,7 @@ export function injectAppInsightsKey() { } // replace the key - return src(["out/telemetry.js"]) + return src(["out/extension.js"]) .pipe(replace(/REPLACE-APP-INSIGHTS-KEY/, process.env.APP_INSIGHTS_KEY)) .pipe(dest("out/")); } diff --git a/extensions/ql-vscode/gulpfile.ts/deploy.ts b/extensions/ql-vscode/gulpfile.ts/deploy.ts index d2143b231..df740710f 100644 --- a/extensions/ql-vscode/gulpfile.ts/deploy.ts +++ b/extensions/ql-vscode/gulpfile.ts/deploy.ts @@ -22,21 +22,27 @@ const packageFiles = [ "language-configuration.json", "snippets.json", "media", - "node_modules", "out", "workspace-databases-schema.json", ]; +async function copyDirectory( + sourcePath: string, + destPath: string, +): Promise { + console.log(`copying ${sourcePath} to ${destPath}`); + await copy(sourcePath, destPath); +} + async function copyPackage( sourcePath: string, destPath: string, ): Promise { - for (const file of packageFiles) { - console.log( - `copying ${resolve(sourcePath, file)} to ${resolve(destPath, file)}`, - ); - await copy(resolve(sourcePath, file), resolve(destPath, file)); - } + await Promise.all( + packageFiles.map((file) => + copyDirectory(resolve(sourcePath, file), resolve(destPath, file)), + ), + ); } export async function deployPackage( @@ -88,6 +94,12 @@ export async function deployPackage( ); await copyPackage(sourcePath, distPath); + // This is necessary for vsce to know the dependencies + await copyDirectory( + resolve(sourcePath, "node_modules"), + resolve(distPath, "node_modules"), + ); + return { distPath, name: packageJson.name, diff --git a/extensions/ql-vscode/gulpfile.ts/index.ts b/extensions/ql-vscode/gulpfile.ts/index.ts index d40da540b..9a428898c 100644 --- a/extensions/ql-vscode/gulpfile.ts/index.ts +++ b/extensions/ql-vscode/gulpfile.ts/index.ts @@ -1,7 +1,13 @@ -import { series, parallel } from "gulp"; -import { compileTypeScript, watchTypeScript, cleanOutput } from "./typescript"; +import { parallel, series } from "gulp"; +import { + compileEsbuild, + watchEsbuild, + checkTypeScript, + watchCheckTypeScript, + cleanOutput, + copyWasmFiles, +} from "./typescript"; import { compileTextMateGrammar } from "./textmate"; -import { copyTestData, watchTestData } from "./tests"; import { compileView, watchView } from "./webpack"; import { packageExtension } from "./package"; import { injectAppInsightsKey } from "./appInsights"; @@ -9,21 +15,25 @@ import { injectAppInsightsKey } from "./appInsights"; export const buildWithoutPackage = series( cleanOutput, parallel( - compileTypeScript, + compileEsbuild, + copyWasmFiles, + checkTypeScript, compileTextMateGrammar, compileView, - copyTestData, ), ); +export const watch = parallel(watchEsbuild, watchCheckTypeScript, watchView); + export { cleanOutput, compileTextMateGrammar, - watchTypeScript, + watchEsbuild, + watchCheckTypeScript, watchView, - compileTypeScript, - copyTestData, - watchTestData, + compileEsbuild, + copyWasmFiles, + checkTypeScript, injectAppInsightsKey, compileView, }; diff --git a/extensions/ql-vscode/gulpfile.ts/package.ts b/extensions/ql-vscode/gulpfile.ts/package.ts index 77f2bec30..bd71d8763 100644 --- a/extensions/ql-vscode/gulpfile.ts/package.ts +++ b/extensions/ql-vscode/gulpfile.ts/package.ts @@ -3,7 +3,9 @@ import { deployPackage } from "./deploy"; import { spawn } from "child-process-promise"; export async function packageExtension(): Promise { - const deployedPackage = await deployPackage(resolve("package.json")); + const deployedPackage = await deployPackage( + resolve(__dirname, "../package.json"), + ); console.log( `Packaging extension '${deployedPackage.name}@${deployedPackage.version}'...`, ); @@ -16,7 +18,7 @@ export async function packageExtension(): Promise { `${deployedPackage.name}-${deployedPackage.version}.vsix`, ), ]; - const proc = spawn("./node_modules/.bin/vsce", args, { + const proc = spawn(resolve(__dirname, "../node_modules/.bin/vsce"), args, { cwd: deployedPackage.distPath, }); proc.childProcess.stdout!.on("data", (data) => { diff --git a/extensions/ql-vscode/gulpfile.ts/tests.ts b/extensions/ql-vscode/gulpfile.ts/tests.ts deleted file mode 100644 index 88d1e7473..000000000 --- a/extensions/ql-vscode/gulpfile.ts/tests.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { watch, src, dest } from "gulp"; - -export function copyTestData() { - return Promise.all([copyNoWorkspaceData(), copyCliIntegrationData()]); -} - -export function watchTestData() { - return watch(["src/vscode-tests/*/data/**/*"], copyTestData); -} - -function copyNoWorkspaceData() { - return src("src/vscode-tests/no-workspace/data/**/*").pipe( - dest("out/vscode-tests/no-workspace/data"), - ); -} - -function copyCliIntegrationData() { - return src("src/vscode-tests/cli-integration/data/**/*").pipe( - dest("out/vscode-tests/cli-integration/data"), - ); -} diff --git a/extensions/ql-vscode/gulpfile.ts/textmate.ts b/extensions/ql-vscode/gulpfile.ts/textmate.ts index 6d8b216ff..885e627e2 100644 --- a/extensions/ql-vscode/gulpfile.ts/textmate.ts +++ b/extensions/ql-vscode/gulpfile.ts/textmate.ts @@ -1,7 +1,7 @@ -import { src, dest } from "gulp"; +import { dest, src } from "gulp"; import { load } from "js-yaml"; import { obj } from "through2"; -import * as PluginError from "plugin-error"; +import PluginError from "plugin-error"; import * as Vinyl from "vinyl"; /** diff --git a/extensions/ql-vscode/gulpfile.ts/typescript.ts b/extensions/ql-vscode/gulpfile.ts/typescript.ts index 71840c3b0..0f02de6a7 100644 --- a/extensions/ql-vscode/gulpfile.ts/typescript.ts +++ b/extensions/ql-vscode/gulpfile.ts/typescript.ts @@ -1,8 +1,8 @@ import { gray, red } from "ansi-colors"; -import { dest, watch } from "gulp"; -import { init, write } from "gulp-sourcemaps"; -import * as ts from "gulp-typescript"; -import * as del from "del"; +import { dest, src, watch } from "gulp"; +import esbuild from "gulp-esbuild"; +import ts from "gulp-typescript"; +import del from "del"; function goodReporter(): ts.reporter.Reporter { return { @@ -35,20 +35,46 @@ export function cleanOutput() { : Promise.resolve(); } -export function compileTypeScript() { - return tsProject - .src() - .pipe(init()) - .pipe(tsProject(goodReporter())) +export function compileEsbuild() { + return src("./src/extension.ts") .pipe( - write(".", { - includeContent: false, - sourceRoot: ".", + esbuild({ + outfile: "extension.js", + bundle: true, + external: ["vscode", "fsevents"], + format: "cjs", + platform: "node", + target: "es2020", + sourcemap: "linked", + sourceRoot: "..", + loader: { + ".node": "copy", + }, }), ) .pipe(dest("out")); } -export function watchTypeScript() { - watch("src/**/*.ts", compileTypeScript); +export function watchEsbuild() { + watch("src/**/*.ts", compileEsbuild); +} + +export function checkTypeScript() { + // This doesn't actually output the TypeScript files, it just + // runs the TypeScript compiler and reports any errors. + return tsProject.src().pipe(tsProject(goodReporter())); +} + +export function watchCheckTypeScript() { + watch("src/**/*.ts", checkTypeScript); +} + +export function copyWasmFiles() { + // We need to copy this file for the source-map package to work. Without this fie, the source-map + // package is not able to load the WASM file because we are not including the full node_modules + // directory. In version 0.7.4, it is not possible to call SourceMapConsumer.initialize in Node environments + // to configure the path to the WASM file. So, source-map will always load the file from `__dirname/mappings.wasm`. + // In version 0.8.0, it may be possible to do this properly by calling SourceMapConsumer.initialize by + // using the "browser" field in source-map's package.json to load the WASM file from a given file path. + return src("node_modules/source-map/lib/mappings.wasm").pipe(dest("out")); } diff --git a/extensions/ql-vscode/gulpfile.ts/webpack.config.ts b/extensions/ql-vscode/gulpfile.ts/webpack.config.ts index 18a17ba5c..7f84a891d 100644 --- a/extensions/ql-vscode/gulpfile.ts/webpack.config.ts +++ b/extensions/ql-vscode/gulpfile.ts/webpack.config.ts @@ -1,6 +1,6 @@ import { resolve } from "path"; import * as webpack from "webpack"; -import * as MiniCssExtractPlugin from "mini-css-extract-plugin"; +import MiniCssExtractPlugin from "mini-css-extract-plugin"; export const config: webpack.Configuration = { mode: "development", diff --git a/extensions/ql-vscode/gulpfile.ts/webpack.ts b/extensions/ql-vscode/gulpfile.ts/webpack.ts index 92b21a05f..62045334f 100644 --- a/extensions/ql-vscode/gulpfile.ts/webpack.ts +++ b/extensions/ql-vscode/gulpfile.ts/webpack.ts @@ -1,4 +1,4 @@ -import * as webpack from "webpack"; +import webpack from "webpack"; import { config } from "./webpack.config"; export function compileView(cb: (err?: Error) => void) { diff --git a/extensions/ql-vscode/jest.config.js b/extensions/ql-vscode/jest.config.js index eb44660a8..68933e683 100644 --- a/extensions/ql-vscode/jest.config.js +++ b/extensions/ql-vscode/jest.config.js @@ -7,9 +7,9 @@ module.exports = { projects: [ "/src/view", - "/test", - "/src/vscode-tests/cli-integration", - "/src/vscode-tests/no-workspace", - "/src/vscode-tests/minimal-workspace", + "/test/unit-tests", + "/test/vscode-tests/cli-integration", + "/test/vscode-tests/no-workspace", + "/test/vscode-tests/minimal-workspace", ], }; diff --git a/extensions/ql-vscode/package-lock.json b/extensions/ql-vscode/package-lock.json index 9dacb0425..5a85948e7 100644 --- a/extensions/ql-vscode/package-lock.json +++ b/extensions/ql-vscode/package-lock.json @@ -21,13 +21,13 @@ "chokidar": "^3.5.3", "classnames": "~2.2.6", "d3": "^7.6.1", - "d3-graphviz": "^2.6.1", + "d3-graphviz": "^5.0.2", "fs-extra": "^10.0.1", "glob-promise": "^4.2.2", "immutable": "^4.0.0", "js-yaml": "^4.1.0", "minimist": "~1.2.6", - "msw": "^0.47.4", + "msw": "^0.49.0", "nanoid": "^3.2.0", "node-fetch": "~2.6.7", "p-queue": "^6.0.0", @@ -109,6 +109,7 @@ "cross-env": "^7.0.3", "css-loader": "~3.1.0", "del": "^6.0.0", + "esbuild": "^0.15.15", "eslint": "^8.23.1", "eslint-config-prettier": "^8.5.0", "eslint-plugin-github": "^4.4.1", @@ -120,6 +121,7 @@ "file-loader": "^6.2.0", "glob": "^7.1.4", "gulp": "^4.0.2", + "gulp-esbuild": "^0.10.5", "gulp-replace": "^1.1.3", "gulp-sourcemaps": "^3.0.0", "gulp-typescript": "^5.0.1", @@ -410,9 +412,9 @@ } }, "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -2545,6 +2547,38 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.15.tgz", + "integrity": "sha512-JJjZjJi2eBL01QJuWjfCdZxcIgot+VoK6Fq7eKF9w4YHm9hwl7nhBR1o2Wnt/WcANk5l9SkpvrldW1PLuXxcbw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.15.tgz", + "integrity": "sha512-lhz6UNPMDXUhtXSulw8XlFAtSYO26WmHQnCi2Lg2p+/TMiJKNLtZCYUxV4wG6rZMzXmr8InGpNwk+DLT2Hm0PA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -2726,6 +2760,109 @@ "xtend": "~4.0.1" } }, + "node_modules/@hpcc-js/wasm": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@hpcc-js/wasm/-/wasm-2.5.0.tgz", + "integrity": "sha512-G26BamgaHW46f6P8bmkygapgNcy+tTDMwIvCzmMzdp39sxUS1u4gaT/vR2SSDc4x3SfL5RE4B2B8ef/wd429Hg==", + "dependencies": { + "yargs": "17.6.2" + }, + "bin": { + "dot-wasm": "bin/dot-wasm.js" + } + }, + "node_modules/@hpcc-js/wasm/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@hpcc-js/wasm/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@hpcc-js/wasm/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@hpcc-js/wasm/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@hpcc-js/wasm/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/@hpcc-js/wasm/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@hpcc-js/wasm/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@hpcc-js/wasm/node_modules/yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.6", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", @@ -5008,9 +5145,9 @@ } }, "node_modules/@mdx-js/mdx/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -5730,9 +5867,9 @@ "dev": true }, "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -6994,9 +7131,9 @@ "dev": true }, "node_modules/@storybook/builder-webpack4/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -7742,9 +7879,9 @@ "dev": true }, "node_modules/@storybook/builder-webpack5/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -8524,9 +8661,9 @@ "dev": true }, "node_modules/@storybook/core-common/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -10087,9 +10224,9 @@ "dev": true }, "node_modules/@storybook/manager-webpack4/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -11023,9 +11160,9 @@ "dev": true }, "node_modules/@storybook/manager-webpack5/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -11373,9 +11510,9 @@ } }, "node_modules/@storybook/mdx1-csf/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -12041,9 +12178,9 @@ } }, "node_modules/@storybook/source-loader/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -16102,9 +16239,9 @@ } }, "node_modules/babel-loader/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -19404,96 +19541,24 @@ } }, "node_modules/d3-graphviz": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/d3-graphviz/-/d3-graphviz-2.6.1.tgz", - "integrity": "sha512-878AFSagQyr5tTOrM7YiVYeUC2/NoFcOB3/oew+LAML0xekyJSw9j3WOCUMBsc95KYe9XBYZ+SKKuObVya1tJQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/d3-graphviz/-/d3-graphviz-5.0.2.tgz", + "integrity": "sha512-EVRow9rnFgm/L1trbbnu2PGOND11IcSEdWXbrDbz9hH0/Kj3YM2AqMkkTN/EAWgawD5/zryyCy+3Vm05oSJ1Kg==", "dependencies": { - "d3-dispatch": "^1.0.3", - "d3-format": "^1.2.0", - "d3-interpolate": "^1.1.5", - "d3-path": "^1.0.5", - "d3-selection": "^1.1.0", - "d3-timer": "^1.0.6", - "d3-transition": "^1.1.1", - "d3-zoom": "^1.5.0", - "viz.js": "^1.8.2" - } - }, - "node_modules/d3-graphviz/node_modules/d3-color": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" - }, - "node_modules/d3-graphviz/node_modules/d3-dispatch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", - "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" - }, - "node_modules/d3-graphviz/node_modules/d3-drag": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", - "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", - "dependencies": { - "d3-dispatch": "1", - "d3-selection": "1" - } - }, - "node_modules/d3-graphviz/node_modules/d3-ease": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", - "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" - }, - "node_modules/d3-graphviz/node_modules/d3-format": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", - "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" - }, - "node_modules/d3-graphviz/node_modules/d3-interpolate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", - "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", - "dependencies": { - "d3-color": "1" - } - }, - "node_modules/d3-graphviz/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" - }, - "node_modules/d3-graphviz/node_modules/d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" - }, - "node_modules/d3-graphviz/node_modules/d3-timer": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", - "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" - }, - "node_modules/d3-graphviz/node_modules/d3-transition": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", - "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", - "dependencies": { - "d3-color": "1", - "d3-dispatch": "1", - "d3-ease": "1", - "d3-interpolate": "1", - "d3-selection": "^1.1.0", - "d3-timer": "1" - } - }, - "node_modules/d3-graphviz/node_modules/d3-zoom": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", - "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", - "dependencies": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" + "@hpcc-js/wasm": "2.5.0", + "d3-dispatch": "^3.0.1", + "d3-format": "^3.1.0", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-timer": "^3.0.1", + "d3-transition": "^3.0.1", + "d3-zoom": "^3.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "d3-selection": "^3.0.0" } }, "node_modules/d3-hierarchy": { @@ -19516,9 +19581,9 @@ } }, "node_modules/d3-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", "engines": { "node": ">=12" } @@ -20824,6 +20889,363 @@ "es6-symbol": "^3.1.1" } }, + "node_modules/esbuild": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.15.tgz", + "integrity": "sha512-TEw/lwK4Zzld9x3FedV6jy8onOUHqcEX3ADFk4k+gzPUwrxn8nWV62tH0udo8jOtjFodlEfc4ypsqX3e+WWO6w==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.15", + "@esbuild/linux-loong64": "0.15.15", + "esbuild-android-64": "0.15.15", + "esbuild-android-arm64": "0.15.15", + "esbuild-darwin-64": "0.15.15", + "esbuild-darwin-arm64": "0.15.15", + "esbuild-freebsd-64": "0.15.15", + "esbuild-freebsd-arm64": "0.15.15", + "esbuild-linux-32": "0.15.15", + "esbuild-linux-64": "0.15.15", + "esbuild-linux-arm": "0.15.15", + "esbuild-linux-arm64": "0.15.15", + "esbuild-linux-mips64le": "0.15.15", + "esbuild-linux-ppc64le": "0.15.15", + "esbuild-linux-riscv64": "0.15.15", + "esbuild-linux-s390x": "0.15.15", + "esbuild-netbsd-64": "0.15.15", + "esbuild-openbsd-64": "0.15.15", + "esbuild-sunos-64": "0.15.15", + "esbuild-windows-32": "0.15.15", + "esbuild-windows-64": "0.15.15", + "esbuild-windows-arm64": "0.15.15" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.15.tgz", + "integrity": "sha512-F+WjjQxO+JQOva3tJWNdVjouFMLK6R6i5gjDvgUthLYJnIZJsp1HlF523k73hELY20WPyEO8xcz7aaYBVkeg5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.15.tgz", + "integrity": "sha512-attlyhD6Y22jNyQ0fIIQ7mnPvDWKw7k6FKnsXlBvQE6s3z6s6cuEHcSgoirquQc7TmZgVCK5fD/2uxmRN+ZpcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.15.tgz", + "integrity": "sha512-ohZtF8W1SHJ4JWldsPVdk8st0r9ExbAOSrBOh5L+Mq47i696GVwv1ab/KlmbUoikSTNoXEhDzVpxUR/WIO19FQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.15.tgz", + "integrity": "sha512-P8jOZ5zshCNIuGn+9KehKs/cq5uIniC+BeCykvdVhx/rBXSxmtj3CUIKZz4sDCuESMbitK54drf/2QX9QHG5Ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.15.tgz", + "integrity": "sha512-KkTg+AmDXz1IvA9S1gt8dE24C8Thx0X5oM0KGF322DuP+P3evwTL9YyusHAWNsh4qLsR80nvBr/EIYs29VSwuA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.15.tgz", + "integrity": "sha512-FUcML0DRsuyqCMfAC+HoeAqvWxMeq0qXvclZZ/lt2kLU6XBnDA5uKTLUd379WYEyVD4KKFctqWd9tTuk8C/96g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.15.tgz", + "integrity": "sha512-q28Qn5pZgHNqug02aTkzw5sW9OklSo96b5nm17Mq0pDXrdTBcQ+M6Q9A1B+dalFeynunwh/pvfrNucjzwDXj+Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.15.tgz", + "integrity": "sha512-217KPmWMirkf8liO+fj2qrPwbIbhNTGNVtvqI1TnOWJgcMjUWvd677Gq3fTzXEjilkx2yWypVnTswM2KbXgoAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.15.tgz", + "integrity": "sha512-RYVW9o2yN8yM7SB1yaWr378CwrjvGCyGybX3SdzPHpikUHkME2AP55Ma20uNwkNyY2eSYFX9D55kDrfQmQBR4w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.15.tgz", + "integrity": "sha512-/ltmNFs0FivZkYsTzAsXIfLQX38lFnwJTWCJts0IbCqWZQe+jjj0vYBNbI0kmXLb3y5NljiM5USVAO1NVkdh2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.15.tgz", + "integrity": "sha512-PksEPb321/28GFFxtvL33yVPfnMZihxkEv5zME2zapXGp7fA1X2jYeiTUK+9tJ/EGgcNWuwvtawPxJG7Mmn86A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.15.tgz", + "integrity": "sha512-ek8gJBEIhcpGI327eAZigBOHl58QqrJrYYIZBWQCnH3UnXoeWMrMZLeeZL8BI2XMBhP+sQ6ERctD5X+ajL/AIA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.15.tgz", + "integrity": "sha512-H5ilTZb33/GnUBrZMNJtBk7/OXzDHDXjIzoLXHSutwwsLxSNaLxzAaMoDGDd/keZoS+GDBqNVxdCkpuiRW4OSw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.15.tgz", + "integrity": "sha512-jKaLUg78mua3rrtrkpv4Or2dNTJU7bgHN4bEjT4OX4GR7nLBSA9dfJezQouTxMmIW7opwEC5/iR9mpC18utnxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.15.tgz", + "integrity": "sha512-aOvmF/UkjFuW6F36HbIlImJTTx45KUCHJndtKo+KdP8Dhq3mgLRKW9+6Ircpm8bX/RcS3zZMMmaBLkvGY06Gvw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.15.tgz", + "integrity": "sha512-HFFX+WYedx1w2yJ1VyR1Dfo8zyYGQZf1cA69bLdrHzu9svj6KH6ZLK0k3A1/LFPhcEY9idSOhsB2UyU0tHPxgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.15.tgz", + "integrity": "sha512-jOPBudffG4HN8yJXcK9rib/ZTFoTA5pvIKbRrt3IKAGMq1EpBi4xoVoSRrq/0d4OgZLaQbmkHp8RO9eZIn5atA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.15.tgz", + "integrity": "sha512-MDkJ3QkjnCetKF0fKxCyYNBnOq6dmidcwstBVeMtXSgGYTy8XSwBeIE4+HuKiSsG6I/mXEb++px3IGSmTN0XiA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.15.tgz", + "integrity": "sha512-xaAUIB2qllE888SsMU3j9nrqyLbkqqkpQyWVkfwSil6BBPgcPk3zOFitTTncEKCLTQy3XV9RuH7PDj3aJDljWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.15.tgz", + "integrity": "sha512-ttuoCYCIJAFx4UUKKWYnFdrVpoXa3+3WWkXVI6s09U+YjhnyM5h96ewTq/WgQj9LFSIlABQvadHSOQyAVjW5xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -22502,6 +22924,12 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.1.0.tgz", + "integrity": "sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -22705,9 +23133,9 @@ "dev": true }, "node_modules/file-loader/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -24052,6 +24480,69 @@ "node": ">= 0.10" } }, + "node_modules/gulp-esbuild": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/gulp-esbuild/-/gulp-esbuild-0.10.6.tgz", + "integrity": "sha512-GOGG5+1389qU83brN5oLZv6d3wOCmL8SppOjXmc/a32jhuA+5EuPnY4iqfc1FyUCJGbdpZ7hQWN7MaYf0ZJXzw==", + "dev": true, + "dependencies": { + "esbuild": "^0.15.10", + "plugin-error": "^2.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gulp-esbuild/node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-esbuild/node_modules/plugin-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.0.tgz", + "integrity": "sha512-o4bwIOmuFwUg2MU6xt7plGEQY3YyENx6kvwaFZBrUpamA91FdS9w3U+pU0y4OuDoBQe+jf3RLGSfQebSRBEVsQ==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-esbuild/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/gulp-esbuild/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gulp-replace": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", @@ -26736,15 +27227,6 @@ "node": ">=12" } }, - "node_modules/jest-cli/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/jest-config": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.3.1.tgz", @@ -30693,9 +31175,9 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -32308,9 +32790,9 @@ "dev": true }, "node_modules/msw": { - "version": "0.47.4", - "resolved": "https://registry.npmjs.org/msw/-/msw-0.47.4.tgz", - "integrity": "sha512-Psftt8Yfl0+l+qqg9OlmKEsxF8S/vtda0CmlR6y8wTaWrMMzuCDa55n2hEGC0ZRDwuV6FFWc/4CjoDsBpATKBw==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-0.49.0.tgz", + "integrity": "sha512-xX5RMSMjN58j8G/V26Uaf5LP464VltuWyd66TQimLueVYfG47RKydGsd4JW165Jb/gjoaQxh5Tdvv31wdZAOlA==", "hasInstallScript": true, "dependencies": { "@mswjs/cookies": "^0.2.2", @@ -32329,7 +32811,6 @@ "node-fetch": "^2.6.7", "outvariant": "^1.3.0", "path-to-regexp": "^6.2.0", - "statuses": "^2.0.0", "strict-event-emitter": "^0.2.6", "type-fest": "^2.19.0", "yargs": "^17.3.1" @@ -32345,7 +32826,7 @@ "url": "https://opencollective.com/mswjs" }, "peerDependencies": { - "typescript": ">= 4.2.x <= 4.8.x" + "typescript": ">= 4.4.x <= 4.9.x" }, "peerDependenciesMeta": { "typescript": { @@ -32541,14 +33022,6 @@ "node": ">=12" } }, - "node_modules/msw/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", @@ -34365,9 +34838,9 @@ "dev": true }, "node_modules/postcss-loader/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -34874,6 +35347,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/ramda": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", @@ -34979,9 +35458,9 @@ "dev": true }, "node_modules/raw-loader/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -35559,9 +36038,9 @@ } }, "node_modules/remark-mdx/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -36917,6 +37396,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, "engines": { "node": ">= 0.8" } @@ -36993,6 +37473,16 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "node_modules/streamx": { + "version": "2.12.5", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.12.5.tgz", + "integrity": "sha512-Y+nkFw57Z5JHT3zLlqFm3GccOy2FeYdUrrqita6Dd8kr/8enPn9GKa8IYf3/DmEKfZl/E2sWoSKUnd4qhonrgg==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.0.0", + "queue-tick": "^1.0.0" + } + }, "node_modules/strict-event-emitter": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz", @@ -37247,9 +37737,9 @@ "dev": true }, "node_modules/style-loader/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -37541,6 +38031,15 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/telejson": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", @@ -38182,9 +38681,9 @@ } }, "node_modules/ts-jest/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -38205,15 +38704,6 @@ "node": ">=8" } }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/ts-json-schema-generator": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.1.2.tgz", @@ -38273,9 +38763,9 @@ } }, "node_modules/ts-json-schema-generator/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -38421,13 +38911,10 @@ } }, "node_modules/ts-loader/node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -39278,9 +39765,9 @@ "dev": true }, "node_modules/url-loader/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -39621,12 +40108,6 @@ "node": ">=0.10.0" } }, - "node_modules/viz.js": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/viz.js/-/viz.js-1.8.2.tgz", - "integrity": "sha512-W+1+N/hdzLpQZEcvz79n2IgUE9pfx6JLdHh3Kh8RGvLL8P1LdJVQmi2OsDcLdY4QVID4OUy+FPelyerX0nJxIQ==", - "deprecated": "no longer supported" - }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -40748,12 +41229,11 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs/node_modules/ansi-regex": { @@ -41126,9 +41606,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "ms": { @@ -42627,6 +43107,20 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "@esbuild/android-arm": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.15.tgz", + "integrity": "sha512-JJjZjJi2eBL01QJuWjfCdZxcIgot+VoK6Fq7eKF9w4YHm9hwl7nhBR1o2Wnt/WcANk5l9SkpvrldW1PLuXxcbw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.15.tgz", + "integrity": "sha512-lhz6UNPMDXUhtXSulw8XlFAtSYO26WmHQnCi2Lg2p+/TMiJKNLtZCYUxV4wG6rZMzXmr8InGpNwk+DLT2Hm0PA==", + "dev": true, + "optional": true + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -42771,6 +43265,81 @@ } } }, + "@hpcc-js/wasm": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@hpcc-js/wasm/-/wasm-2.5.0.tgz", + "integrity": "sha512-G26BamgaHW46f6P8bmkygapgNcy+tTDMwIvCzmMzdp39sxUS1u4gaT/vR2SSDc4x3SfL5RE4B2B8ef/wd429Hg==", + "requires": { + "yargs": "17.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "17.6.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz", + "integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==", + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + } + } + }, "@humanwhocodes/config-array": { "version": "0.11.6", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", @@ -44531,9 +45100,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "ms": { @@ -45097,9 +45666,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -45967,9 +46536,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-runner": { @@ -46526,9 +47095,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -47112,9 +47681,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-runner": { @@ -48371,9 +48940,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-runner": { @@ -49065,9 +49634,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -49302,9 +49871,9 @@ }, "dependencies": { "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -49774,9 +50343,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -53134,9 +53703,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -55722,98 +56291,18 @@ } }, "d3-graphviz": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/d3-graphviz/-/d3-graphviz-2.6.1.tgz", - "integrity": "sha512-878AFSagQyr5tTOrM7YiVYeUC2/NoFcOB3/oew+LAML0xekyJSw9j3WOCUMBsc95KYe9XBYZ+SKKuObVya1tJQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/d3-graphviz/-/d3-graphviz-5.0.2.tgz", + "integrity": "sha512-EVRow9rnFgm/L1trbbnu2PGOND11IcSEdWXbrDbz9hH0/Kj3YM2AqMkkTN/EAWgawD5/zryyCy+3Vm05oSJ1Kg==", "requires": { - "d3-dispatch": "^1.0.3", - "d3-format": "^1.2.0", - "d3-interpolate": "^1.1.5", - "d3-path": "^1.0.5", - "d3-selection": "^1.1.0", - "d3-timer": "^1.0.6", - "d3-transition": "^1.1.1", - "d3-zoom": "^1.5.0", - "viz.js": "^1.8.2" - }, - "dependencies": { - "d3-color": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", - "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" - }, - "d3-dispatch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", - "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" - }, - "d3-drag": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", - "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", - "requires": { - "d3-dispatch": "1", - "d3-selection": "1" - } - }, - "d3-ease": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", - "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" - }, - "d3-format": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", - "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" - }, - "d3-interpolate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", - "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", - "requires": { - "d3-color": "1" - } - }, - "d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" - }, - "d3-selection": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", - "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" - }, - "d3-timer": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", - "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" - }, - "d3-transition": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", - "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", - "requires": { - "d3-color": "1", - "d3-dispatch": "1", - "d3-ease": "1", - "d3-interpolate": "1", - "d3-selection": "^1.1.0", - "d3-timer": "1" - } - }, - "d3-zoom": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", - "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", - "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" - } - } + "@hpcc-js/wasm": "2.5.0", + "d3-dispatch": "^3.0.1", + "d3-format": "^3.1.0", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-timer": "^3.0.1", + "d3-transition": "^3.0.1", + "d3-zoom": "^3.0.0" } }, "d3-hierarchy": { @@ -55830,9 +56319,9 @@ } }, "d3-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" }, "d3-polygon": { "version": "3.0.1", @@ -56874,6 +57363,176 @@ "es6-symbol": "^3.1.1" } }, + "esbuild": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.15.tgz", + "integrity": "sha512-TEw/lwK4Zzld9x3FedV6jy8onOUHqcEX3ADFk4k+gzPUwrxn8nWV62tH0udo8jOtjFodlEfc4ypsqX3e+WWO6w==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.15.15", + "@esbuild/linux-loong64": "0.15.15", + "esbuild-android-64": "0.15.15", + "esbuild-android-arm64": "0.15.15", + "esbuild-darwin-64": "0.15.15", + "esbuild-darwin-arm64": "0.15.15", + "esbuild-freebsd-64": "0.15.15", + "esbuild-freebsd-arm64": "0.15.15", + "esbuild-linux-32": "0.15.15", + "esbuild-linux-64": "0.15.15", + "esbuild-linux-arm": "0.15.15", + "esbuild-linux-arm64": "0.15.15", + "esbuild-linux-mips64le": "0.15.15", + "esbuild-linux-ppc64le": "0.15.15", + "esbuild-linux-riscv64": "0.15.15", + "esbuild-linux-s390x": "0.15.15", + "esbuild-netbsd-64": "0.15.15", + "esbuild-openbsd-64": "0.15.15", + "esbuild-sunos-64": "0.15.15", + "esbuild-windows-32": "0.15.15", + "esbuild-windows-64": "0.15.15", + "esbuild-windows-arm64": "0.15.15" + } + }, + "esbuild-android-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.15.tgz", + "integrity": "sha512-F+WjjQxO+JQOva3tJWNdVjouFMLK6R6i5gjDvgUthLYJnIZJsp1HlF523k73hELY20WPyEO8xcz7aaYBVkeg5Q==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.15.tgz", + "integrity": "sha512-attlyhD6Y22jNyQ0fIIQ7mnPvDWKw7k6FKnsXlBvQE6s3z6s6cuEHcSgoirquQc7TmZgVCK5fD/2uxmRN+ZpcQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.15.tgz", + "integrity": "sha512-ohZtF8W1SHJ4JWldsPVdk8st0r9ExbAOSrBOh5L+Mq47i696GVwv1ab/KlmbUoikSTNoXEhDzVpxUR/WIO19FQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.15.tgz", + "integrity": "sha512-P8jOZ5zshCNIuGn+9KehKs/cq5uIniC+BeCykvdVhx/rBXSxmtj3CUIKZz4sDCuESMbitK54drf/2QX9QHG5Ag==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.15.tgz", + "integrity": "sha512-KkTg+AmDXz1IvA9S1gt8dE24C8Thx0X5oM0KGF322DuP+P3evwTL9YyusHAWNsh4qLsR80nvBr/EIYs29VSwuA==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.15.tgz", + "integrity": "sha512-FUcML0DRsuyqCMfAC+HoeAqvWxMeq0qXvclZZ/lt2kLU6XBnDA5uKTLUd379WYEyVD4KKFctqWd9tTuk8C/96g==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.15.tgz", + "integrity": "sha512-q28Qn5pZgHNqug02aTkzw5sW9OklSo96b5nm17Mq0pDXrdTBcQ+M6Q9A1B+dalFeynunwh/pvfrNucjzwDXj+Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.15.tgz", + "integrity": "sha512-217KPmWMirkf8liO+fj2qrPwbIbhNTGNVtvqI1TnOWJgcMjUWvd677Gq3fTzXEjilkx2yWypVnTswM2KbXgoAg==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.15.tgz", + "integrity": "sha512-RYVW9o2yN8yM7SB1yaWr378CwrjvGCyGybX3SdzPHpikUHkME2AP55Ma20uNwkNyY2eSYFX9D55kDrfQmQBR4w==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.15.tgz", + "integrity": "sha512-/ltmNFs0FivZkYsTzAsXIfLQX38lFnwJTWCJts0IbCqWZQe+jjj0vYBNbI0kmXLb3y5NljiM5USVAO1NVkdh2g==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.15.tgz", + "integrity": "sha512-PksEPb321/28GFFxtvL33yVPfnMZihxkEv5zME2zapXGp7fA1X2jYeiTUK+9tJ/EGgcNWuwvtawPxJG7Mmn86A==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.15.tgz", + "integrity": "sha512-ek8gJBEIhcpGI327eAZigBOHl58QqrJrYYIZBWQCnH3UnXoeWMrMZLeeZL8BI2XMBhP+sQ6ERctD5X+ajL/AIA==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.15.tgz", + "integrity": "sha512-H5ilTZb33/GnUBrZMNJtBk7/OXzDHDXjIzoLXHSutwwsLxSNaLxzAaMoDGDd/keZoS+GDBqNVxdCkpuiRW4OSw==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.15.tgz", + "integrity": "sha512-jKaLUg78mua3rrtrkpv4Or2dNTJU7bgHN4bEjT4OX4GR7nLBSA9dfJezQouTxMmIW7opwEC5/iR9mpC18utnxQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.15.tgz", + "integrity": "sha512-aOvmF/UkjFuW6F36HbIlImJTTx45KUCHJndtKo+KdP8Dhq3mgLRKW9+6Ircpm8bX/RcS3zZMMmaBLkvGY06Gvw==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.15.tgz", + "integrity": "sha512-HFFX+WYedx1w2yJ1VyR1Dfo8zyYGQZf1cA69bLdrHzu9svj6KH6ZLK0k3A1/LFPhcEY9idSOhsB2UyU0tHPxgQ==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.15.tgz", + "integrity": "sha512-jOPBudffG4HN8yJXcK9rib/ZTFoTA5pvIKbRrt3IKAGMq1EpBi4xoVoSRrq/0d4OgZLaQbmkHp8RO9eZIn5atA==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.15.tgz", + "integrity": "sha512-MDkJ3QkjnCetKF0fKxCyYNBnOq6dmidcwstBVeMtXSgGYTy8XSwBeIE4+HuKiSsG6I/mXEb++px3IGSmTN0XiA==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.15.tgz", + "integrity": "sha512-xaAUIB2qllE888SsMU3j9nrqyLbkqqkpQyWVkfwSil6BBPgcPk3zOFitTTncEKCLTQy3XV9RuH7PDj3aJDljWA==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.15", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.15.tgz", + "integrity": "sha512-ttuoCYCIJAFx4UUKKWYnFdrVpoXa3+3WWkXVI6s09U+YjhnyM5h96ewTq/WgQj9LFSIlABQvadHSOQyAVjW5xQ==", + "dev": true, + "optional": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -58136,6 +58795,12 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "fast-fifo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.1.0.tgz", + "integrity": "sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==", + "dev": true + }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -58303,9 +58968,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -59387,6 +60052,56 @@ } } }, + "gulp-esbuild": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/gulp-esbuild/-/gulp-esbuild-0.10.6.tgz", + "integrity": "sha512-GOGG5+1389qU83brN5oLZv6d3wOCmL8SppOjXmc/a32jhuA+5EuPnY4iqfc1FyUCJGbdpZ7hQWN7MaYf0ZJXzw==", + "dev": true, + "requires": { + "esbuild": "^0.15.10", + "plugin-error": "^2.0.0", + "vinyl": "^3.0.0" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "plugin-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.0.tgz", + "integrity": "sha512-o4bwIOmuFwUg2MU6xt7plGEQY3YyENx6kvwaFZBrUpamA91FdS9w3U+pU0y4OuDoBQe+jf3RLGSfQebSRBEVsQ==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1" + } + }, + "replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true + }, + "vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } + } + } + }, "gulp-replace": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", @@ -61398,12 +62113,6 @@ "y18n": "^5.0.5", "yargs-parser": "^21.0.0" } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true } } }, @@ -64363,9 +65072,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -65683,9 +66392,9 @@ "dev": true }, "msw": { - "version": "0.47.4", - "resolved": "https://registry.npmjs.org/msw/-/msw-0.47.4.tgz", - "integrity": "sha512-Psftt8Yfl0+l+qqg9OlmKEsxF8S/vtda0CmlR6y8wTaWrMMzuCDa55n2hEGC0ZRDwuV6FFWc/4CjoDsBpATKBw==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-0.49.0.tgz", + "integrity": "sha512-xX5RMSMjN58j8G/V26Uaf5LP464VltuWyd66TQimLueVYfG47RKydGsd4JW165Jb/gjoaQxh5Tdvv31wdZAOlA==", "requires": { "@mswjs/cookies": "^0.2.2", "@mswjs/interceptors": "^0.17.5", @@ -65703,7 +66412,6 @@ "node-fetch": "^2.6.7", "outvariant": "^1.3.0", "path-to-regexp": "^6.2.0", - "statuses": "^2.0.0", "strict-event-emitter": "^0.2.6", "type-fest": "^2.19.0", "yargs": "^17.3.1" @@ -65845,11 +66553,6 @@ "y18n": "^5.0.5", "yargs-parser": "^21.0.0" } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" } } }, @@ -67274,9 +67977,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -67657,6 +68360,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "ramda": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", @@ -67737,9 +68446,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -68185,9 +68894,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "ms": { @@ -69266,7 +69975,8 @@ "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true }, "store2": { "version": "2.14.2", @@ -69340,6 +70050,16 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "streamx": { + "version": "2.12.5", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.12.5.tgz", + "integrity": "sha512-Y+nkFw57Z5JHT3zLlqFm3GccOy2FeYdUrrqita6Dd8kr/8enPn9GKa8IYf3/DmEKfZl/E2sWoSKUnd4qhonrgg==", + "dev": true, + "requires": { + "fast-fifo": "^1.0.0", + "queue-tick": "^1.0.0" + } + }, "strict-event-emitter": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz", @@ -69529,9 +70249,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -69762,6 +70482,15 @@ } } }, + "teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "requires": { + "streamx": "^2.12.5" + } + }, "telejson": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", @@ -70249,9 +70978,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "supports-color": { @@ -70262,12 +70991,6 @@ "requires": { "has-flag": "^4.0.0" } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true } } }, @@ -70315,9 +71038,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "minimatch": { @@ -70415,13 +71138,10 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true }, "loader-utils": { "version": "2.0.4", @@ -71050,9 +71770,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -71318,11 +72038,6 @@ } } }, - "viz.js": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/viz.js/-/viz.js-1.8.2.tgz", - "integrity": "sha512-W+1+N/hdzLpQZEcvz79n2IgUE9pfx6JLdHh3Kh8RGvLL8P1LdJVQmi2OsDcLdY4QVID4OUy+FPelyerX0nJxIQ==" - }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -72240,10 +72955,9 @@ } }, "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, "yauzl": { "version": "2.10.0", diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index 5cd9b16a6..822a969c1 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -37,7 +37,7 @@ "onLanguage:ql", "onLanguage:ql-summary", "onView:codeQLDatabases", - "onView:codeQLDatabasesExperimental", + "onView:codeQLVariantAnalysisRepositories", "onView:codeQLQueryHistory", "onView:codeQLAstViewer", "onView:codeQLEvalLogViewer", @@ -59,9 +59,14 @@ "onCommand:codeQL.chooseDatabaseGithub", "onCommand:codeQLDatabases.chooseDatabase", "onCommand:codeQLDatabases.setCurrentDatabase", - "onCommand:codeQLDatabasesExperimental.openConfigFile", - "onCommand:codeQLDatabasesExperimental.addNewList", - "onCommand:codeQLDatabasesExperimental.setSelectedItem", + "onCommand:codeQLVariantAnalysisRepositories.openConfigFile", + "onCommand:codeQLVariantAnalysisRepositories.addNewDatabase", + "onCommand:codeQLVariantAnalysisRepositories.addNewList", + "onCommand:codeQLVariantAnalysisRepositories.setSelectedItem", + "onCommand:codeQLVariantAnalysisRepositories.setSelectedItemContextMenu", + "onCommand:codeQLVariantAnalysisRepositories.renameItemContextMenu", + "onCommand:codeQLVariantAnalysisRepositories.openOnGitHubContextMenu", + "onCommand:codeQLVariantAnalysisRepositories.removeItemContextMenu", "onCommand:codeQL.quickQuery", "onCommand:codeQL.restartQueryServer", "onWebviewPanel:resultsView", @@ -358,19 +363,39 @@ "title": "CodeQL: Copy Version Information" }, { - "command": "codeQLDatabasesExperimental.openConfigFile", + "command": "codeQLVariantAnalysisRepositories.openConfigFile", "title": "Open Database Configuration File", "icon": "$(edit)" }, { - "command": "codeQLDatabasesExperimental.addNewList", + "command": "codeQLVariantAnalysisRepositories.addNewDatabase", + "title": "Add new database", + "icon": "$(add)" + }, + { + "command": "codeQLVariantAnalysisRepositories.addNewList", "title": "Add new list", "icon": "$(new-folder)" }, { - "command": "codeQLDatabasesExperimental.setSelectedItem", - "title": "Select Item", - "icon": "$(circle-small-filled)" + "command": "codeQLVariantAnalysisRepositories.setSelectedItem", + "title": "✓" + }, + { + "command": "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu", + "title": "Select" + }, + { + "command": "codeQLVariantAnalysisRepositories.renameItemContextMenu", + "title": "Rename" + }, + { + "command": "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu", + "title": "Open on GitHub" + }, + { + "command": "codeQLVariantAnalysisRepositories.removeItemContextMenu", + "title": "Remove" }, { "command": "codeQLDatabases.chooseDatabaseFolder", @@ -755,17 +780,38 @@ "group": "navigation" }, { - "command": "codeQLDatabasesExperimental.openConfigFile", - "when": "view == codeQLDatabasesExperimental", + "command": "codeQLVariantAnalysisRepositories.openConfigFile", + "when": "view == codeQLVariantAnalysisRepositories", "group": "navigation" }, { - "command": "codeQLDatabasesExperimental.addNewList", - "when": "view == codeQLDatabasesExperimental && codeQLDatabasesExperimental.configError == false", + "command": "codeQLVariantAnalysisRepositories.addNewDatabase", + "when": "view == codeQLVariantAnalysisRepositories && codeQLVariantAnalysisRepositories.configError == false", + "group": "navigation" + }, + { + "command": "codeQLVariantAnalysisRepositories.addNewList", + "when": "view == codeQLVariantAnalysisRepositories && codeQLVariantAnalysisRepositories.configError == false", "group": "navigation" } ], "view/item/context": [ + { + "command": "codeQLVariantAnalysisRepositories.removeItemContextMenu", + "when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeRemoved/" + }, + { + "command": "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu", + "when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeSelected/" + }, + { + "command": "codeQLVariantAnalysisRepositories.renameItemContextMenu", + "when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeRenamed/" + }, + { + "command": "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu", + "when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeOpenedOnGitHub/" + }, { "command": "codeQLDatabases.setCurrentDatabase", "group": "inline", @@ -792,8 +838,8 @@ "when": "view == codeQLDatabases" }, { - "command": "codeQLDatabasesExperimental.setSelectedItem", - "when": "view == codeQLDatabasesExperimental && viewItem == selectableDbItem", + "command": "codeQLVariantAnalysisRepositories.setSelectedItem", + "when": "view == codeQLVariantAnalysisRepositories && viewItem =~ /canBeSelected/", "group": "inline" }, { @@ -979,15 +1025,35 @@ "when": "resourceScheme == codeql-zip-archive && config.codeQL.canary" }, { - "command": "codeQLDatabasesExperimental.openConfigFile", + "command": "codeQLVariantAnalysisRepositories.openConfigFile", "when": "false" }, { - "command": "codeQLDatabasesExperimental.addNewList", + "command": "codeQLVariantAnalysisRepositories.addNewDatabase", "when": "false" }, { - "command": "codeQLDatabasesExperimental.setSelectedItem", + "command": "codeQLVariantAnalysisRepositories.addNewList", + "when": "false" + }, + { + "command": "codeQLVariantAnalysisRepositories.setSelectedItem", + "when": "false" + }, + { + "command": "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu", + "when": "false" + }, + { + "command": "codeQLVariantAnalysisRepositories.renameItemContextMenu", + "when": "false" + }, + { + "command": "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu", + "when": "false" + }, + { + "command": "codeQLVariantAnalysisRepositories.removeItemContextMenu", "when": "false" }, { @@ -1226,9 +1292,9 @@ "name": "Databases" }, { - "id": "codeQLDatabasesExperimental", - "name": "Databases", - "when": "config.codeQL.canary && config.codeQL.newQueryRunExperience" + "id": "codeQLVariantAnalysisRepositories", + "name": "Variant Analysis Repositories", + "when": "config.codeQL.canary && config.codeQL.variantAnalysis.repositoriesPanel" }, { "id": "codeQLQueryHistory", @@ -1266,17 +1332,14 @@ }, "scripts": { "build": "gulp", - "watch": "npm-run-all -p watch:*", - "watch:extension": "tsc --watch", - "watch:webpack": "gulp watchView", - "watch:files": "gulp watchTestData", + "watch": "gulp watch", "test": "npm-run-all -p test:*", - "test:unit": "cross-env TZ=UTC LANG=en-US jest --projects test", + "test:unit": "cross-env TZ=UTC LANG=en-US jest --projects test/unit-tests", "test:view": "jest --projects src/view", "integration": "npm-run-all integration:*", - "integration:no-workspace": "jest --projects src/vscode-tests/no-workspace", - "integration:minimal-workspace": "jest --projects src/vscode-tests/minimal-workspace", - "cli-integration": "jest --projects src/vscode-tests/cli-integration", + "integration:no-workspace": "jest --projects test/vscode-tests/no-workspace", + "integration:minimal-workspace": "jest --projects test/vscode-tests/minimal-workspace", + "cli-integration": "jest --projects test/vscode-tests/cli-integration", "update-vscode": "node ./node_modules/vscode/bin/install", "format": "prettier --write **/*.{ts,tsx} && eslint . --ext .ts,.tsx --fix", "lint": "eslint . --ext .ts,.tsx --max-warnings=0", @@ -1299,13 +1362,13 @@ "chokidar": "^3.5.3", "classnames": "~2.2.6", "d3": "^7.6.1", - "d3-graphviz": "^2.6.1", + "d3-graphviz": "^5.0.2", "fs-extra": "^10.0.1", "glob-promise": "^4.2.2", "immutable": "^4.0.0", "js-yaml": "^4.1.0", "minimist": "~1.2.6", - "msw": "^0.47.4", + "msw": "^0.49.0", "nanoid": "^3.2.0", "node-fetch": "~2.6.7", "p-queue": "^6.0.0", @@ -1387,6 +1450,7 @@ "cross-env": "^7.0.3", "css-loader": "~3.1.0", "del": "^6.0.0", + "esbuild": "^0.15.15", "eslint": "^8.23.1", "eslint-config-prettier": "^8.5.0", "eslint-plugin-github": "^4.4.1", @@ -1398,6 +1462,7 @@ "file-loader": "^6.2.0", "glob": "^7.1.4", "gulp": "^4.0.2", + "gulp-esbuild": "^0.10.5", "gulp-replace": "^1.1.3", "gulp-sourcemaps": "^3.0.0", "gulp-typescript": "^5.0.1", diff --git a/extensions/ql-vscode/src/authentication.ts b/extensions/ql-vscode/src/authentication.ts index 597c5a078..6abea03f1 100644 --- a/extensions/ql-vscode/src/authentication.ts +++ b/extensions/ql-vscode/src/authentication.ts @@ -13,102 +13,59 @@ const SCOPES = ["repo", "gist"]; * Handles authentication to GitHub, using the VS Code [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication). */ export class Credentials { + /** + * A specific octokit to return, otherwise a new authenticated octokit will be created when needed. + */ private octokit: Octokit.Octokit | undefined; // Explicitly make the constructor private, so that we can't accidentally call the constructor from outside the class // without also initializing the class. - // eslint-disable-next-line @typescript-eslint/no-empty-function - private constructor() {} + private constructor(octokit?: Octokit.Octokit) { + this.octokit = octokit; + } /** - * Initializes an instance of credentials with an octokit instance. + * Initializes a Credentials instance. This will generate octokit instances + * authenticated as the user. If there is not already an authenticated GitHub + * session available then the user will be prompted to log in. * - * Do not call this method until you know you actually need an instance of credentials. - * since calling this method will require the user to log in. - * - * @param context The extension context. * @returns An instance of credentials. */ - static async initialize( - context: vscode.ExtensionContext, - ): Promise { - const c = new Credentials(); - c.registerListeners(context); - c.octokit = await c.createOctokit(false); - return c; + static async initialize(): Promise { + return new Credentials(); } /** * Initializes an instance of credentials with an octokit instance using - * a token from the user's GitHub account. This method is meant to be - * used non-interactive environments such as tests. + * a specific known token. This method is meant to be used in + * non-interactive environments such as tests. * * @param overrideToken The GitHub token to use for authentication. * @returns An instance of credentials. */ static async initializeWithToken(overrideToken: string) { - const c = new Credentials(); - c.octokit = await c.createOctokit(false, overrideToken); - return c; - } - - private async createOctokit( - createIfNone: boolean, - overrideToken?: string, - ): Promise { - if (overrideToken) { - return new Octokit.Octokit({ auth: overrideToken, retry }); - } - - const session = await vscode.authentication.getSession( - GITHUB_AUTH_PROVIDER_ID, - SCOPES, - { createIfNone }, - ); - - if (session) { - return new Octokit.Octokit({ - auth: session.accessToken, - retry, - }); - } else { - return undefined; - } - } - - registerListeners(context: vscode.ExtensionContext): void { - // Sessions are changed when a user logs in or logs out. - context.subscriptions.push( - vscode.authentication.onDidChangeSessions(async (e) => { - if (e.provider.id === GITHUB_AUTH_PROVIDER_ID) { - this.octokit = await this.createOctokit(false); - } - }), - ); + return new Credentials(new Octokit.Octokit({ auth: overrideToken, retry })); } /** * Creates or returns an instance of Octokit. * - * @param requireAuthentication Whether the Octokit instance needs to be authenticated as user. * @returns An instance of Octokit. */ - async getOctokit(requireAuthentication = true): Promise { + async getOctokit(): Promise { if (this.octokit) { return this.octokit; } - this.octokit = await this.createOctokit(requireAuthentication); + const session = await vscode.authentication.getSession( + GITHUB_AUTH_PROVIDER_ID, + SCOPES, + { createIfNone: true }, + ); - if (!this.octokit) { - if (requireAuthentication) { - throw new Error("Did not initialize Octokit."); - } - - // We don't want to set this in this.octokit because that would prevent - // authenticating when requireCredentials is true. - return new Octokit.Octokit({ retry }); - } - return this.octokit; + return new Octokit.Octokit({ + auth: session.accessToken, + retry, + }); } } diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts index 8cc6208af..d2043be9a 100644 --- a/extensions/ql-vscode/src/cli.ts +++ b/extensions/ql-vscode/src/cli.ts @@ -6,7 +6,7 @@ import * as sarif from "sarif"; import { SemVer } from "semver"; import { Readable } from "stream"; import { StringDecoder } from "string_decoder"; -import * as tk from "tree-kill"; +import tk from "tree-kill"; import { promisify } from "util"; import { CancellationToken, commands, Disposable, Uri } from "vscode"; diff --git a/extensions/ql-vscode/src/commandRunner.ts b/extensions/ql-vscode/src/commandRunner.ts index 8b9fa1013..c2eb180c8 100644 --- a/extensions/ql-vscode/src/commandRunner.ts +++ b/extensions/ql-vscode/src/commandRunner.ts @@ -147,7 +147,7 @@ export function commandRunner( return undefined; } finally { const executionTime = Date.now() - startTime; - telemetryListener.sendCommandUsage(commandId, executionTime, error); + telemetryListener?.sendCommandUsage(commandId, executionTime, error); } }); } @@ -201,7 +201,7 @@ export function commandRunnerWithProgress( return undefined; } finally { const executionTime = Date.now() - startTime; - telemetryListener.sendCommandUsage(commandId, executionTime, error); + telemetryListener?.sendCommandUsage(commandId, executionTime, error); } }); } diff --git a/extensions/ql-vscode/src/common/app.ts b/extensions/ql-vscode/src/common/app.ts index f9d35f315..8bf3dcbf4 100644 --- a/extensions/ql-vscode/src/common/app.ts +++ b/extensions/ql-vscode/src/common/app.ts @@ -1,6 +1,7 @@ import { Disposable } from "../pure/disposable-object"; import { AppEventEmitter } from "./events"; import { Logger } from "./logging"; +import { Memento } from "./memento"; export interface App { createEventEmitter(): AppEventEmitter; @@ -11,6 +12,7 @@ export interface App { extensionPath: string; globalStoragePath: string; workspaceStoragePath?: string; + workspaceState: Memento; } export enum AppMode { diff --git a/extensions/ql-vscode/src/common/github-url-identifier-helper.ts b/extensions/ql-vscode/src/common/github-url-identifier-helper.ts new file mode 100644 index 000000000..2499ef663 --- /dev/null +++ b/extensions/ql-vscode/src/common/github-url-identifier-helper.ts @@ -0,0 +1,71 @@ +import { OWNER_REGEX, REPO_REGEX } from "../pure/helpers-pure"; + +/** + * Checks if a string is a valid GitHub NWO. + * @param identifier The GitHub NWO + * @returns + */ +export function isValidGitHubNwo(identifier: string): boolean { + return validGitHubNwoOrOwner(identifier, "nwo"); +} + +/** + * Checks if a string is a valid GitHub owner. + * @param identifier The GitHub owner + * @returns + */ +export function isValidGitHubOwner(identifier: string): boolean { + return validGitHubNwoOrOwner(identifier, "owner"); +} + +function validGitHubNwoOrOwner( + identifier: string, + kind: "owner" | "nwo", +): boolean { + return kind === "owner" + ? OWNER_REGEX.test(identifier) + : REPO_REGEX.test(identifier); +} + +/** + * Extracts an NWO from a GitHub URL. + * @param githubUrl The GitHub repository URL + * @return The corresponding NWO, or undefined if the URL is not valid + */ +export function getNwoFromGitHubUrl(githubUrl: string): string | undefined { + return getNwoOrOwnerFromGitHubUrl(githubUrl, "nwo"); +} + +/** + * Extracts an owner from a GitHub URL. + * @param githubUrl The GitHub repository URL + * @return The corresponding Owner, or undefined if the URL is not valid + */ +export function getOwnerFromGitHubUrl(githubUrl: string): string | undefined { + return getNwoOrOwnerFromGitHubUrl(githubUrl, "owner"); +} + +function getNwoOrOwnerFromGitHubUrl( + githubUrl: string, + kind: "owner" | "nwo", +): string | undefined { + try { + const uri = new URL(githubUrl); + if (uri.protocol !== "https:") { + return; + } + if (uri.hostname !== "github.com" && uri.hostname !== "www.github.com") { + return; + } + const paths = uri.pathname.split("/").filter((segment: string) => segment); + const owner = `${paths[0]}`; + if (kind === "owner") { + return owner ? owner : undefined; + } + const nwo = `${paths[0]}/${paths[1]}`; + return paths[1] ? nwo : undefined; + } catch (e) { + // Ignore the error here, since we catch failures at a higher level. + return; + } +} diff --git a/extensions/ql-vscode/src/common/memento.ts b/extensions/ql-vscode/src/common/memento.ts new file mode 100644 index 000000000..a39754d68 --- /dev/null +++ b/extensions/ql-vscode/src/common/memento.ts @@ -0,0 +1,44 @@ +/** + * A memento represents a storage utility. It can store and retrieve + * values. + * + * It is an interface used by the VS Code API. We replicate it here + * to avoid the dependency to the VS Code API. + */ +export interface Memento { + /** + * Returns the stored keys. + * + * @return The stored keys. + */ + keys(): readonly string[]; + + /** + * Return a value. + * + * @param key A string. + * @return The stored value or `undefined`. + */ + get(key: string): T | undefined; + + /** + * Return a value. + * + * @param key A string. + * @param defaultValue A value that should be returned when there is no + * value (`undefined`) with the given key. + * @return The stored value or the defaultValue. + */ + get(key: string, defaultValue: T): T; + + /** + * Store a value. The value must be JSON-stringifyable. + * + * *Note* that using `undefined` as value removes the key from the underlying + * storage. + * + * @param key A string. + * @param value A value. MUST not contain cyclic references. + */ + update(key: string, value: any): Thenable; +} diff --git a/extensions/ql-vscode/src/common/vscode/vscode-app.ts b/extensions/ql-vscode/src/common/vscode/vscode-app.ts index 69f262fc9..46813ac1c 100644 --- a/extensions/ql-vscode/src/common/vscode/vscode-app.ts +++ b/extensions/ql-vscode/src/common/vscode/vscode-app.ts @@ -3,6 +3,7 @@ import { Disposable } from "../../pure/disposable-object"; import { App, AppMode } from "../app"; import { AppEventEmitter } from "../events"; import { extLogger, Logger } from "../logging"; +import { Memento } from "../memento"; import { VSCodeAppEventEmitter } from "./events"; export class ExtensionApp implements App { @@ -22,6 +23,10 @@ export class ExtensionApp implements App { return this.extensionContext.storageUri?.fsPath; } + public get workspaceState(): Memento { + return this.extensionContext.workspaceState; + } + public get subscriptions(): Disposable[] { return this.extensionContext.subscriptions; } diff --git a/extensions/ql-vscode/src/config.ts b/extensions/ql-vscode/src/config.ts index da6be434b..3ecd1866b 100644 --- a/extensions/ql-vscode/src/config.ts +++ b/extensions/ql-vscode/src/config.ts @@ -476,7 +476,7 @@ export const NO_CACHE_AST_VIEWER = new Setting( ); // Settings for variant analysis -const REMOTE_QUERIES_SETTING = new Setting("variantAnalysis", ROOT_SETTING); +const VARIANT_ANALYSIS_SETTING = new Setting("variantAnalysis", ROOT_SETTING); /** * Lists of GitHub repositories that you want to query remotely via the "Run Variant Analysis" command. @@ -487,7 +487,7 @@ const REMOTE_QUERIES_SETTING = new Setting("variantAnalysis", ROOT_SETTING); */ const REMOTE_REPO_LISTS = new Setting( "repositoryLists", - REMOTE_QUERIES_SETTING, + VARIANT_ANALYSIS_SETTING, ); export function getRemoteRepositoryLists(): @@ -513,7 +513,7 @@ export async function setRemoteRepositoryLists( */ const REPO_LISTS_PATH = new Setting( "repositoryListsPath", - REMOTE_QUERIES_SETTING, + VARIANT_ANALYSIS_SETTING, ); export function getRemoteRepositoryListsPath(): string | undefined { @@ -528,7 +528,7 @@ export function getRemoteRepositoryListsPath(): string | undefined { */ const REMOTE_CONTROLLER_REPO = new Setting( "controllerRepo", - REMOTE_QUERIES_SETTING, + VARIANT_ANALYSIS_SETTING, ); export function getRemoteControllerRepo(): string | undefined { @@ -544,7 +544,7 @@ export async function setRemoteControllerRepo(repo: string | undefined) { * Default value is "main". * Note: This command is only available for internal users. */ -const ACTION_BRANCH = new Setting("actionBranch", REMOTE_QUERIES_SETTING); +const ACTION_BRANCH = new Setting("actionBranch", VARIANT_ANALYSIS_SETTING); export function getActionBranch(): string { return ACTION_BRANCH.getValue() || "main"; @@ -559,16 +559,15 @@ export function isVariantAnalysisLiveResultsEnabled(): boolean { } /** - * A flag indicating whether to use the new query run experience which involves - * using a new database panel. + * A flag indicating whether to use the new "variant analysis repositories" panel. */ -const NEW_QUERY_RUN_EXPERIENCE = new Setting( - "newQueryRunExperience", - ROOT_SETTING, +const VARIANT_ANALYSIS_REPOS_PANEL = new Setting( + "repositoriesPanel", + VARIANT_ANALYSIS_SETTING, ); -export function isNewQueryRunExperienceEnabled(): boolean { - return !!NEW_QUERY_RUN_EXPERIENCE.getValue(); +export function isVariantAnalysisReposPanelEnabled(): boolean { + return !!VARIANT_ANALYSIS_REPOS_PANEL.getValue(); } // Settings for mocking the GitHub API. diff --git a/extensions/ql-vscode/src/databaseFetcher.ts b/extensions/ql-vscode/src/databaseFetcher.ts index f3d46e370..d3280bea1 100644 --- a/extensions/ql-vscode/src/databaseFetcher.ts +++ b/extensions/ql-vscode/src/databaseFetcher.ts @@ -23,9 +23,9 @@ import { extLogger } from "./common"; import { Credentials } from "./authentication"; import { getErrorMessage } from "./pure/helpers-pure"; import { - convertGitHubUrlToNwo, - looksLikeGithubRepo, -} from "./databases/github-nwo"; + getNwoFromGitHubUrl, + isValidGitHubNwo, +} from "./common/github-url-identifier-helper"; /** * Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file. @@ -100,19 +100,16 @@ export async function promptImportGithubDatabase( return; } - if (!looksLikeGithubRepo(githubRepo)) { + const nwo = getNwoFromGitHubUrl(githubRepo) || githubRepo; + if (!isValidGitHubNwo(nwo)) { throw new Error(`Invalid GitHub repository: ${githubRepo}`); } const octokit = credentials - ? await credentials.getOctokit(true) + ? await credentials.getOctokit() : new Octokit.Octokit({ retry }); - const result = await convertGithubNwoToDatabaseUrl( - githubRepo, - octokit, - progress, - ); + const result = await convertGithubNwoToDatabaseUrl(nwo, octokit, progress); if (!result) { return; } @@ -446,7 +443,7 @@ export async function findDirWithFile( } export async function convertGithubNwoToDatabaseUrl( - githubRepo: string, + nwo: string, octokit: Octokit.Octokit, progress: ProgressCallback, ): Promise< @@ -458,7 +455,6 @@ export async function convertGithubNwoToDatabaseUrl( | undefined > { try { - const nwo = convertGitHubUrlToNwo(githubRepo) || githubRepo; const [owner, repo] = nwo.split("/"); const response = await octokit.request( @@ -480,7 +476,7 @@ export async function convertGithubNwoToDatabaseUrl( }; } catch (e) { void extLogger.log(`Error: ${getErrorMessage(e)}`); - throw new Error(`Unable to get database for '${githubRepo}'`); + throw new Error(`Unable to get database for '${nwo}'`); } } diff --git a/extensions/ql-vscode/src/databases.ts b/extensions/ql-vscode/src/databases.ts index 3a7ce23f4..f1f0a4e2d 100644 --- a/extensions/ql-vscode/src/databases.ts +++ b/extensions/ql-vscode/src/databases.ts @@ -1,5 +1,5 @@ import { pathExists, stat, remove } from "fs-extra"; -import * as glob from "glob-promise"; +import { promise as glob } from "glob-promise"; import { join, basename, resolve, relative, dirname, extname } from "path"; import * as vscode from "vscode"; import * as cli from "./cli"; @@ -21,6 +21,7 @@ import { DisposableObject } from "./pure/disposable-object"; import { Logger, extLogger } from "./common"; import { getErrorMessage } from "./pure/helpers-pure"; import { QueryRunner } from "./queryRunner"; +import { pathsEqual } from "./pure/files"; /** * databases.ts @@ -523,7 +524,11 @@ export class DatabaseItemImpl implements DatabaseItem { // database for /one/two/three/test.ql is at /one/two/three/three.testproj const testdir = dirname(testPath); const testdirbase = basename(testdir); - return databasePath == join(testdir, `${testdirbase}.testproj`); + return pathsEqual( + databasePath, + join(testdir, `${testdirbase}.testproj`), + process.platform, + ); } } catch { // No information available for test path - assume database is unaffected. @@ -924,7 +929,7 @@ export class DatabaseManager extends DisposableObject { // Delete folder from file system only if it is controlled by the extension if (this.isExtensionControlledLocation(item.databaseUri)) { void extLogger.log("Deleting database from filesystem."); - remove(item.databaseUri.fsPath).then( + await remove(item.databaseUri.fsPath).then( () => void extLogger.log(`Deleted '${item.databaseUri.fsPath}'`), (e) => void extLogger.log( diff --git a/extensions/ql-vscode/src/databases/config/db-config-store.ts b/extensions/ql-vscode/src/databases/config/db-config-store.ts index fc5536816..69f10c2e6 100644 --- a/extensions/ql-vscode/src/databases/config/db-config-store.ts +++ b/extensions/ql-vscode/src/databases/config/db-config-store.ts @@ -3,7 +3,14 @@ import { join } from "path"; import { cloneDbConfig, DbConfig, - ExpandedDbItem, + removeLocalDb, + removeLocalList, + removeRemoteList, + removeRemoteOwner, + removeRemoteRepo, + renameLocalDb, + renameLocalList, + renameRemoteList, SelectedDbItem, } from "./db-config"; import * as chokidar from "chokidar"; @@ -16,6 +23,13 @@ import { DbConfigValidationErrorKind, } from "../db-validation-errors"; import { ValueResult } from "../../common/value-result"; +import { + LocalDatabaseDbItem, + LocalListDbItem, + VariantAnalysisUserDefinedListDbItem, + DbItem, + DbItemKind, +} from "../db-item"; export class DbConfigStore extends DisposableObject { public readonly onDidChangeConfig: AppEvent; @@ -28,7 +42,10 @@ export class DbConfigStore extends DisposableObject { private configErrors: DbConfigValidationError[]; private configWatcher: chokidar.FSWatcher | undefined; - public constructor(private readonly app: App) { + public constructor( + private readonly app: App, + private readonly shouldWatchConfig = true, + ) { super(); const storagePath = app.workspaceStoragePath || app.globalStoragePath; @@ -44,7 +61,9 @@ export class DbConfigStore extends DisposableObject { public async initialize(): Promise { await this.loadConfig(); - this.watchConfig(); + if (this.shouldWatchConfig) { + this.watchConfig(); + } } public dispose(disposeHandler?: DisposeHandler): void { @@ -73,7 +92,7 @@ export class DbConfigStore extends DisposableObject { throw Error("Cannot select database item if config is not loaded"); } - const config: DbConfig = { + const config = { ...this.config, selected: dbItem, }; @@ -81,15 +100,110 @@ export class DbConfigStore extends DisposableObject { await this.writeConfig(config); } - public async updateExpandedState(expandedItems: ExpandedDbItem[]) { + public async removeDbItem(dbItem: DbItem): Promise { if (!this.config) { - throw Error("Cannot update expansion state if config is not loaded"); + throw Error("Cannot remove item if config is not loaded"); } - const config: DbConfig = { - ...this.config, - expanded: expandedItems, - }; + let config: DbConfig; + + switch (dbItem.kind) { + case DbItemKind.LocalList: + config = removeLocalList(this.config, dbItem.listName); + break; + case DbItemKind.VariantAnalysisUserDefinedList: + config = removeRemoteList(this.config, dbItem.listName); + break; + case DbItemKind.LocalDatabase: + // When we start using local databases these need to be removed from disk as well. + config = removeLocalDb( + this.config, + dbItem.databaseName, + dbItem.parentListName, + ); + break; + case DbItemKind.RemoteRepo: + config = removeRemoteRepo( + this.config, + dbItem.repoFullName, + dbItem.parentListName, + ); + break; + case DbItemKind.RemoteOwner: + config = removeRemoteOwner(this.config, dbItem.ownerName); + break; + default: + throw Error(`Type '${dbItem.kind}' cannot be removed`); + } + + await this.writeConfig(config); + } + + public async addRemoteRepo( + repoNwo: string, + parentList?: string, + ): Promise { + if (!this.config) { + throw Error("Cannot add remote repo if config is not loaded"); + } + + if (repoNwo === "") { + throw Error("Repository name cannot be empty"); + } + + if (this.doesRemoteDbExist(repoNwo)) { + throw Error( + `A remote repository with the name '${repoNwo}' already exists`, + ); + } + + const config = cloneDbConfig(this.config); + if (parentList) { + const parent = config.databases.variantAnalysis.repositoryLists.find( + (list) => list.name === parentList, + ); + if (!parent) { + throw Error(`Cannot find parent list '${parentList}'`); + } else { + parent.repositories.push(repoNwo); + } + } else { + config.databases.variantAnalysis.repositories.push(repoNwo); + } + await this.writeConfig(config); + } + + public async addRemoteOwner(owner: string): Promise { + if (!this.config) { + throw Error("Cannot add remote owner if config is not loaded"); + } + + if (owner === "") { + throw Error("Owner name cannot be empty"); + } + + if (this.doesRemoteOwnerExist(owner)) { + throw Error(`A remote owner with the name '${owner}' already exists`); + } + + const config = cloneDbConfig(this.config); + config.databases.variantAnalysis.owners.push(owner); + + await this.writeConfig(config); + } + + public async addLocalList(listName: string): Promise { + if (!this.config) { + throw Error("Cannot add local list if config is not loaded"); + } + + this.validateLocalListName(listName); + + const config = cloneDbConfig(this.config); + config.databases.local.lists.push({ + name: listName, + databases: [], + }); await this.writeConfig(config); } @@ -99,12 +213,10 @@ export class DbConfigStore extends DisposableObject { throw Error("Cannot add remote list if config is not loaded"); } - if (this.doesRemoteListExist(listName)) { - throw Error(`A remote list with the name '${listName}' already exists`); - } + this.validateRemoteListName(listName); - const config: DbConfig = cloneDbConfig(this.config); - config.databases.remote.repositoryLists.push({ + const config = cloneDbConfig(this.config); + config.databases.variantAnalysis.repositoryLists.push({ name: listName, repositories: [], }); @@ -112,16 +224,126 @@ export class DbConfigStore extends DisposableObject { await this.writeConfig(config); } + public async renameLocalList( + currentDbItem: LocalListDbItem, + newName: string, + ) { + if (!this.config) { + throw Error("Cannot rename local list if config is not loaded"); + } + + this.validateLocalListName(newName); + + const updatedConfig = renameLocalList( + this.config, + currentDbItem.listName, + newName, + ); + + await this.writeConfig(updatedConfig); + } + + public async renameRemoteList( + currentDbItem: VariantAnalysisUserDefinedListDbItem, + newName: string, + ) { + if (!this.config) { + throw Error("Cannot rename remote list if config is not loaded"); + } + + this.validateRemoteListName(newName); + + const updatedConfig = renameRemoteList( + this.config, + currentDbItem.listName, + newName, + ); + + await this.writeConfig(updatedConfig); + } + + public async renameLocalDb( + currentDbItem: LocalDatabaseDbItem, + newName: string, + parentListName?: string, + ): Promise { + if (!this.config) { + throw Error("Cannot rename local db if config is not loaded"); + } + + this.validateLocalDbName(newName); + + const updatedConfig = renameLocalDb( + this.config, + currentDbItem.databaseName, + newName, + parentListName, + ); + + await this.writeConfig(updatedConfig); + } + public doesRemoteListExist(listName: string): boolean { if (!this.config) { throw Error("Cannot check remote list existence if config is not loaded"); } - return this.config.databases.remote.repositoryLists.some( + return this.config.databases.variantAnalysis.repositoryLists.some( (l) => l.name === listName, ); } + public doesLocalListExist(listName: string): boolean { + if (!this.config) { + throw Error("Cannot check local list existence if config is not loaded"); + } + + return this.config.databases.local.lists.some((l) => l.name === listName); + } + + public doesLocalDbExist(dbName: string, listName?: string): boolean { + if (!this.config) { + throw Error( + "Cannot check remote database existence if config is not loaded", + ); + } + + if (listName) { + return this.config.databases.local.lists.some( + (l) => + l.name === listName && l.databases.some((d) => d.name === dbName), + ); + } + + return this.config.databases.local.databases.some((d) => d.name === dbName); + } + + public doesRemoteDbExist(dbName: string, listName?: string): boolean { + if (!this.config) { + throw Error( + "Cannot check remote database existence if config is not loaded", + ); + } + + if (listName) { + return this.config.databases.variantAnalysis.repositoryLists.some( + (l) => l.name === listName && l.repositories.includes(dbName), + ); + } + + return this.config.databases.variantAnalysis.repositories.includes(dbName); + } + + public doesRemoteOwnerExist(owner: string): boolean { + if (!this.config) { + throw Error( + "Cannot check remote owner existence if config is not loaded", + ); + } + + return this.config.databases.variantAnalysis.owners.includes(owner); + } + private async writeConfig(config: DbConfig): Promise { await outputJSON(this.configPath, config, { spaces: 2, @@ -161,14 +383,14 @@ export class DbConfigStore extends DisposableObject { this.config = newConfig; await this.app.executeCommand( "setContext", - "codeQLDatabasesExperimental.configError", + "codeQLVariantAnalysisRepositories.configError", false, ); } else { this.config = undefined; await this.app.executeCommand( "setContext", - "codeQLDatabasesExperimental.configError", + "codeQLVariantAnalysisRepositories.configError", true, ); } @@ -195,14 +417,14 @@ export class DbConfigStore extends DisposableObject { this.config = newConfig; void this.app.executeCommand( "setContext", - "codeQLDatabasesExperimental.configError", + "codeQLVariantAnalysisRepositories.configError", false, ); } else { this.config = undefined; void this.app.executeCommand( "setContext", - "codeQLDatabasesExperimental.configError", + "codeQLVariantAnalysisRepositories.configError", true, ); } @@ -229,7 +451,7 @@ export class DbConfigStore extends DisposableObject { private createEmptyConfig(): DbConfig { return { databases: { - remote: { + variantAnalysis: { repositoryLists: [], owners: [], repositories: [], @@ -239,7 +461,36 @@ export class DbConfigStore extends DisposableObject { databases: [], }, }, - expanded: [], }; } + + private validateLocalListName(listName: string): void { + if (listName === "") { + throw Error("List name cannot be empty"); + } + + if (this.doesLocalListExist(listName)) { + throw Error(`A local list with the name '${listName}' already exists`); + } + } + + private validateRemoteListName(listName: string): void { + if (listName === "") { + throw Error("List name cannot be empty"); + } + + if (this.doesRemoteListExist(listName)) { + throw Error(`A remote list with the name '${listName}' already exists`); + } + } + + private validateLocalDbName(dbName: string): void { + if (dbName === "") { + throw Error("Database name cannot be empty"); + } + + if (this.doesLocalDbExist(dbName)) { + throw Error(`A local database with the name '${dbName}' already exists`); + } + } } diff --git a/extensions/ql-vscode/src/databases/config/db-config-validator.ts b/extensions/ql-vscode/src/databases/config/db-config-validator.ts index ee820181b..fcd0615a5 100644 --- a/extensions/ql-vscode/src/databases/config/db-config-validator.ts +++ b/extensions/ql-vscode/src/databases/config/db-config-validator.ts @@ -57,7 +57,7 @@ export class DbConfigValidator { } const duplicateRemoteDbLists = findDuplicateStrings( - dbConfig.databases.remote.repositoryLists.map((n) => n.name), + dbConfig.databases.variantAnalysis.repositoryLists.map((n) => n.name), ); if (duplicateRemoteDbLists.length > 0) { errors.push(buildError(duplicateRemoteDbLists)); @@ -83,7 +83,7 @@ export class DbConfigValidator { } const duplicateRemoteDbs = findDuplicateStrings( - dbConfig.databases.remote.repositories, + dbConfig.databases.variantAnalysis.repositories, ); if (duplicateRemoteDbs.length > 0) { errors.push(buildError(duplicateRemoteDbs)); @@ -111,7 +111,7 @@ export class DbConfigValidator { } } - for (const list of dbConfig.databases.remote.repositoryLists) { + for (const list of dbConfig.databases.variantAnalysis.repositoryLists) { const dups = findDuplicateStrings(list.repositories); if (dups.length > 0) { errors.push(buildError(list.name, dups)); @@ -124,7 +124,9 @@ export class DbConfigValidator { private validateOwners(dbConfig: DbConfig): DbConfigValidationError[] { const errors: DbConfigValidationError[] = []; - const dups = findDuplicateStrings(dbConfig.databases.remote.owners); + const dups = findDuplicateStrings( + dbConfig.databases.variantAnalysis.owners, + ); if (dups.length > 0) { errors.push({ kind: DbConfigValidationErrorKind.DuplicateNames, diff --git a/extensions/ql-vscode/src/databases/config/db-config.ts b/extensions/ql-vscode/src/databases/config/db-config.ts index cc5c777c2..5afa3497d 100644 --- a/extensions/ql-vscode/src/databases/config/db-config.ts +++ b/extensions/ql-vscode/src/databases/config/db-config.ts @@ -2,12 +2,11 @@ export interface DbConfig { databases: DbConfigDatabases; - expanded: ExpandedDbItem[]; selected?: SelectedDbItem; } export interface DbConfigDatabases { - remote: RemoteDbConfig; + variantAnalysis: RemoteDbConfig; local: LocalDbConfig; } @@ -15,17 +14,17 @@ export type SelectedDbItem = | SelectedLocalUserDefinedList | SelectedLocalDatabase | SelectedRemoteSystemDefinedList - | SelectedRemoteUserDefinedList + | SelectedVariantAnalysisUserDefinedList | SelectedRemoteOwner | SelectedRemoteRepository; export enum SelectedDbItemKind { LocalUserDefinedList = "localUserDefinedList", LocalDatabase = "localDatabase", - RemoteSystemDefinedList = "remoteSystemDefinedList", - RemoteUserDefinedList = "remoteUserDefinedList", - RemoteOwner = "remoteOwner", - RemoteRepository = "remoteRepository", + VariantAnalysisSystemDefinedList = "variantAnalysisSystemDefinedList", + VariantAnalysisUserDefinedList = "variantAnalysisUserDefinedList", + VariantAnalysisOwner = "variantAnalysisOwner", + VariantAnalysisRepository = "variantAnalysisRepository", } export interface SelectedLocalUserDefinedList { @@ -40,22 +39,22 @@ export interface SelectedLocalDatabase { } export interface SelectedRemoteSystemDefinedList { - kind: SelectedDbItemKind.RemoteSystemDefinedList; + kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList; listName: string; } -export interface SelectedRemoteUserDefinedList { - kind: SelectedDbItemKind.RemoteUserDefinedList; +export interface SelectedVariantAnalysisUserDefinedList { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList; listName: string; } export interface SelectedRemoteOwner { - kind: SelectedDbItemKind.RemoteOwner; + kind: SelectedDbItemKind.VariantAnalysisOwner; ownerName: string; } export interface SelectedRemoteRepository { - kind: SelectedDbItemKind.RemoteRepository; + kind: SelectedDbItemKind.VariantAnalysisRepository; repositoryName: string; listName?: string; } @@ -88,49 +87,18 @@ export interface LocalDatabase { storagePath: string; } -export type ExpandedDbItem = - | RootLocalExpandedDbItem - | LocalUserDefinedListExpandedDbItem - | RootRemoteExpandedDbItem - | RemoteUserDefinedListExpandedDbItem; - -export enum ExpandedDbItemKind { - RootLocal = "rootLocal", - LocalUserDefinedList = "localUserDefinedList", - RootRemote = "rootRemote", - RemoteUserDefinedList = "remoteUserDefinedList", -} - -export interface RootLocalExpandedDbItem { - kind: ExpandedDbItemKind.RootLocal; -} - -export interface LocalUserDefinedListExpandedDbItem { - kind: ExpandedDbItemKind.LocalUserDefinedList; - listName: string; -} - -export interface RootRemoteExpandedDbItem { - kind: ExpandedDbItemKind.RootRemote; -} - -export interface RemoteUserDefinedListExpandedDbItem { - kind: ExpandedDbItemKind.RemoteUserDefinedList; - listName: string; -} - export function cloneDbConfig(config: DbConfig): DbConfig { return { databases: { - remote: { - repositoryLists: config.databases.remote.repositoryLists.map( + variantAnalysis: { + repositoryLists: config.databases.variantAnalysis.repositoryLists.map( (list) => ({ name: list.name, repositories: [...list.repositories], }), ), - owners: [...config.databases.remote.owners], - repositories: [...config.databases.remote.repositories], + owners: [...config.databases.variantAnalysis.owners], + repositories: [...config.databases.variantAnalysis.repositories], }, local: { lists: config.databases.local.lists.map((list) => ({ @@ -140,13 +108,223 @@ export function cloneDbConfig(config: DbConfig): DbConfig { databases: config.databases.local.databases.map((db) => ({ ...db })), }, }, - expanded: config.expanded.map(cloneDbConfigExpandedItem), selected: config.selected ? cloneDbConfigSelectedItem(config.selected) : undefined, }; } +export function renameLocalList( + originalConfig: DbConfig, + currentListName: string, + newListName: string, +): DbConfig { + const config = cloneDbConfig(originalConfig); + + const list = getLocalList(config, currentListName); + list.name = newListName; + + if ( + config.selected?.kind === SelectedDbItemKind.LocalUserDefinedList || + config.selected?.kind === SelectedDbItemKind.LocalDatabase + ) { + if (config.selected.listName === currentListName) { + config.selected.listName = newListName; + } + } + + return config; +} + +export function renameRemoteList( + originalConfig: DbConfig, + currentListName: string, + newListName: string, +): DbConfig { + const config = cloneDbConfig(originalConfig); + + const list = getRemoteList(config, currentListName); + list.name = newListName; + + if ( + config.selected?.kind === + SelectedDbItemKind.VariantAnalysisUserDefinedList || + config.selected?.kind === SelectedDbItemKind.VariantAnalysisRepository + ) { + if (config.selected.listName === currentListName) { + config.selected.listName = newListName; + } + } + + return config; +} + +export function renameLocalDb( + originalConfig: DbConfig, + currentDbName: string, + newDbName: string, + parentListName?: string, +): DbConfig { + const config = cloneDbConfig(originalConfig); + + if (parentListName) { + const list = getLocalList(config, parentListName); + const dbIndex = list.databases.findIndex((db) => db.name === currentDbName); + if (dbIndex === -1) { + throw Error( + `Cannot find database '${currentDbName}' in list '${parentListName}'`, + ); + } + list.databases[dbIndex].name = newDbName; + } else { + const dbIndex = config.databases.local.databases.findIndex( + (db) => db.name === currentDbName, + ); + if (dbIndex === -1) { + throw Error(`Cannot find database '${currentDbName}' in local databases`); + } + config.databases.local.databases[dbIndex].name = newDbName; + } + + if ( + config.selected?.kind === SelectedDbItemKind.LocalDatabase && + config.selected.databaseName === currentDbName + ) { + config.selected.databaseName = newDbName; + } + + return config; +} + +export function removeLocalList( + originalConfig: DbConfig, + listName: string, +): DbConfig { + const config = cloneDbConfig(originalConfig); + + config.databases.local.lists = config.databases.local.lists.filter( + (list) => list.name !== listName, + ); + + if (config.selected?.kind === SelectedDbItemKind.LocalUserDefinedList) { + config.selected = undefined; + } + + if ( + config.selected?.kind === SelectedDbItemKind.LocalDatabase && + config.selected?.listName === listName + ) { + config.selected = undefined; + } + + return config; +} + +export function removeRemoteList( + originalConfig: DbConfig, + listName: string, +): DbConfig { + const config = cloneDbConfig(originalConfig); + + config.databases.variantAnalysis.repositoryLists = + config.databases.variantAnalysis.repositoryLists.filter( + (list) => list.name !== listName, + ); + + if ( + config.selected?.kind === SelectedDbItemKind.VariantAnalysisUserDefinedList + ) { + config.selected = undefined; + } + + if ( + config.selected?.kind === SelectedDbItemKind.VariantAnalysisRepository && + config.selected?.listName === listName + ) { + config.selected = undefined; + } + + return config; +} + +export function removeLocalDb( + originalConfig: DbConfig, + databaseName: string, + parentListName?: string, +): DbConfig { + const config = cloneDbConfig(originalConfig); + + if (parentListName) { + const parentList = getLocalList(config, parentListName); + parentList.databases = parentList.databases.filter( + (db) => db.name !== databaseName, + ); + } else { + config.databases.local.databases = config.databases.local.databases.filter( + (db) => db.name !== databaseName, + ); + } + + if ( + config.selected?.kind === SelectedDbItemKind.LocalDatabase && + config.selected?.databaseName === databaseName && + config.selected?.listName === parentListName + ) { + config.selected = undefined; + } + + return config; +} + +export function removeRemoteRepo( + originalConfig: DbConfig, + repoFullName: string, + parentListName?: string, +): DbConfig { + const config = cloneDbConfig(originalConfig); + + if (parentListName) { + const parentList = getRemoteList(config, parentListName); + parentList.repositories = parentList.repositories.filter( + (r) => r !== repoFullName, + ); + } else { + config.databases.variantAnalysis.repositories = + config.databases.variantAnalysis.repositories.filter( + (r) => r !== repoFullName, + ); + } + + if ( + config.selected?.kind === SelectedDbItemKind.VariantAnalysisRepository && + config.selected?.repositoryName === repoFullName && + config.selected?.listName === parentListName + ) { + config.selected = undefined; + } + + return config; +} + +export function removeRemoteOwner( + originalConfig: DbConfig, + ownerName: string, +): DbConfig { + const config = cloneDbConfig(originalConfig); + + config.databases.variantAnalysis.owners = + config.databases.variantAnalysis.owners.filter((o) => o !== ownerName); + + if ( + config.selected?.kind === SelectedDbItemKind.VariantAnalysisOwner && + config.selected?.ownerName === ownerName + ) { + config.selected = undefined; + } + + return config; +} + function cloneDbConfigSelectedItem(selected: SelectedDbItem): SelectedDbItem { switch (selected.kind) { case SelectedDbItemKind.LocalUserDefinedList: @@ -160,40 +338,51 @@ function cloneDbConfigSelectedItem(selected: SelectedDbItem): SelectedDbItem { databaseName: selected.databaseName, listName: selected.listName, }; - case SelectedDbItemKind.RemoteSystemDefinedList: + case SelectedDbItemKind.VariantAnalysisSystemDefinedList: return { - kind: SelectedDbItemKind.RemoteSystemDefinedList, + kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList, listName: selected.listName, }; - case SelectedDbItemKind.RemoteUserDefinedList: + case SelectedDbItemKind.VariantAnalysisUserDefinedList: return { - kind: SelectedDbItemKind.RemoteUserDefinedList, + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, listName: selected.listName, }; - case SelectedDbItemKind.RemoteOwner: + case SelectedDbItemKind.VariantAnalysisOwner: return { - kind: SelectedDbItemKind.RemoteOwner, + kind: SelectedDbItemKind.VariantAnalysisOwner, ownerName: selected.ownerName, }; - case SelectedDbItemKind.RemoteRepository: + case SelectedDbItemKind.VariantAnalysisRepository: return { - kind: SelectedDbItemKind.RemoteRepository, + kind: SelectedDbItemKind.VariantAnalysisRepository, repositoryName: selected.repositoryName, listName: selected.listName, }; } } -function cloneDbConfigExpandedItem(item: ExpandedDbItem): ExpandedDbItem { - switch (item.kind) { - case ExpandedDbItemKind.RootLocal: - case ExpandedDbItemKind.RootRemote: - return { kind: item.kind }; - case ExpandedDbItemKind.LocalUserDefinedList: - case ExpandedDbItemKind.RemoteUserDefinedList: - return { - kind: item.kind, - listName: item.listName, - }; +function getLocalList(config: DbConfig, listName: string): LocalList { + const list = config.databases.local.lists.find((l) => l.name === listName); + + if (!list) { + throw Error(`Cannot find local list '${listName}'`); } + + return list; +} + +function getRemoteList( + config: DbConfig, + listName: string, +): RemoteRepositoryList { + const list = config.databases.variantAnalysis.repositoryLists.find( + (l) => l.name === listName, + ); + + if (!list) { + throw Error(`Cannot find remote list '${listName}'`); + } + + return list; } diff --git a/extensions/ql-vscode/src/databases/db-item-expansion.ts b/extensions/ql-vscode/src/databases/db-item-expansion.ts index 00decd853..42ce035f6 100644 --- a/extensions/ql-vscode/src/databases/db-item-expansion.ts +++ b/extensions/ql-vscode/src/databases/db-item-expansion.ts @@ -1,7 +1,37 @@ -import { ExpandedDbItem, ExpandedDbItemKind } from "./config/db-config"; -import { DbItem, DbItemKind } from "./db-item"; +import { DbItem, DbItemKind, flattenDbItems } from "./db-item"; -export function calculateNewExpandedState( +export type ExpandedDbItem = + | RootLocalExpandedDbItem + | LocalUserDefinedListExpandedDbItem + | RootRemoteExpandedDbItem + | VariantAnalysisUserDefinedListExpandedDbItem; + +export enum ExpandedDbItemKind { + RootLocal = "rootLocal", + LocalUserDefinedList = "localUserDefinedList", + RootRemote = "rootRemote", + RemoteUserDefinedList = "remoteUserDefinedList", +} + +export interface RootLocalExpandedDbItem { + kind: ExpandedDbItemKind.RootLocal; +} + +export interface LocalUserDefinedListExpandedDbItem { + kind: ExpandedDbItemKind.LocalUserDefinedList; + listName: string; +} + +export interface RootRemoteExpandedDbItem { + kind: ExpandedDbItemKind.RootRemote; +} + +export interface VariantAnalysisUserDefinedListExpandedDbItem { + kind: ExpandedDbItemKind.RemoteUserDefinedList; + listName: string; +} + +export function updateExpandedItem( currentExpandedItems: ExpandedDbItem[], dbItem: DbItem, itemExpanded: boolean, @@ -20,6 +50,34 @@ export function calculateNewExpandedState( } } +export function replaceExpandedItem( + currentExpandedItems: ExpandedDbItem[], + currentDbItem: DbItem, + newDbItem: DbItem, +): ExpandedDbItem[] { + const newExpandedItems: ExpandedDbItem[] = []; + + for (const item of currentExpandedItems) { + if (isDbItemEqualToExpandedDbItem(currentDbItem, item)) { + newExpandedItems.push(mapDbItemToExpandedDbItem(newDbItem)); + } else { + newExpandedItems.push(item); + } + } + + return newExpandedItems; +} + +export function cleanNonExistentExpandedItems( + currentExpandedItems: ExpandedDbItem[], + dbItems: DbItem[], +): ExpandedDbItem[] { + const flattenedDbItems = flattenDbItems(dbItems); + return currentExpandedItems.filter((i) => + flattenedDbItems.some((dbItem) => isDbItemEqualToExpandedDbItem(dbItem, i)), + ); +} + function mapDbItemToExpandedDbItem(dbItem: DbItem): ExpandedDbItem { switch (dbItem.kind) { case DbItemKind.RootLocal: @@ -31,7 +89,7 @@ function mapDbItemToExpandedDbItem(dbItem: DbItem): ExpandedDbItem { }; case DbItemKind.RootRemote: return { kind: ExpandedDbItemKind.RootRemote }; - case DbItemKind.RemoteUserDefinedList: + case DbItemKind.VariantAnalysisUserDefinedList: return { kind: ExpandedDbItemKind.RemoteUserDefinedList, listName: dbItem.listName, @@ -55,12 +113,15 @@ function isDbItemEqualToExpandedDbItem( ); case DbItemKind.RootRemote: return expandedDbItem.kind === ExpandedDbItemKind.RootRemote; - case DbItemKind.RemoteUserDefinedList: + case DbItemKind.VariantAnalysisUserDefinedList: return ( expandedDbItem.kind === ExpandedDbItemKind.RemoteUserDefinedList && expandedDbItem.listName === dbItem.listName ); - default: - throw Error(`Unknown db item kind ${dbItem.kind}`); + case DbItemKind.LocalDatabase: + case DbItemKind.RemoteSystemDefinedList: + case DbItemKind.RemoteOwner: + case DbItemKind.RemoteRepo: + return false; } } diff --git a/extensions/ql-vscode/src/databases/db-item-naming.ts b/extensions/ql-vscode/src/databases/db-item-naming.ts new file mode 100644 index 000000000..c6346fad8 --- /dev/null +++ b/extensions/ql-vscode/src/databases/db-item-naming.ts @@ -0,0 +1,19 @@ +import { DbItem, DbItemKind } from "./db-item"; + +export function getDbItemName(dbItem: DbItem): string | undefined { + switch (dbItem.kind) { + case DbItemKind.RootLocal: + case DbItemKind.RootRemote: + return undefined; + case DbItemKind.LocalList: + case DbItemKind.VariantAnalysisUserDefinedList: + case DbItemKind.RemoteSystemDefinedList: + return dbItem.listName; + case DbItemKind.RemoteOwner: + return dbItem.ownerName; + case DbItemKind.LocalDatabase: + return dbItem.databaseName; + case DbItemKind.RemoteRepo: + return dbItem.repoFullName; + } +} diff --git a/extensions/ql-vscode/src/databases/db-item-selection.ts b/extensions/ql-vscode/src/databases/db-item-selection.ts index d4960ad37..4d178662b 100644 --- a/extensions/ql-vscode/src/databases/db-item-selection.ts +++ b/extensions/ql-vscode/src/databases/db-item-selection.ts @@ -33,7 +33,7 @@ function extractSelected( } } break; - case DbItemKind.RemoteUserDefinedList: + case DbItemKind.VariantAnalysisUserDefinedList: for (const repo of dbItem.repos) { if (repo.selected) { return repo; @@ -59,36 +59,36 @@ export function mapDbItemToSelectedDbItem( listName: dbItem.listName, }; - case DbItemKind.RemoteUserDefinedList: + case DbItemKind.VariantAnalysisUserDefinedList: return { - kind: SelectedDbItemKind.RemoteUserDefinedList, + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, listName: dbItem.listName, }; case DbItemKind.RemoteSystemDefinedList: return { - kind: SelectedDbItemKind.RemoteSystemDefinedList, + kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList, listName: dbItem.listName, }; case DbItemKind.RemoteOwner: return { - kind: SelectedDbItemKind.RemoteOwner, + kind: SelectedDbItemKind.VariantAnalysisOwner, ownerName: dbItem.ownerName, }; case DbItemKind.LocalDatabase: return { kind: SelectedDbItemKind.LocalDatabase, - listName: dbItem?.parentListName, databaseName: dbItem.databaseName, + listName: dbItem?.parentListName, }; case DbItemKind.RemoteRepo: return { - kind: SelectedDbItemKind.RemoteRepository, - listName: dbItem?.parentListName, + kind: SelectedDbItemKind.VariantAnalysisRepository, repositoryName: dbItem.repoFullName, + listName: dbItem?.parentListName, }; } } diff --git a/extensions/ql-vscode/src/databases/db-item.ts b/extensions/ql-vscode/src/databases/db-item.ts index 031a008d5..981c88342 100644 --- a/extensions/ql-vscode/src/databases/db-item.ts +++ b/extensions/ql-vscode/src/databases/db-item.ts @@ -6,11 +6,30 @@ export enum DbItemKind { LocalDatabase = "LocalDatabase", RootRemote = "RootRemote", RemoteSystemDefinedList = "RemoteSystemDefinedList", - RemoteUserDefinedList = "RemoteUserDefinedList", + VariantAnalysisUserDefinedList = "VariantAnalysisUserDefinedList", RemoteOwner = "RemoteOwner", RemoteRepo = "RemoteRepo", } +export const remoteDbKinds = [ + DbItemKind.RootRemote, + DbItemKind.RemoteSystemDefinedList, + DbItemKind.VariantAnalysisUserDefinedList, + DbItemKind.RemoteOwner, + DbItemKind.RemoteRepo, +]; + +export const localDbKinds = [ + DbItemKind.RootLocal, + DbItemKind.LocalList, + DbItemKind.LocalDatabase, +]; + +export enum DbListKind { + Local = "Local", + Remote = "Remote", +} + export interface RootLocalDbItem { kind: DbItemKind.RootLocal; expanded: boolean; @@ -51,7 +70,7 @@ export type DbItem = export type RemoteDbItem = | RemoteSystemDefinedListDbItem - | RemoteUserDefinedListDbItem + | VariantAnalysisUserDefinedListDbItem | RemoteOwnerDbItem | RemoteRepoDbItem; @@ -63,8 +82,8 @@ export interface RemoteSystemDefinedListDbItem { listDescription: string; } -export interface RemoteUserDefinedListDbItem { - kind: DbItemKind.RemoteUserDefinedList; +export interface VariantAnalysisUserDefinedListDbItem { + kind: DbItemKind.VariantAnalysisUserDefinedList; expanded: boolean; selected: boolean; listName: string; @@ -90,10 +109,10 @@ export function isRemoteSystemDefinedListDbItem( return dbItem.kind === DbItemKind.RemoteSystemDefinedList; } -export function isRemoteUserDefinedListDbItem( +export function isVariantAnalysisUserDefinedListDbItem( dbItem: DbItem, -): dbItem is RemoteUserDefinedListDbItem { - return dbItem.kind === DbItemKind.RemoteUserDefinedList; +): dbItem is VariantAnalysisUserDefinedListDbItem { + return dbItem.kind === DbItemKind.VariantAnalysisUserDefinedList; } export function isRemoteOwnerDbItem( @@ -126,7 +145,36 @@ const SelectableDbItemKinds = [ DbItemKind.LocalList, DbItemKind.LocalDatabase, DbItemKind.RemoteSystemDefinedList, - DbItemKind.RemoteUserDefinedList, + DbItemKind.VariantAnalysisUserDefinedList, DbItemKind.RemoteOwner, DbItemKind.RemoteRepo, ]; + +export function flattenDbItems(dbItems: DbItem[]): DbItem[] { + const allItems: DbItem[] = []; + + for (const dbItem of dbItems) { + allItems.push(dbItem); + switch (dbItem.kind) { + case DbItemKind.RootLocal: + allItems.push(...flattenDbItems(dbItem.children)); + break; + case DbItemKind.LocalList: + allItems.push(...flattenDbItems(dbItem.databases)); + break; + case DbItemKind.RootRemote: + allItems.push(...flattenDbItems(dbItem.children)); + break; + case DbItemKind.VariantAnalysisUserDefinedList: + allItems.push(...dbItem.repos); + break; + case DbItemKind.LocalDatabase: + case DbItemKind.RemoteSystemDefinedList: + case DbItemKind.RemoteOwner: + case DbItemKind.RemoteRepo: + break; + } + } + + return allItems; +} diff --git a/extensions/ql-vscode/src/databases/db-manager.ts b/extensions/ql-vscode/src/databases/db-manager.ts index a07a63177..091bd4489 100644 --- a/extensions/ql-vscode/src/databases/db-manager.ts +++ b/extensions/ql-vscode/src/databases/db-manager.ts @@ -2,8 +2,20 @@ import { App } from "../common/app"; import { AppEvent, AppEventEmitter } from "../common/events"; import { ValueResult } from "../common/value-result"; import { DbConfigStore } from "./config/db-config-store"; -import { DbItem } from "./db-item"; -import { calculateNewExpandedState } from "./db-item-expansion"; +import { + DbItem, + DbItemKind, + DbListKind, + LocalDatabaseDbItem, + LocalListDbItem, + VariantAnalysisUserDefinedListDbItem, +} from "./db-item"; +import { + updateExpandedItem, + replaceExpandedItem, + ExpandedDbItem, + cleanNonExistentExpandedItems, +} from "./db-item-expansion"; import { getSelectedDbItem, mapDbItemToSelectedDbItem, @@ -14,8 +26,12 @@ import { DbConfigValidationError } from "./db-validation-errors"; export class DbManager { public readonly onDbItemsChanged: AppEvent; private readonly onDbItemsChangesEventEmitter: AppEventEmitter; + private static readonly DB_EXPANDED_STATE_KEY = "db_expanded"; - constructor(app: App, private readonly dbConfigStore: DbConfigStore) { + constructor( + private readonly app: App, + private readonly dbConfigStore: DbConfigStore, + ) { this.onDbItemsChangesEventEmitter = app.createEventEmitter(); this.onDbItemsChanged = this.onDbItemsChangesEventEmitter.event; @@ -40,9 +56,11 @@ export class DbManager { return ValueResult.fail(configResult.errors); } + const expandedItems = this.getExpandedItems(); + return ValueResult.ok([ - createRemoteTree(configResult.value), - createLocalTree(configResult.value), + createRemoteTree(configResult.value, expandedItems), + createLocalTree(configResult.value, expandedItems), ]); } @@ -57,33 +75,157 @@ export class DbManager { } } - public async updateDbItemExpandedState( + public async removeDbItem(dbItem: DbItem): Promise { + await this.dbConfigStore.removeDbItem(dbItem); + + await this.removeDbItemFromExpandedState(dbItem); + } + + public async removeDbItemFromExpandedState(dbItem: DbItem): Promise { + // When collapsing or expanding a list we clean up the expanded state and remove + // all items that don't exist anymore. + + await this.updateDbItemExpandedState(dbItem, false); + } + + public async addDbItemToExpandedState(dbItem: DbItem): Promise { + // When collapsing or expanding a list we clean up the expanded state and remove + // all items that don't exist anymore. + + await this.updateDbItemExpandedState(dbItem, true); + } + + public async addNewRemoteRepo( + nwo: string, + parentList?: string, + ): Promise { + await this.dbConfigStore.addRemoteRepo(nwo, parentList); + } + + public async addNewRemoteOwner(owner: string): Promise { + await this.dbConfigStore.addRemoteOwner(owner); + } + + public async addNewList( + listKind: DbListKind, + listName: string, + ): Promise { + switch (listKind) { + case DbListKind.Local: + await this.dbConfigStore.addLocalList(listName); + break; + case DbListKind.Remote: + await this.dbConfigStore.addRemoteList(listName); + break; + default: + throw Error(`Unknown list kind '${listKind}'`); + } + } + + public async renameList( + currentDbItem: LocalListDbItem | VariantAnalysisUserDefinedListDbItem, + newName: string, + ): Promise { + if (currentDbItem.kind === DbItemKind.LocalList) { + await this.dbConfigStore.renameLocalList(currentDbItem, newName); + } else if ( + currentDbItem.kind === DbItemKind.VariantAnalysisUserDefinedList + ) { + await this.dbConfigStore.renameRemoteList(currentDbItem, newName); + } + + const newDbItem = { ...currentDbItem, listName: newName }; + const newExpandedItems = replaceExpandedItem( + this.getExpandedItems(), + currentDbItem, + newDbItem, + ); + + await this.setExpandedItems(newExpandedItems); + } + + public async renameLocalDb( + currentDbItem: LocalDatabaseDbItem, + newName: string, + ): Promise { + await this.dbConfigStore.renameLocalDb( + currentDbItem, + newName, + currentDbItem.parentListName, + ); + } + + public doesListExist(listKind: DbListKind, listName: string): boolean { + switch (listKind) { + case DbListKind.Local: + return this.dbConfigStore.doesLocalListExist(listName); + case DbListKind.Remote: + return this.dbConfigStore.doesRemoteListExist(listName); + default: + throw Error(`Unknown list kind '${listKind}'`); + } + } + + public doesRemoteOwnerExist(owner: string): boolean { + return this.dbConfigStore.doesRemoteOwnerExist(owner); + } + + public doesRemoteRepoExist(nwo: string, listName?: string): boolean { + return this.dbConfigStore.doesRemoteDbExist(nwo, listName); + } + + public doesLocalDbExist(dbName: string, listName?: string): boolean { + return this.dbConfigStore.doesLocalDbExist(dbName, listName); + } + + private getExpandedItems(): ExpandedDbItem[] { + const items = this.app.workspaceState.get( + DbManager.DB_EXPANDED_STATE_KEY, + ); + + return items || []; + } + + private async setExpandedItems(items: ExpandedDbItem[]): Promise { + await this.app.workspaceState.update( + DbManager.DB_EXPANDED_STATE_KEY, + items, + ); + } + + private async updateExpandedItems(items: ExpandedDbItem[]): Promise { + let itemsToStore; + + const dbItemsResult = this.getDbItems(); + + if (dbItemsResult.isFailure) { + // Log an error but don't throw an exception since if the db items are failing + // to be read, then there is a bigger problem than the expanded state. + void this.app.logger.log( + `Could not read db items when calculating expanded state: ${JSON.stringify( + dbItemsResult.errors, + )}`, + ); + itemsToStore = items; + } else { + itemsToStore = cleanNonExistentExpandedItems(items, dbItemsResult.value); + } + + await this.setExpandedItems(itemsToStore); + } + + private async updateDbItemExpandedState( dbItem: DbItem, itemExpanded: boolean, ): Promise { - const configResult = this.dbConfigStore.getConfig(); - if (configResult.isFailure) { - throw Error("Cannot update expanded state if config is not loaded"); - } + const currentExpandedItems = this.getExpandedItems(); - const newExpandedItems = calculateNewExpandedState( - configResult.value.expanded, + const newExpandedItems = updateExpandedItem( + currentExpandedItems, dbItem, itemExpanded, ); - await this.dbConfigStore.updateExpandedState(newExpandedItems); - } - - public async addNewRemoteList(listName: string): Promise { - if (this.dbConfigStore.doesRemoteListExist(listName)) { - throw Error(`A list with the name '${listName}' already exists`); - } - - await this.dbConfigStore.addRemoteList(listName); - } - - public doesRemoteListExist(listName: string): boolean { - return this.dbConfigStore.doesRemoteListExist(listName); + await this.updateExpandedItems(newExpandedItems); } } diff --git a/extensions/ql-vscode/src/databases/db-module.ts b/extensions/ql-vscode/src/databases/db-module.ts index 588cbd7ff..db50b88be 100644 --- a/extensions/ql-vscode/src/databases/db-module.ts +++ b/extensions/ql-vscode/src/databases/db-module.ts @@ -6,7 +6,7 @@ import { DbConfigStore } from "./config/db-config-store"; import { DbManager } from "./db-manager"; import { DbPanel } from "./ui/db-panel"; import { DbSelectionDecorationProvider } from "./ui/db-selection-decoration-provider"; -import { isCanary, isNewQueryRunExperienceEnabled } from "../config"; +import { isCanary, isVariantAnalysisReposPanelEnabled } from "../config"; export class DbModule extends DisposableObject { public readonly dbManager: DbManager; @@ -36,7 +36,7 @@ export class DbModule extends DisposableObject { return true; } - return isCanary() && isNewQueryRunExperienceEnabled(); + return isCanary() && isVariantAnalysisReposPanelEnabled(); } private async initialize(): Promise { diff --git a/extensions/ql-vscode/src/databases/db-tree-creator.ts b/extensions/ql-vscode/src/databases/db-tree-creator.ts index df2a1241d..0fa06ece7 100644 --- a/extensions/ql-vscode/src/databases/db-tree-creator.ts +++ b/extensions/ql-vscode/src/databases/db-tree-creator.ts @@ -1,6 +1,5 @@ import { DbConfig, - ExpandedDbItemKind, LocalDatabase, LocalList, RemoteRepositoryList, @@ -13,31 +12,36 @@ import { RemoteOwnerDbItem, RemoteRepoDbItem, RemoteSystemDefinedListDbItem, - RemoteUserDefinedListDbItem, + VariantAnalysisUserDefinedListDbItem, RootLocalDbItem, RootRemoteDbItem, } from "./db-item"; +import { ExpandedDbItem, ExpandedDbItemKind } from "./db-item-expansion"; -export function createRemoteTree(dbConfig: DbConfig): RootRemoteDbItem { +export function createRemoteTree( + dbConfig: DbConfig, + expandedItems: ExpandedDbItem[], +): RootRemoteDbItem { const systemDefinedLists = [ createSystemDefinedList(10, dbConfig), createSystemDefinedList(100, dbConfig), createSystemDefinedList(1000, dbConfig), ]; - const userDefinedRepoLists = dbConfig.databases.remote.repositoryLists.map( - (r) => createRemoteUserDefinedList(r, dbConfig), - ); - const owners = dbConfig.databases.remote.owners.map((o) => + const userDefinedRepoLists = + dbConfig.databases.variantAnalysis.repositoryLists.map((r) => + createVariantAnalysisUserDefinedList(r, dbConfig, expandedItems), + ); + const owners = dbConfig.databases.variantAnalysis.owners.map((o) => createOwnerItem(o, dbConfig), ); - const repos = dbConfig.databases.remote.repositories.map((r) => + const repos = dbConfig.databases.variantAnalysis.repositories.map((r) => createRepoItem(r, dbConfig), ); - const expanded = - dbConfig.expanded && - dbConfig.expanded.some((e) => e.kind === ExpandedDbItemKind.RootRemote); + const expanded = expandedItems.some( + (e) => e.kind === ExpandedDbItemKind.RootRemote, + ); return { kind: DbItemKind.RootRemote, @@ -51,17 +55,20 @@ export function createRemoteTree(dbConfig: DbConfig): RootRemoteDbItem { }; } -export function createLocalTree(dbConfig: DbConfig): RootLocalDbItem { +export function createLocalTree( + dbConfig: DbConfig, + expandedItems: ExpandedDbItem[], +): RootLocalDbItem { const localLists = dbConfig.databases.local.lists.map((l) => - createLocalList(l, dbConfig), + createLocalList(l, dbConfig, expandedItems), ); const localDbs = dbConfig.databases.local.databases.map((l) => createLocalDb(l, dbConfig), ); - const expanded = - dbConfig.expanded && - dbConfig.expanded.some((e) => e.kind === ExpandedDbItemKind.RootLocal); + const expanded = expandedItems.some( + (e) => e.kind === ExpandedDbItemKind.RootLocal, + ); return { kind: DbItemKind.RootLocal, @@ -78,7 +85,8 @@ function createSystemDefinedList( const selected = dbConfig.selected && - dbConfig.selected.kind === SelectedDbItemKind.RemoteSystemDefinedList && + dbConfig.selected.kind === + SelectedDbItemKind.VariantAnalysisSystemDefinedList && dbConfig.selected.listName === listName; return { @@ -90,25 +98,25 @@ function createSystemDefinedList( }; } -function createRemoteUserDefinedList( +function createVariantAnalysisUserDefinedList( list: RemoteRepositoryList, dbConfig: DbConfig, -): RemoteUserDefinedListDbItem { + expandedItems: ExpandedDbItem[], +): VariantAnalysisUserDefinedListDbItem { const selected = dbConfig.selected && - dbConfig.selected.kind === SelectedDbItemKind.RemoteUserDefinedList && + dbConfig.selected.kind === + SelectedDbItemKind.VariantAnalysisUserDefinedList && dbConfig.selected.listName === list.name; - const expanded = - dbConfig.expanded && - dbConfig.expanded.some( - (e) => - e.kind === ExpandedDbItemKind.RemoteUserDefinedList && - e.listName === list.name, - ); + const expanded = expandedItems.some( + (e) => + e.kind === ExpandedDbItemKind.RemoteUserDefinedList && + e.listName === list.name, + ); return { - kind: DbItemKind.RemoteUserDefinedList, + kind: DbItemKind.VariantAnalysisUserDefinedList, listName: list.name, repos: list.repositories.map((r) => createRepoItem(r, dbConfig, list.name)), selected: !!selected, @@ -119,7 +127,7 @@ function createRemoteUserDefinedList( function createOwnerItem(owner: string, dbConfig: DbConfig): RemoteOwnerDbItem { const selected = dbConfig.selected && - dbConfig.selected.kind === SelectedDbItemKind.RemoteOwner && + dbConfig.selected.kind === SelectedDbItemKind.VariantAnalysisOwner && dbConfig.selected.ownerName === owner; return { @@ -136,7 +144,7 @@ function createRepoItem( ): RemoteRepoDbItem { const selected = dbConfig.selected && - dbConfig.selected.kind === SelectedDbItemKind.RemoteRepository && + dbConfig.selected.kind === SelectedDbItemKind.VariantAnalysisRepository && dbConfig.selected.repositoryName === repo && dbConfig.selected.listName === listName; @@ -148,19 +156,21 @@ function createRepoItem( }; } -function createLocalList(list: LocalList, dbConfig: DbConfig): LocalListDbItem { +function createLocalList( + list: LocalList, + dbConfig: DbConfig, + expandedItems: ExpandedDbItem[], +): LocalListDbItem { const selected = dbConfig.selected && dbConfig.selected.kind === SelectedDbItemKind.LocalUserDefinedList && dbConfig.selected.listName === list.name; - const expanded = - dbConfig.expanded && - dbConfig.expanded.some( - (e) => - e.kind === ExpandedDbItemKind.LocalUserDefinedList && - e.listName === list.name, - ); + const expanded = expandedItems.some( + (e) => + e.kind === ExpandedDbItemKind.LocalUserDefinedList && + e.listName === list.name, + ); return { kind: DbItemKind.LocalList, diff --git a/extensions/ql-vscode/src/databases/github-nwo.ts b/extensions/ql-vscode/src/databases/github-nwo.ts deleted file mode 100644 index 5943e6db0..000000000 --- a/extensions/ql-vscode/src/databases/github-nwo.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Uri } from "vscode"; -import { REPO_REGEX } from "../pure/helpers-pure"; - -/** - * The URL pattern is https://github.com/{owner}/{name}/{subpages}. - * - * This function accepts any URL that matches the pattern above. It also accepts just the - * name with owner (NWO): `/`. - * - * @param githubRepo The GitHub repository URL or NWO - * - * @return true if this looks like a valid GitHub repository URL or NWO - */ -export function looksLikeGithubRepo( - githubRepo: string | undefined, -): githubRepo is string { - if (!githubRepo) { - return false; - } - if (REPO_REGEX.test(githubRepo) || convertGitHubUrlToNwo(githubRepo)) { - return true; - } - return false; -} - -/** - * Converts a GitHub repository URL to the corresponding NWO. - * @param githubUrl The GitHub repository URL - * @return The corresponding NWO, or undefined if the URL is not valid - */ -export function convertGitHubUrlToNwo(githubUrl: string): string | undefined { - try { - const uri = Uri.parse(githubUrl, true); - if (uri.scheme !== "https") { - return; - } - if (uri.authority !== "github.com" && uri.authority !== "www.github.com") { - return; - } - const paths = uri.path.split("/").filter((segment: string) => segment); - const nwo = `${paths[0]}/${paths[1]}`; - if (REPO_REGEX.test(nwo)) { - return nwo; - } - return; - } catch (e) { - // Ignore the error here, since we catch failures at a higher level. - // In particular: returning undefined leads to an error in 'promptImportGithubDatabase'. - return; - } -} diff --git a/extensions/ql-vscode/src/databases/ui/db-item-mapper.ts b/extensions/ql-vscode/src/databases/ui/db-item-mapper.ts index 5670e16dd..d092d6a31 100644 --- a/extensions/ql-vscode/src/databases/ui/db-item-mapper.ts +++ b/extensions/ql-vscode/src/databases/ui/db-item-mapper.ts @@ -34,7 +34,7 @@ export function mapDbItemToTreeViewItem(dbItem: DbItem): DbTreeViewItem { dbItem.listDescription, ); - case DbItemKind.RemoteUserDefinedList: + case DbItemKind.VariantAnalysisUserDefinedList: return createDbTreeViewItemUserDefinedList( dbItem, dbItem.listName, diff --git a/extensions/ql-vscode/src/databases/ui/db-panel.ts b/extensions/ql-vscode/src/databases/ui/db-panel.ts index 6b4f89050..ae6567d98 100644 --- a/extensions/ql-vscode/src/databases/ui/db-panel.ts +++ b/extensions/ql-vscode/src/databases/ui/db-panel.ts @@ -1,55 +1,118 @@ -import { TreeViewExpansionEvent, window, workspace } from "vscode"; -import { commandRunner } from "../../commandRunner"; +import { + commands, + QuickPickItem, + TreeView, + TreeViewExpansionEvent, + Uri, + window, + workspace, +} from "vscode"; +import { commandRunner, UserCancellationException } from "../../commandRunner"; +import { + getNwoFromGitHubUrl, + isValidGitHubNwo, + getOwnerFromGitHubUrl, + isValidGitHubOwner, +} from "../../common/github-url-identifier-helper"; import { showAndLogErrorMessage } from "../../helpers"; import { DisposableObject } from "../../pure/disposable-object"; +import { + DbItem, + DbItemKind, + DbListKind, + LocalDatabaseDbItem, + LocalListDbItem, + remoteDbKinds, + VariantAnalysisUserDefinedListDbItem, +} from "../db-item"; +import { getDbItemName } from "../db-item-naming"; import { DbManager } from "../db-manager"; import { DbTreeDataProvider } from "./db-tree-data-provider"; import { DbTreeViewItem } from "./db-tree-view-item"; +import { getGitHubUrl } from "./db-tree-view-item-action"; + +export interface RemoteDatabaseQuickPickItem extends QuickPickItem { + kind: string; +} + +export interface AddListQuickPickItem extends QuickPickItem { + kind: DbListKind; +} export class DbPanel extends DisposableObject { private readonly dataProvider: DbTreeDataProvider; + private readonly treeView: TreeView; public constructor(private readonly dbManager: DbManager) { super(); this.dataProvider = new DbTreeDataProvider(dbManager); - const treeView = window.createTreeView("codeQLDatabasesExperimental", { + this.treeView = window.createTreeView("codeQLVariantAnalysisRepositories", { treeDataProvider: this.dataProvider, canSelectMany: false, }); this.push( - treeView.onDidCollapseElement(async (e) => { + this.treeView.onDidCollapseElement(async (e) => { await this.onDidCollapseElement(e); }), ); this.push( - treeView.onDidExpandElement(async (e) => { + this.treeView.onDidExpandElement(async (e) => { await this.onDidExpandElement(e); }), ); - this.push(treeView); + this.push(this.treeView); } public async initialize(): Promise { this.push( - commandRunner("codeQLDatabasesExperimental.openConfigFile", () => + commandRunner("codeQLVariantAnalysisRepositories.openConfigFile", () => this.openConfigFile(), ), ); this.push( - commandRunner("codeQLDatabasesExperimental.addNewList", () => - this.addNewRemoteList(), + commandRunner("codeQLVariantAnalysisRepositories.addNewDatabase", () => + this.addNewRemoteDatabase(), + ), + ); + this.push( + commandRunner("codeQLVariantAnalysisRepositories.addNewList", () => + this.addNewList(), ), ); this.push( commandRunner( - "codeQLDatabasesExperimental.setSelectedItem", + "codeQLVariantAnalysisRepositories.setSelectedItem", (treeViewItem: DbTreeViewItem) => this.setSelectedItem(treeViewItem), ), ); + this.push( + commandRunner( + "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu", + (treeViewItem: DbTreeViewItem) => this.setSelectedItem(treeViewItem), + ), + ); + this.push( + commandRunner( + "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu", + (treeViewItem: DbTreeViewItem) => this.openOnGitHub(treeViewItem), + ), + ); + this.push( + commandRunner( + "codeQLVariantAnalysisRepositories.renameItemContextMenu", + (treeViewItem: DbTreeViewItem) => this.renameItem(treeViewItem), + ), + ); + this.push( + commandRunner( + "codeQLVariantAnalysisRepositories.removeItemContextMenu", + (treeViewItem: DbTreeViewItem) => this.removeItem(treeViewItem), + ), + ); } private async openConfigFile(): Promise { @@ -58,21 +121,160 @@ export class DbPanel extends DisposableObject { await window.showTextDocument(document); } - private async addNewRemoteList(): Promise { + private async addNewRemoteDatabase(): Promise { + const highlightedItem = await this.getHighlightedDbItem(); + + if (highlightedItem?.kind === DbItemKind.VariantAnalysisUserDefinedList) { + await this.addNewRemoteRepo(highlightedItem.listName); + } else if ( + highlightedItem?.kind === DbItemKind.RemoteRepo && + highlightedItem.parentListName + ) { + await this.addNewRemoteRepo(highlightedItem.parentListName); + } else { + const quickPickItems = [ + { + label: "$(repo) From a GitHub repository", + detail: "Add a remote repository from GitHub", + alwaysShow: true, + kind: "repo", + }, + { + label: "$(organization) All repositories of a GitHub org or owner", + detail: + "Add a remote list of repositories from a GitHub organization/owner", + alwaysShow: true, + kind: "owner", + }, + ]; + const databaseKind = + await window.showQuickPick( + quickPickItems, + { + title: "Add a remote repository", + placeHolder: "Select an option", + ignoreFocusOut: true, + }, + ); + if (!databaseKind) { + // We don't need to display a warning pop-up in this case, since the user just escaped out of the operation. + // We set 'true' to make this a silent exception. + throw new UserCancellationException("No repository selected", true); + } + if (databaseKind.kind === "repo") { + await this.addNewRemoteRepo(); + } else if (databaseKind.kind === "owner") { + await this.addNewRemoteOwner(); + } + } + } + + private async addNewRemoteRepo(parentList?: string): Promise { + const repoName = await window.showInputBox({ + title: "Add a remote repository", + prompt: "Insert a GitHub repository URL or name with owner", + placeHolder: "github.com// or /", + }); + if (!repoName) { + return; + } + + const nwo = getNwoFromGitHubUrl(repoName) || repoName; + if (!isValidGitHubNwo(nwo)) { + void showAndLogErrorMessage(`Invalid GitHub repository: ${repoName}`); + return; + } + + if (this.dbManager.doesRemoteRepoExist(nwo, parentList)) { + void showAndLogErrorMessage(`The repository '${nwo}' already exists`); + return; + } + + await this.dbManager.addNewRemoteRepo(nwo, parentList); + } + + private async addNewRemoteOwner(): Promise { + const ownerName = await window.showInputBox({ + title: "Add all repositories of a GitHub org or owner", + prompt: "Insert a GitHub organization or owner name", + placeHolder: "github.com/ or ", + }); + + if (!ownerName) { + return; + } + + const owner = getOwnerFromGitHubUrl(ownerName) || ownerName; + if (!isValidGitHubOwner(owner)) { + void showAndLogErrorMessage(`Invalid user or organization: ${owner}`); + return; + } + + if (this.dbManager.doesRemoteOwnerExist(owner)) { + void showAndLogErrorMessage(`The owner '${owner}' already exists`); + return; + } + + await this.dbManager.addNewRemoteOwner(owner); + } + + private async addNewList(): Promise { + const listKind = await this.getAddNewListKind(); + const listName = await window.showInputBox({ prompt: "Enter a name for the new list", placeHolder: "example-list", }); - if (listName === undefined) { + if (listName === undefined || listName === "") { return; } - if (this.dbManager.doesRemoteListExist(listName)) { - void showAndLogErrorMessage( - `A list with the name '${listName}' already exists`, - ); + if (this.dbManager.doesListExist(listKind, listName)) { + void showAndLogErrorMessage(`The list '${listName}' already exists`); + return; + } + + await this.dbManager.addNewList(listKind, listName); + } + + private async getAddNewListKind(): Promise { + const highlightedItem = await this.getHighlightedDbItem(); + if (highlightedItem) { + return remoteDbKinds.includes(highlightedItem.kind) + ? DbListKind.Remote + : DbListKind.Local; } else { - await this.dbManager.addNewRemoteList(listName); + const quickPickItems = [ + { + label: "$(cloud) Remote", + detail: "Add a remote database from GitHub", + alwaysShow: true, + kind: DbListKind.Remote, + }, + { + label: "$(database) Local", + detail: "Import a database from the cloud or a local file", + alwaysShow: true, + kind: DbListKind.Local, + }, + ]; + const selectedOption = await window.showQuickPick( + quickPickItems, + { + title: "Add a new database", + ignoreFocusOut: true, + }, + ); + if (!selectedOption) { + // We don't need to display a warning pop-up in this case, since the user just escaped out of the operation. + // We set 'true' to make this a silent exception. + throw new UserCancellationException( + "No database list kind selected", + true, + ); + } + + return selectedOption.kind; } } @@ -85,6 +287,97 @@ export class DbPanel extends DisposableObject { await this.dbManager.setSelectedDbItem(treeViewItem.dbItem); } + private async renameItem(treeViewItem: DbTreeViewItem): Promise { + const dbItem = treeViewItem.dbItem; + if (dbItem === undefined) { + throw new Error( + "Not a database item that can be renamed. Please select a valid item.", + ); + } + + const oldName = getDbItemName(dbItem); + + const newName = await window.showInputBox({ + prompt: "Enter the new name", + value: oldName, + }); + + if (newName === undefined || newName === "") { + return; + } + + switch (dbItem.kind) { + case DbItemKind.LocalList: + await this.renameLocalListItem(dbItem, newName); + break; + case DbItemKind.LocalDatabase: + await this.renameLocalDatabaseItem(dbItem, newName); + break; + case DbItemKind.VariantAnalysisUserDefinedList: + await this.renameVariantAnalysisUserDefinedListItem(dbItem, newName); + break; + default: + throw Error(`Action not allowed for the '${dbItem.kind}' db item kind`); + } + } + + private async renameLocalListItem( + dbItem: LocalListDbItem, + newName: string, + ): Promise { + if (dbItem.listName === newName) { + return; + } + + if (this.dbManager.doesListExist(DbListKind.Local, newName)) { + void showAndLogErrorMessage(`The list '${newName}' already exists`); + return; + } + + await this.dbManager.renameList(dbItem, newName); + } + + private async renameLocalDatabaseItem( + dbItem: LocalDatabaseDbItem, + newName: string, + ): Promise { + if (dbItem.databaseName === newName) { + return; + } + + if (this.dbManager.doesLocalDbExist(newName, dbItem.parentListName)) { + void showAndLogErrorMessage(`The database '${newName}' already exists`); + return; + } + + await this.dbManager.renameLocalDb(dbItem, newName); + } + + private async renameVariantAnalysisUserDefinedListItem( + dbItem: VariantAnalysisUserDefinedListDbItem, + newName: string, + ): Promise { + if (dbItem.listName === newName) { + return; + } + + if (this.dbManager.doesListExist(DbListKind.Remote, newName)) { + void showAndLogErrorMessage(`The list '${newName}' already exists`); + return; + } + + await this.dbManager.renameList(dbItem, newName); + } + + private async removeItem(treeViewItem: DbTreeViewItem): Promise { + if (treeViewItem.dbItem === undefined) { + throw new Error( + "Not a removable database item. Please select a valid item.", + ); + } + await this.dbManager.removeDbItem(treeViewItem.dbItem); + } + private async onDidCollapseElement( event: TreeViewExpansionEvent, ): Promise { @@ -93,7 +386,7 @@ export class DbPanel extends DisposableObject { throw Error("Expected a database item."); } - await this.dbManager.updateDbItemExpandedState(event.element.dbItem, false); + await this.dbManager.removeDbItemFromExpandedState(event.element.dbItem); } private async onDidExpandElement( @@ -104,6 +397,32 @@ export class DbPanel extends DisposableObject { throw Error("Expected a database item."); } - await this.dbManager.updateDbItemExpandedState(event.element.dbItem, true); + await this.dbManager.addDbItemToExpandedState(event.element.dbItem); + } + + /** + * Gets the currently highlighted database item in the tree view. + * The VS Code API calls this the "selection", but we already have a notion of selection + * (i.e. which item has a check mark next to it), so we call this "highlighted". + * + * @returns The highlighted database item, or `undefined` if no item is highlighted. + */ + private async getHighlightedDbItem(): Promise { + // You can only select one item at a time, so selection[0] gives the selection + return this.treeView.selection[0]?.dbItem; + } + + private async openOnGitHub(treeViewItem: DbTreeViewItem): Promise { + if (treeViewItem.dbItem === undefined) { + throw new Error("Unable to open on GitHub. Please select a valid item."); + } + const githubUrl = getGitHubUrl(treeViewItem.dbItem); + if (!githubUrl) { + throw new Error( + "Unable to open on GitHub. Please select a remote repository or owner.", + ); + } + + await commands.executeCommand("vscode.open", Uri.parse(githubUrl)); } } diff --git a/extensions/ql-vscode/src/databases/ui/db-selection-decoration-provider.ts b/extensions/ql-vscode/src/databases/ui/db-selection-decoration-provider.ts index 1f04a09a8..67aa6da01 100644 --- a/extensions/ql-vscode/src/databases/ui/db-selection-decoration-provider.ts +++ b/extensions/ql-vscode/src/databases/ui/db-selection-decoration-provider.ts @@ -5,15 +5,16 @@ import { ProviderResult, Uri, } from "vscode"; +import { SELECTED_DB_ITEM_RESOURCE_URI } from "./db-tree-view-item"; export class DbSelectionDecorationProvider implements FileDecorationProvider { provideFileDecoration( uri: Uri, _token: CancellationToken, ): ProviderResult { - if (uri?.query === "selected=true") { + if (uri.toString(true) === SELECTED_DB_ITEM_RESOURCE_URI) { return { - badge: "●", + badge: "✓", tooltip: "Currently selected", }; } diff --git a/extensions/ql-vscode/src/databases/ui/db-tree-view-item-action.ts b/extensions/ql-vscode/src/databases/ui/db-tree-view-item-action.ts new file mode 100644 index 000000000..c60aa9466 --- /dev/null +++ b/extensions/ql-vscode/src/databases/ui/db-tree-view-item-action.ts @@ -0,0 +1,72 @@ +import { DbItem, DbItemKind, isSelectableDbItem } from "../db-item"; + +export type DbTreeViewItemAction = + | "canBeSelected" + | "canBeRemoved" + | "canBeRenamed" + | "canBeOpenedOnGitHub"; + +export function getDbItemActions(dbItem: DbItem): DbTreeViewItemAction[] { + const actions: DbTreeViewItemAction[] = []; + + if (canBeSelected(dbItem)) { + actions.push("canBeSelected"); + } + if (canBeRemoved(dbItem)) { + actions.push("canBeRemoved"); + } + if (canBeRenamed(dbItem)) { + actions.push("canBeRenamed"); + } + if (canBeOpenedOnGitHub(dbItem)) { + actions.push("canBeOpenedOnGitHub"); + } + + return actions; +} + +const dbItemKindsThatCanBeRemoved = [ + DbItemKind.LocalList, + DbItemKind.VariantAnalysisUserDefinedList, + DbItemKind.LocalDatabase, + DbItemKind.RemoteRepo, + DbItemKind.RemoteOwner, +]; + +const dbItemKindsThatCanBeRenamed = [ + DbItemKind.LocalList, + DbItemKind.VariantAnalysisUserDefinedList, + DbItemKind.LocalDatabase, +]; + +const dbItemKindsThatCanBeOpenedOnGitHub = [ + DbItemKind.RemoteOwner, + DbItemKind.RemoteRepo, +]; + +function canBeSelected(dbItem: DbItem): boolean { + return isSelectableDbItem(dbItem) && !dbItem.selected; +} + +function canBeRemoved(dbItem: DbItem): boolean { + return dbItemKindsThatCanBeRemoved.includes(dbItem.kind); +} + +function canBeRenamed(dbItem: DbItem): boolean { + return dbItemKindsThatCanBeRenamed.includes(dbItem.kind); +} + +function canBeOpenedOnGitHub(dbItem: DbItem): boolean { + return dbItemKindsThatCanBeOpenedOnGitHub.includes(dbItem.kind); +} + +export function getGitHubUrl(dbItem: DbItem): string | undefined { + switch (dbItem.kind) { + case DbItemKind.RemoteOwner: + return `https://github.com/${dbItem.ownerName}`; + case DbItemKind.RemoteRepo: + return `https://github.com/${dbItem.repoFullName}`; + default: + return undefined; + } +} diff --git a/extensions/ql-vscode/src/databases/ui/db-tree-view-item.ts b/extensions/ql-vscode/src/databases/ui/db-tree-view-item.ts index 4606e7165..fc4c94ccf 100644 --- a/extensions/ql-vscode/src/databases/ui/db-tree-view-item.ts +++ b/extensions/ql-vscode/src/databases/ui/db-tree-view-item.ts @@ -7,10 +7,13 @@ import { RemoteOwnerDbItem, RemoteRepoDbItem, RemoteSystemDefinedListDbItem, - RemoteUserDefinedListDbItem, + VariantAnalysisUserDefinedListDbItem, RootLocalDbItem, RootRemoteDbItem, } from "../db-item"; +import { getDbItemActions } from "./db-tree-view-item-action"; + +export const SELECTED_DB_ITEM_RESOURCE_URI = "codeql://databases?selected=true"; /** * Represents an item in the database tree view. This item could be @@ -30,18 +33,21 @@ export class DbTreeViewItem extends vscode.TreeItem { ) { super(label, collapsibleState); - if (dbItem && isSelectableDbItem(dbItem)) { - if (dbItem.selected) { + if (dbItem) { + this.contextValue = getContextValue(dbItem); + if (isSelectableDbItem(dbItem) && dbItem.selected) { // Define the resource id to drive the UI to render this item as selected. - this.resourceUri = vscode.Uri.parse("codeql://databases?selected=true"); - } else { - // Define a context value to drive the UI to show an action to select the item. - this.contextValue = "selectableDbItem"; + this.resourceUri = vscode.Uri.parse(SELECTED_DB_ITEM_RESOURCE_URI); } } } } +function getContextValue(dbItem: DbItem): string | undefined { + const actions = getDbItemActions(dbItem); + return actions.length > 0 ? actions.join(",") : undefined; +} + export function createDbTreeViewItemError( label: string, tooltip: string, @@ -91,7 +97,7 @@ export function createDbTreeViewItemSystemDefinedList( } export function createDbTreeViewItemUserDefinedList( - dbItem: LocalListDbItem | RemoteUserDefinedListDbItem, + dbItem: LocalListDbItem | VariantAnalysisUserDefinedListDbItem, listName: string, children: DbTreeViewItem[], ): DbTreeViewItem { diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index abf49939b..a26ddc209 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -591,7 +591,7 @@ async function activateWithInstalledDistribution( qs, getContextStoragePath(ctx), ctx.extensionPath, - () => Credentials.initialize(ctx), + () => Credentials.initialize(), ); databaseUI.init(); ctx.subscriptions.push(databaseUI); @@ -642,7 +642,7 @@ async function activateWithInstalledDistribution( cliServer, variantAnalysisStorageDir, variantAnalysisResultsManager, - dbModule?.dbManager, // the dbModule is only needed when the newQueryRunExperience is enabled + dbModule?.dbManager, // the dbModule is only needed when variantAnalysisReposPanel is enabled ); ctx.subscriptions.push(variantAnalysisManager); ctx.subscriptions.push(variantAnalysisResultsManager); @@ -1164,6 +1164,15 @@ async function activateWithInstalledDistribution( }), ); + ctx.subscriptions.push( + commandRunner( + "codeQL.openVariantAnalysisLogs", + async (variantAnalysisId: number) => { + await variantAnalysisManager.openVariantAnalysisLogs(variantAnalysisId); + }, + ), + ); + ctx.subscriptions.push( commandRunner( "codeQL.copyVariantAnalysisRepoList", @@ -1236,7 +1245,7 @@ async function activateWithInstalledDistribution( commandRunner( "codeQL.exportRemoteQueryResults", async (queryId: string) => { - await exportRemoteQueryResults(qhm, rqm, ctx, queryId); + await exportRemoteQueryResults(qhm, rqm, queryId); }, ), ); @@ -1251,7 +1260,6 @@ async function activateWithInstalledDistribution( filterSort?: RepositoriesFilterSortStateWithIds, ) => { await exportVariantAnalysisResults( - ctx, variantAnalysisManager, variantAnalysisId, filterSort, @@ -1356,7 +1364,7 @@ async function activateWithInstalledDistribution( "codeQL.chooseDatabaseGithub", async (progress: ProgressCallback, token: CancellationToken) => { const credentials = isCanary() - ? await Credentials.initialize(ctx) + ? await Credentials.initialize() : undefined; await databaseUI.handleChooseDatabaseGithub( credentials, @@ -1411,7 +1419,7 @@ async function activateWithInstalledDistribution( * Credentials for authenticating to GitHub. * These are used when making API calls. */ - const credentials = await Credentials.initialize(ctx); + const credentials = await Credentials.initialize(); const octokit = await credentials.getOctokit(); const userInfo = await octokit.users.getAuthenticated(); void showAndLogInformationMessage( diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index 658e297d0..1bdb0772e 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 * as glob from "glob-promise"; +import { promise as glob } from "glob-promise"; import { load } from "js-yaml"; import { join, basename } from "path"; import { dirSync } from "tmp-promise"; 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 69420961d..c7968ba97 100644 --- a/extensions/ql-vscode/src/log-insights/summary-language-support.ts +++ b/extensions/ql-vscode/src/log-insights/summary-language-support.ts @@ -59,18 +59,22 @@ export class SummaryLanguageSupport extends DisposableObject { super(); this.push( - window.onDidChangeActiveTextEditor(this.handleDidChangeActiveTextEditor), - ); - this.push( - window.onDidChangeTextEditorSelection( - this.handleDidChangeTextEditorSelection, + window.onDidChangeActiveTextEditor( + this.handleDidChangeActiveTextEditor.bind(this), ), ); this.push( - workspace.onDidCloseTextDocument(this.handleDidCloseTextDocument), + window.onDidChangeTextEditorSelection( + this.handleDidChangeTextEditorSelection.bind(this), + ), + ); + this.push( + workspace.onDidCloseTextDocument( + this.handleDidCloseTextDocument.bind(this), + ), ); - this.push(commandRunner("codeQL.gotoQL", this.handleGotoQL)); + this.push(commandRunner("codeQL.gotoQL", this.handleGotoQL.bind(this))); } /** diff --git a/extensions/ql-vscode/src/pure/files.ts b/extensions/ql-vscode/src/pure/files.ts index 2859ff846..c07e0f26d 100644 --- a/extensions/ql-vscode/src/pure/files.ts +++ b/extensions/ql-vscode/src/pure/files.ts @@ -1,5 +1,5 @@ import { pathExists, stat, readdir } from "fs-extra"; -import { join } from "path"; +import { join, resolve } from "path"; /** * Recursively finds all .ql files in this set of Uris. @@ -50,3 +50,20 @@ export async function getDirectoryNamesInsidePath( return dirNames; } + +export function pathsEqual( + path1: string, + path2: string, + platform: NodeJS.Platform, +): boolean { + // On Windows, "C:/", "C:\", and "c:/" are all equivalent. We need + // to normalize the paths to ensure they all get resolved to the + // same format. On Windows, we also need to do the comparison + // case-insensitively. + path1 = resolve(path1); + path2 = resolve(path2); + if (platform === "win32") { + return path1.toLowerCase() === path2.toLowerCase(); + } + return path1 === path2; +} diff --git a/extensions/ql-vscode/src/query-history.ts b/extensions/ql-vscode/src/query-history.ts index 0052b860d..4e1731ad3 100644 --- a/extensions/ql-vscode/src/query-history.ts +++ b/extensions/ql-vscode/src/query-history.ts @@ -397,7 +397,7 @@ export class QueryHistoryManager extends DisposableObject { private readonly variantAnalysisManager: VariantAnalysisManager, private readonly evalLogViewer: EvalLogViewer, private readonly queryStorageDir: string, - private readonly ctx: ExtensionContext, + ctx: ExtensionContext, private readonly queryHistoryConfigListener: QueryHistoryConfig, private readonly labelProvider: HistoryItemLabelProvider, private readonly doCompareCallback: ( @@ -633,7 +633,7 @@ export class QueryHistoryManager extends DisposableObject { } private getCredentials() { - return Credentials.initialize(this.ctx); + return Credentials.initialize(); } /** diff --git a/extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts b/extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts index b8974007f..04f94d68a 100644 --- a/extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts @@ -1,7 +1,7 @@ import { pathExists } from "fs-extra"; import { EOL } from "os"; import { extname } from "path"; -import { CancellationToken, ExtensionContext } from "vscode"; +import { CancellationToken } from "vscode"; import { Credentials } from "../authentication"; import { Logger } from "../common"; @@ -26,7 +26,6 @@ export class AnalysesResultsManager { private readonly analysesResults: Map; constructor( - private readonly ctx: ExtensionContext, private readonly cliServer: CodeQLCliServer, readonly storagePath: string, private readonly logger: Logger, @@ -43,7 +42,7 @@ export class AnalysesResultsManager { return; } - const credentials = await Credentials.initialize(this.ctx); + const credentials = await Credentials.initialize(); void this.logger.log( `Downloading and processing results for ${analysisSummary.nwo}`, @@ -77,7 +76,7 @@ export class AnalysesResultsManager { (x) => !this.isAnalysisInMemory(x), ); - const credentials = await Credentials.initialize(this.ctx); + const credentials = await Credentials.initialize(); void this.logger.log("Downloading and processing analyses results"); diff --git a/extensions/ql-vscode/src/remote-queries/export-results.ts b/extensions/ql-vscode/src/remote-queries/export-results.ts index 1641269d4..e829889b5 100644 --- a/extensions/ql-vscode/src/remote-queries/export-results.ts +++ b/extensions/ql-vscode/src/remote-queries/export-results.ts @@ -4,7 +4,6 @@ import { ensureDir, writeFile } from "fs-extra"; import { commands, CancellationToken, - ExtensionContext, Uri, ViewColumn, window, @@ -74,7 +73,6 @@ export async function exportSelectedRemoteQueryResults( export async function exportRemoteQueryResults( queryHistoryManager: QueryHistoryManager, remoteQueriesManager: RemoteQueriesManager, - ctx: ExtensionContext, queryId: string, ): Promise { const queryHistoryItem = queryHistoryManager.getRemoteQueryById(queryId); @@ -107,7 +105,6 @@ export async function exportRemoteQueryResults( const exportedResultsDirectory = join(exportDirectory, "exported-results"); await exportRemoteQueryAnalysisResults( - ctx, exportedResultsDirectory, query, analysesResults, @@ -116,7 +113,6 @@ export async function exportRemoteQueryResults( } export async function exportRemoteQueryAnalysisResults( - ctx: ExtensionContext, exportedResultsPath: string, query: RemoteQuery, analysesResults: AnalysisResults[], @@ -126,7 +122,6 @@ export async function exportRemoteQueryAnalysisResults( const markdownFiles = generateMarkdown(query, analysesResults, exportFormat); await exportResults( - ctx, exportedResultsPath, description, markdownFiles, @@ -141,7 +136,6 @@ const MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS = 2; * The user is prompted to select the export format. */ export async function exportVariantAnalysisResults( - ctx: ExtensionContext, variantAnalysisManager: VariantAnalysisManager, variantAnalysisId: number, filterSort: RepositoriesFilterSortStateWithIds | undefined, @@ -187,6 +181,17 @@ export async function exportVariantAnalysisResults( throw new UserCancellationException("Cancelled"); } + const repositories = filterAndSortRepositoriesWithResults( + variantAnalysis.scannedRepos, + filterSort, + )?.filter( + (repo) => + repo.resultCount && + repoStates.find((r) => r.repositoryId === repo.repository.id) + ?.downloadStatus === + VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, + ); + async function* getAnalysesResults(): AsyncGenerator< [VariantAnalysisScannedRepository, VariantAnalysisScannedRepositoryResult] > { @@ -194,38 +199,11 @@ export async function exportVariantAnalysisResults( return; } - const repositories = filterAndSortRepositoriesWithResults( - variantAnalysis.scannedRepos, - filterSort, - ); if (!repositories) { return; } for (const repo of repositories) { - const repoState = repoStates.find( - (r) => r.repositoryId === repo.repository.id, - ); - - // Do not export if it has not yet completed or the download has not yet succeeded. - if ( - repoState?.downloadStatus !== - VariantAnalysisScannedRepositoryDownloadStatus.Succeeded - ) { - continue; - } - - if (repo.resultCount == 0) { - yield [ - repo, - { - variantAnalysisId: variantAnalysis.id, - repositoryId: repo.repository.id, - }, - ]; - continue; - } - const result = await variantAnalysisManager.loadResults( variantAnalysis.id, repo.repository.fullName, @@ -255,10 +233,10 @@ export async function exportVariantAnalysisResults( ); await exportVariantAnalysisAnalysisResults( - ctx, exportedResultsDirectory, variantAnalysis, getAnalysesResults(), + repositories?.length ?? 0, exportFormat, progress, token, @@ -266,12 +244,12 @@ export async function exportVariantAnalysisResults( } export async function exportVariantAnalysisAnalysisResults( - ctx: ExtensionContext, exportedResultsPath: string, variantAnalysis: VariantAnalysis, analysesResults: AsyncIterable< [VariantAnalysisScannedRepository, VariantAnalysisScannedRepositoryResult] >, + expectedAnalysesResultsCount: number, exportFormat: "gist" | "local", progress: ProgressCallback, token: CancellationToken, @@ -289,6 +267,7 @@ export async function exportVariantAnalysisAnalysisResults( const { markdownFiles, summaries } = await generateVariantAnalysisMarkdown( variantAnalysis, analysesResults, + expectedAnalysesResultsCount, exportFormat, ); const description = buildVariantAnalysisGistDescription( @@ -297,7 +276,6 @@ export async function exportVariantAnalysisAnalysisResults( ); await exportResults( - ctx, exportedResultsPath, description, markdownFiles, @@ -341,7 +319,6 @@ async function determineExportFormat(): Promise<"gist" | "local" | undefined> { } export async function exportResults( - ctx: ExtensionContext, exportedResultsPath: string, description: string, markdownFiles: MarkdownFile[], @@ -354,7 +331,7 @@ export async function exportResults( } if (exportFormat === "gist") { - await exportToGist(ctx, description, markdownFiles, progress, token); + await exportToGist(description, markdownFiles, progress, token); } else if (exportFormat === "local") { await exportToLocalMarkdown( exportedResultsPath, @@ -366,7 +343,6 @@ export async function exportResults( } export async function exportToGist( - ctx: ExtensionContext, description: string, markdownFiles: MarkdownFile[], progress?: ProgressCallback, @@ -378,7 +354,7 @@ export async function exportToGist( message: "Creating Gist", }); - const credentials = await Credentials.initialize(ctx); + const credentials = await Credentials.initialize(); if (token?.isCancellationRequested) { throw new UserCancellationException("Cancelled"); diff --git a/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts b/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts index 065488131..10c9925b3 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts @@ -81,20 +81,19 @@ export class RemoteQueriesManager extends DisposableObject { private readonly view: RemoteQueriesView; constructor( - private readonly ctx: ExtensionContext, + ctx: ExtensionContext, private readonly cliServer: CodeQLCliServer, private readonly storagePath: string, logger: Logger, ) { super(); this.analysesResultsManager = new AnalysesResultsManager( - ctx, cliServer, storagePath, logger, ); this.view = new RemoteQueriesView(ctx, logger, this.analysesResultsManager); - this.remoteQueriesMonitor = new RemoteQueriesMonitor(ctx, logger); + this.remoteQueriesMonitor = new RemoteQueriesMonitor(logger); this.remoteQueryAddedEventEmitter = this.push( new EventEmitter(), @@ -160,7 +159,7 @@ export class RemoteQueriesManager extends DisposableObject { progress: ProgressCallback, token: CancellationToken, ): Promise { - const credentials = await Credentials.initialize(this.ctx); + const credentials = await Credentials.initialize(); const { actionBranch, @@ -218,7 +217,7 @@ export class RemoteQueriesManager extends DisposableObject { remoteQuery: RemoteQuery, cancellationToken: CancellationToken, ): Promise { - const credentials = await Credentials.initialize(this.ctx); + const credentials = await Credentials.initialize(); const queryWorkflowResult = await this.remoteQueriesMonitor.monitorQuery( remoteQuery, diff --git a/extensions/ql-vscode/src/remote-queries/remote-queries-markdown-generation.ts b/extensions/ql-vscode/src/remote-queries/remote-queries-markdown-generation.ts index c013bca36..169642e32 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-markdown-generation.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-markdown-generation.ts @@ -94,18 +94,25 @@ export async function generateVariantAnalysisMarkdown( results: AsyncIterable< [VariantAnalysisScannedRepository, VariantAnalysisScannedRepositoryResult] >, + expectedResultsCount: number, linkType: MarkdownLinkType, ): Promise { const resultsFiles: MarkdownFile[] = []; const summaries: RepositorySummary[] = []; + for await (const [scannedRepo, result] of results) { - if (!scannedRepo.resultCount || scannedRepo.resultCount === 0) { + if (!scannedRepo.resultCount) { continue; } // Append nwo and results count to the summary table const fullName = scannedRepo.repository.fullName; - const fileName = createFileName(fullName); + const fileName = createVariantAnalysisFileName( + fullName, + resultsFiles.length, + expectedResultsCount, + linkType, + ); summaries.push({ fileName, repository: scannedRepo.repository, @@ -482,6 +489,32 @@ function createFileName(nwo: string) { return `${owner}-${repo}`; } +/** + * Creates the name of the markdown file for a given repository nwo. + * This name doesn't include the file extension. + */ +function createVariantAnalysisFileName( + fullName: string, + index: number, + expectedResultsCount: number, + linkType: MarkdownLinkType, +) { + const baseName = createFileName(fullName); + if (linkType === "gist") { + const requiredNumberOfDecimals = Math.ceil( + Math.log10(expectedResultsCount), + ); + + const prefix = (index + 1) + .toString() + .padStart(requiredNumberOfDecimals, "0"); + + return `result-${prefix}-${baseName}`; + } + + return baseName; +} + /** * Escape characters that could be interpreted as HTML instead of raw code. */ diff --git a/extensions/ql-vscode/src/remote-queries/remote-queries-monitor.ts b/extensions/ql-vscode/src/remote-queries/remote-queries-monitor.ts index 475404414..dd8c0c921 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-monitor.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-monitor.ts @@ -16,20 +16,13 @@ export class RemoteQueriesMonitor { private static readonly maxAttemptCount = 17280; private static readonly sleepTime = 5000; - constructor( - private readonly extensionContext: vscode.ExtensionContext, - private readonly logger: Logger, - ) {} + constructor(private readonly logger: Logger) {} public async monitorQuery( remoteQuery: RemoteQuery, cancellationToken: vscode.CancellationToken, ): Promise { - const credentials = await Credentials.initialize(this.extensionContext); - - if (!credentials) { - throw Error("Error authenticating with GitHub"); - } + const credentials = await Credentials.initialize(); let attemptCount = 0; diff --git a/extensions/ql-vscode/src/remote-queries/repository-selection.ts b/extensions/ql-vscode/src/remote-queries/repository-selection.ts index b98051f4e..546fdea55 100644 --- a/extensions/ql-vscode/src/remote-queries/repository-selection.ts +++ b/extensions/ql-vscode/src/remote-queries/repository-selection.ts @@ -4,7 +4,7 @@ import { extLogger } from "../common"; import { getRemoteRepositoryLists, getRemoteRepositoryListsPath, - isNewQueryRunExperienceEnabled, + isVariantAnalysisReposPanelEnabled, } from "../config"; import { OWNER_REGEX, REPO_REGEX } from "../pure/helpers-pure"; import { UserCancellationException } from "../commandRunner"; @@ -36,7 +36,7 @@ interface RepoList { export async function getRepositorySelection( dbManager?: DbManager, ): Promise { - if (isNewQueryRunExperienceEnabled()) { + if (isVariantAnalysisReposPanelEnabled()) { const selectedDbItem = dbManager?.getSelectedDbItem(); if (selectedDbItem) { switch (selectedDbItem.kind) { @@ -46,7 +46,7 @@ export async function getRepositorySelection( ); case DbItemKind.RemoteSystemDefinedList: return { repositoryLists: [selectedDbItem.listName] }; - case DbItemKind.RemoteUserDefinedList: + case DbItemKind.VariantAnalysisUserDefinedList: if (selectedDbItem.repos.length === 0) { throw new UserCancellationException( "The selected repository list is empty. Please add repositories to it before running a variant analysis.", diff --git a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts index efb749600..50146b750 100644 --- a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts +++ b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts @@ -34,6 +34,7 @@ import { DbManager } from "../databases/db-manager"; export interface QlPack { name: string; version: string; + library?: boolean; dependencies: { [key: string]: string }; defaultSuite?: Array>; defaultSuiteFile?: string; @@ -66,7 +67,7 @@ async function generateQueryPack( const targetQueryFileName = join(queryPackDir, packRelativePath); let language: string | undefined; - if (await pathExists(join(originalPackRoot, "qlpack.yml"))) { + if (await getExistingPackFile(originalPackRoot)) { // don't include ql files. We only want the queryFile to be copied. const toCopy = await cliServer.packPacklist(originalPackRoot, false); @@ -162,7 +163,7 @@ async function generateQueryPack( async function findPackRoot(queryFile: string): Promise { // recursively find the directory containing qlpack.yml let dir = dirname(queryFile); - while (!(await pathExists(join(dir, "qlpack.yml")))) { + while (!(await getExistingPackFile(dir))) { dir = dirname(dir); if (isFileSystemRoot(dir)) { // there is no qlpack.yml in this directory or any parent directory. @@ -174,6 +175,16 @@ async function findPackRoot(queryFile: string): Promise { return dir; } +async function getExistingPackFile(dir: string) { + if (await pathExists(join(dir, "qlpack.yml"))) { + return join(dir, "qlpack.yml"); + } + if (await pathExists(join(dir, "codeql-pack.yml"))) { + return join(dir, "codeql-pack.yml"); + } + return undefined; +} + function isFileSystemRoot(dir: string): boolean { const pathObj = parse(dir); return pathObj.root === dir && pathObj.base === ""; @@ -214,7 +225,7 @@ export async function prepareRemoteQueryRun( uri: Uri | undefined, progress: ProgressCallback, token: CancellationToken, - dbManager?: DbManager, // the dbManager is only needed when the newQueryRunExperience is enabled + dbManager?: DbManager, // the dbManager is only needed when variantAnalysisReposPanel is enabled ): Promise { if (!(await cliServer.cliConstraints.supportsRemoteQueries())) { throw new Error( @@ -314,7 +325,14 @@ async function fixPackFile( queryPackDir: string, packRelativePath: string, ): Promise { - const packPath = join(queryPackDir, "qlpack.yml"); + const packPath = await getExistingPackFile(queryPackDir); + + // This should not happen since we create the pack ourselves. + if (!packPath) { + throw new Error( + `Could not find qlpack.yml or codeql-pack.yml file in '${queryPackDir}'`, + ); + } const qlpack = load(await readFile(packPath, "utf8")) as QlPack; // update pack name diff --git a/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts b/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts index e045e4575..38a39e5c4 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts @@ -19,6 +19,7 @@ import { DisposableObject } from "../pure/disposable-object"; import { Credentials } from "../authentication"; import { VariantAnalysisMonitor } from "./variant-analysis-monitor"; import { + getActionsWorkflowRunUrl, isVariantAnalysisComplete, parseVariantAnalysisQueryLanguage, VariantAnalysis, @@ -60,6 +61,7 @@ import { } from "../pure/variant-analysis-filter-sort"; import { URLSearchParams } from "url"; import { DbManager } from "../databases/db-manager"; +import { isVariantAnalysisReposPanelEnabled } from "../config"; export class VariantAnalysisManager extends DisposableObject @@ -101,12 +103,11 @@ export class VariantAnalysisManager private readonly cliServer: CodeQLCliServer, private readonly storagePath: string, private readonly variantAnalysisResultsManager: VariantAnalysisResultsManager, - private readonly dbManager?: DbManager, // the dbManager is only needed when the newQueryRunExperience is enabled + private readonly dbManager?: DbManager, // the dbManager is only needed when variantAnalysisReposPanel is enabled ) { super(); this.variantAnalysisMonitor = this.push( new VariantAnalysisMonitor( - ctx, this.shouldCancelMonitorVariantAnalysis.bind(this), ), ); @@ -125,7 +126,7 @@ export class VariantAnalysisManager progress: ProgressCallback, token: CancellationToken, ): Promise { - const credentials = await Credentials.initialize(this.ctx); + const credentials = await Credentials.initialize(); const { actionBranch, @@ -479,10 +480,7 @@ export class VariantAnalysisManager await this.onRepoStateUpdated(variantAnalysis.id, repoState); - const credentials = await Credentials.initialize(this.ctx); - if (!credentials) { - throw Error("Error authenticating with GitHub"); - } + const credentials = await Credentials.initialize(); if (cancellationToken && cancellationToken.isCancellationRequested) { repoState.downloadStatus = @@ -580,10 +578,7 @@ export class VariantAnalysisManager ); } - const credentials = await Credentials.initialize(this.ctx); - if (!credentials) { - throw Error("Error authenticating with GitHub"); - } + const credentials = await Credentials.initialize(); void showAndLogInformationMessage( "Cancelling variant analysis. This may take a while.", @@ -591,6 +586,20 @@ export class VariantAnalysisManager await cancelVariantAnalysis(credentials, variantAnalysis); } + public async openVariantAnalysisLogs(variantAnalysisId: number) { + const variantAnalysis = this.variantAnalyses.get(variantAnalysisId); + if (!variantAnalysis) { + throw new Error(`No variant analysis with id: ${variantAnalysisId}`); + } + + const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysis); + + await commands.executeCommand( + "vscode.open", + Uri.parse(actionsWorkflowRunUrl), + ); + } + public async copyRepoListToClipboard( variantAnalysisId: number, filterSort: RepositoriesFilterSortStateWithIds = defaultFilterSortState, @@ -612,12 +621,25 @@ export class VariantAnalysisManager return; } - const text = [ - '"new-repo-list": [', - ...fullNames.slice(0, -1).map((repo) => ` "${repo}",`), - ` "${fullNames[fullNames.length - 1]}"`, - "]", - ]; + let text: string[]; + if (isVariantAnalysisReposPanelEnabled()) { + text = [ + "{", + ` "name": "new-repo-list",`, + ` "repositories": [`, + ...fullNames.slice(0, -1).map((repo) => ` "${repo}",`), + ` "${fullNames[fullNames.length - 1]}"`, + ` ]`, + "}", + ]; + } else { + text = [ + '"new-repo-list": [', + ...fullNames.slice(0, -1).map((repo) => ` "${repo}",`), + ` "${fullNames[fullNames.length - 1]}"`, + "]", + ]; + } await env.clipboard.writeText(text.join(EOL)); } diff --git a/extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts b/extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts index ba528de15..cf9979c5e 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts @@ -1,9 +1,4 @@ -import { - CancellationToken, - commands, - EventEmitter, - ExtensionContext, -} from "vscode"; +import { CancellationToken, commands, EventEmitter } from "vscode"; import { Credentials } from "../authentication"; import { getVariantAnalysis } from "./gh-api/gh-api-client"; @@ -32,7 +27,6 @@ export class VariantAnalysisMonitor extends DisposableObject { readonly onVariantAnalysisChange = this._onVariantAnalysisChange.event; constructor( - private readonly extensionContext: ExtensionContext, private readonly shouldCancelMonitor: ( variantAnalysisId: number, ) => Promise, @@ -44,10 +38,7 @@ export class VariantAnalysisMonitor extends DisposableObject { variantAnalysis: VariantAnalysis, cancellationToken: CancellationToken, ): Promise { - const credentials = await Credentials.initialize(this.extensionContext); - if (!credentials) { - throw Error("Error authenticating with GitHub"); - } + const credentials = await Credentials.initialize(); let attemptCount = 0; const scannedReposDownloaded: number[] = []; diff --git a/extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts b/extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts index c8d91e1f2..24c1589c4 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts @@ -1,4 +1,4 @@ -import { commands, ExtensionContext, Uri, ViewColumn } from "vscode"; +import { commands, ExtensionContext, ViewColumn } from "vscode"; import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview"; import { extLogger } from "../common"; import { @@ -7,7 +7,6 @@ import { } from "../pure/interface-types"; import { assertNever } from "../pure/helpers-pure"; import { - getActionsWorkflowRunUrl, VariantAnalysis, VariantAnalysisScannedRepositoryResult, VariantAnalysisScannedRepositoryState, @@ -147,7 +146,10 @@ export class VariantAnalysisView ); break; case "openLogs": - await this.openLogs(); + await commands.executeCommand( + "codeQL.openVariantAnalysisLogs", + this.variantAnalysisId, + ); break; default: assertNever(msg); @@ -183,23 +185,4 @@ export class VariantAnalysisView repoStates, }); } - - private async openLogs(): Promise { - const variantAnalysis = await this.manager.getVariantAnalysis( - this.variantAnalysisId, - ); - if (!variantAnalysis) { - void showAndLogWarningMessage( - "Could not open variant analysis logs. Variant analysis not found.", - ); - return; - } - - const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysis); - - await commands.executeCommand( - "vscode.open", - Uri.parse(actionsWorkflowRunUrl), - ); - } } diff --git a/extensions/ql-vscode/src/stories/tsconfig.json b/extensions/ql-vscode/src/stories/tsconfig.json index a1ccc8cec..135aa8cd3 100644 --- a/extensions/ql-vscode/src/stories/tsconfig.json +++ b/extensions/ql-vscode/src/stories/tsconfig.json @@ -7,7 +7,7 @@ "lib": ["ES2021", "dom"], "jsx": "react", "sourceMap": true, - "rootDir": "..", + "rootDir": "../../..", "strict": true, "noUnusedLocals": true, "noImplicitReturns": true, diff --git a/extensions/ql-vscode/src/stories/variant-analysis/RepoRow.stories.tsx b/extensions/ql-vscode/src/stories/variant-analysis/RepoRow.stories.tsx index bf1e0d99b..26d497046 100644 --- a/extensions/ql-vscode/src/stories/variant-analysis/RepoRow.stories.tsx +++ b/extensions/ql-vscode/src/stories/variant-analysis/RepoRow.stories.tsx @@ -11,7 +11,7 @@ import { AnalysisAlert, AnalysisRawResults, } from "../../remote-queries/shared/analysis-result"; -import { createMockRepositoryWithMetadata } from "../../vscode-tests/factories/remote-queries/shared/repository"; +import { createMockRepositoryWithMetadata } from "../../../test/factories/remote-queries/shared/repository"; import * as analysesResults from "../remote-queries/data/analysesResultsMessage.json"; import * as rawResults from "../remote-queries/data/rawResults.json"; diff --git a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysis.stories.tsx b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysis.stories.tsx index 182e45c94..3ca84c2ef 100644 --- a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysis.stories.tsx +++ b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysis.stories.tsx @@ -12,8 +12,8 @@ import { VariantAnalysisScannedRepositoryState, VariantAnalysisStatus, } from "../../remote-queries/shared/variant-analysis"; -import { createMockVariantAnalysis } from "../../vscode-tests/factories/remote-queries/shared/variant-analysis"; -import { createMockRepositoryWithMetadata } from "../../vscode-tests/factories/remote-queries/shared/repository"; +import { createMockVariantAnalysis } from "../../../test/factories/remote-queries/shared/variant-analysis"; +import { createMockRepositoryWithMetadata } from "../../../test/factories/remote-queries/shared/repository"; export default { title: "Variant Analysis/Variant Analysis", diff --git a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisAnalyzedRepos.stories.tsx b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisAnalyzedRepos.stories.tsx index 9b7c5f52f..05d6ea5b5 100644 --- a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisAnalyzedRepos.stories.tsx +++ b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisAnalyzedRepos.stories.tsx @@ -12,9 +12,9 @@ import { VariantAnalysisStatus, } from "../../remote-queries/shared/variant-analysis"; import { AnalysisAlert } from "../../remote-queries/shared/analysis-result"; -import { createMockVariantAnalysis } from "../../vscode-tests/factories/remote-queries/shared/variant-analysis"; -import { createMockRepositoryWithMetadata } from "../../vscode-tests/factories/remote-queries/shared/repository"; -import { createMockScannedRepo } from "../../vscode-tests/factories/remote-queries/shared/scanned-repositories"; +import { createMockVariantAnalysis } from "../../../test/factories/remote-queries/shared/variant-analysis"; +import { createMockRepositoryWithMetadata } from "../../../test/factories/remote-queries/shared/repository"; +import { createMockScannedRepo } from "../../../test/factories/remote-queries/shared/scanned-repositories"; import * as analysesResults from "../remote-queries/data/analysesResultsMessage.json"; diff --git a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisHeader.stories.tsx b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisHeader.stories.tsx index a464b5e23..30920a5f9 100644 --- a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisHeader.stories.tsx +++ b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisHeader.stories.tsx @@ -8,8 +8,8 @@ import { VariantAnalysisRepoStatus, VariantAnalysisStatus, } from "../../remote-queries/shared/variant-analysis"; -import { createMockVariantAnalysis } from "../../vscode-tests/factories/remote-queries/shared/variant-analysis"; -import { createMockScannedRepo } from "../../vscode-tests/factories/remote-queries/shared/scanned-repositories"; +import { createMockVariantAnalysis } from "../../../test/factories/remote-queries/shared/variant-analysis"; +import { createMockScannedRepo } from "../../../test/factories/remote-queries/shared/scanned-repositories"; export default { title: "Variant Analysis/Variant Analysis Header", diff --git a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisOutcomePanels.stories.tsx b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisOutcomePanels.stories.tsx index 04c03d910..9241bf183 100644 --- a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisOutcomePanels.stories.tsx +++ b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisOutcomePanels.stories.tsx @@ -9,9 +9,9 @@ import { VariantAnalysisRepoStatus, VariantAnalysisStatus, } from "../../remote-queries/shared/variant-analysis"; -import { createMockScannedRepo } from "../../vscode-tests/factories/remote-queries/shared/scanned-repositories"; -import { createMockVariantAnalysis } from "../../vscode-tests/factories/remote-queries/shared/variant-analysis"; -import { createMockRepositoryWithMetadata } from "../../vscode-tests/factories/remote-queries/shared/repository"; +import { createMockScannedRepo } from "../../../test/factories/remote-queries/shared/scanned-repositories"; +import { createMockVariantAnalysis } from "../../../test/factories/remote-queries/shared/variant-analysis"; +import { createMockRepositoryWithMetadata } from "../../../test/factories/remote-queries/shared/repository"; import { defaultFilterSortState, RepositoriesFilterSortState, diff --git a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisSkippedRepositoriesTab.stories.tsx b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisSkippedRepositoriesTab.stories.tsx index fa90b1df1..26d5bb970 100644 --- a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisSkippedRepositoriesTab.stories.tsx +++ b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisSkippedRepositoriesTab.stories.tsx @@ -4,7 +4,7 @@ import { ComponentMeta, ComponentStory } from "@storybook/react"; import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer"; import { VariantAnalysisSkippedRepositoriesTab } from "../../view/variant-analysis/VariantAnalysisSkippedRepositoriesTab"; -import { createMockRepositoryWithMetadata } from "../../vscode-tests/factories/remote-queries/shared/repository"; +import { createMockRepositoryWithMetadata } from "../../../test/factories/remote-queries/shared/repository"; export default { title: "Variant Analysis/Variant Analysis Skipped Repositories Tab", diff --git a/extensions/ql-vscode/src/telemetry.ts b/extensions/ql-vscode/src/telemetry.ts index 0b86eb15a..b145a3985 100644 --- a/extensions/ql-vscode/src/telemetry.ts +++ b/extensions/ql-vscode/src/telemetry.ts @@ -95,10 +95,8 @@ export class TelemetryListener extends ConfigListener { CANARY_FEATURES.getValue() && !ENABLE_TELEMETRY.getValue() ) { - await Promise.all([ - this.setTelemetryRequested(false), - this.requestTelemetryPermission(), - ]); + await this.setTelemetryRequested(false); + await this.requestTelemetryPermission(); } } @@ -227,12 +225,15 @@ export class TelemetryListener extends ConfigListener { /** * The global Telemetry instance */ -export let telemetryListener: TelemetryListener; +export let telemetryListener: TelemetryListener | undefined; export async function initializeTelemetry( extension: Extension, ctx: ExtensionContext, ): Promise { + if (telemetryListener !== undefined) { + throw new Error("Telemetry is already initialized"); + } telemetryListener = new TelemetryListener( extension.id, extension.packageJSON.version, diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodeFlowsDropdown.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodeFlowsDropdown.tsx index dbc99c2f4..7249d5512 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodeFlowsDropdown.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodeFlowsDropdown.tsx @@ -22,16 +22,21 @@ export const CodeFlowsDropdown = ({ const handleChange = useCallback( (e: ChangeEvent) => { const selectedOption = e.target; - const selectedIndex = selectedOption.value as unknown as number; + const selectedIndex = parseInt(selectedOption.value); setSelectedCodeFlow(codeFlows[selectedIndex]); }, [setSelectedCodeFlow, codeFlows], ); return ( - + unknown) & + React.FormEventHandler + } + > {codeFlows.map((codeFlow, index) => ( - + {getCodeFlowName(codeFlow)} ))} diff --git a/extensions/ql-vscode/src/view/common/CodePaths/__tests__/CodePaths.spec.tsx b/extensions/ql-vscode/src/view/common/CodePaths/__tests__/CodePaths.spec.tsx index 50f1e60f8..c650dbb7d 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/__tests__/CodePaths.spec.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/__tests__/CodePaths.spec.tsx @@ -3,8 +3,8 @@ import { render as reactRender, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { CodePaths, CodePathsProps } from "../CodePaths"; -import { createMockCodeFlows } from "../../../../vscode-tests/factories/remote-queries/shared/CodeFlow"; -import { createMockAnalysisMessage } from "../../../../vscode-tests/factories/remote-queries/shared/AnalysisMessage"; +import { createMockCodeFlows } from "../../../../../test/factories/remote-queries/shared/CodeFlow"; +import { createMockAnalysisMessage } from "../../../../../test/factories/remote-queries/shared/AnalysisMessage"; describe(CodePaths.name, () => { const render = (props?: CodePathsProps) => diff --git a/extensions/ql-vscode/src/view/tsconfig.json b/extensions/ql-vscode/src/view/tsconfig.json index c3a8a6ce0..b158c1e92 100644 --- a/extensions/ql-vscode/src/view/tsconfig.json +++ b/extensions/ql-vscode/src/view/tsconfig.json @@ -7,7 +7,7 @@ "lib": ["ES2021", "dom"], "jsx": "react", "sourceMap": true, - "rootDir": "..", + "rootDir": "../..", "strict": true, "noUnusedLocals": true, "noImplicitReturns": true, diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx index 111021c18..86820343c 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/RepoRow.spec.tsx @@ -6,7 +6,7 @@ import { } from "../../../remote-queries/shared/variant-analysis"; import userEvent from "@testing-library/user-event"; import { RepoRow, RepoRowProps } from "../RepoRow"; -import { createMockRepositoryWithMetadata } from "../../../vscode-tests/factories/remote-queries/shared/repository"; +import { createMockRepositoryWithMetadata } from "../../../../test/factories/remote-queries/shared/repository"; describe(RepoRow.name, () => { const render = (props: Partial = {}) => { diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysis.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysis.spec.tsx index a073ef331..2f1fb3dd4 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysis.spec.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysis.spec.tsx @@ -5,7 +5,7 @@ import { VariantAnalysisStatus, } from "../../../remote-queries/shared/variant-analysis"; import { VariantAnalysis, VariantAnalysisProps } from "../VariantAnalysis"; -import { createMockVariantAnalysis } from "../../../vscode-tests/factories/remote-queries/shared/variant-analysis"; +import { createMockVariantAnalysis } from "../../../../test/factories/remote-queries/shared/variant-analysis"; describe(VariantAnalysis.name, () => { const render = (props: Partial = {}) => diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisAnalyzedRepos.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisAnalyzedRepos.spec.tsx index bf026eed3..da2b9096c 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisAnalyzedRepos.spec.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisAnalyzedRepos.spec.tsx @@ -10,9 +10,9 @@ import { VariantAnalysisAnalyzedRepos, VariantAnalysisAnalyzedReposProps, } from "../VariantAnalysisAnalyzedRepos"; -import { createMockVariantAnalysis } from "../../../vscode-tests/factories/remote-queries/shared/variant-analysis"; -import { createMockRepositoryWithMetadata } from "../../../vscode-tests/factories/remote-queries/shared/repository"; -import { createMockScannedRepo } from "../../../vscode-tests/factories/remote-queries/shared/scanned-repositories"; +import { createMockVariantAnalysis } from "../../../../test/factories/remote-queries/shared/variant-analysis"; +import { createMockRepositoryWithMetadata } from "../../../../test/factories/remote-queries/shared/repository"; +import { createMockScannedRepo } from "../../../../test/factories/remote-queries/shared/scanned-repositories"; import { defaultFilterSortState, SortKey, diff --git a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisOutcomePanels.spec.tsx b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisOutcomePanels.spec.tsx index 0e76ff3c5..d52777d36 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisOutcomePanels.spec.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/__tests__/VariantAnalysisOutcomePanels.spec.tsx @@ -10,12 +10,12 @@ import { VariantAnalysisOutcomePanelProps, VariantAnalysisOutcomePanels, } from "../VariantAnalysisOutcomePanels"; -import { createMockVariantAnalysis } from "../../../vscode-tests/factories/remote-queries/shared/variant-analysis"; -import { createMockRepositoryWithMetadata } from "../../../vscode-tests/factories/remote-queries/shared/repository"; +import { createMockVariantAnalysis } from "../../../../test/factories/remote-queries/shared/variant-analysis"; +import { createMockRepositoryWithMetadata } from "../../../../test/factories/remote-queries/shared/repository"; import { createMockScannedRepo, createMockScannedRepos, -} from "../../../vscode-tests/factories/remote-queries/shared/scanned-repositories"; +} from "../../../../test/factories/remote-queries/shared/scanned-repositories"; import { defaultFilterSortState } from "../../../pure/variant-analysis-filter-sort"; describe(VariantAnalysisOutcomePanels.name, () => { diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/databases/db-panel.test.ts b/extensions/ql-vscode/src/vscode-tests/cli-integration/databases/db-panel.test.ts deleted file mode 100644 index 3eb014b51..000000000 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/databases/db-panel.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { commands, extensions, window } from "vscode"; - -import { CodeQLExtensionInterface } from "../../../extension"; -import { readJson } from "fs-extra"; -import * as path from "path"; -import { DbConfig } from "../../../databases/config/db-config"; - -jest.setTimeout(60_000); - -describe("Db panel UI commands", () => { - let extension: CodeQLExtensionInterface | Record; - let storagePath: string; - - beforeEach(async () => { - extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - - storagePath = - extension.ctx.storageUri?.fsPath || extension.ctx.globalStorageUri.fsPath; - }); - - it("should add new remote db list", async () => { - // Add db list - jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1"); - await commands.executeCommand("codeQLDatabasesExperimental.addNewList"); - - // Check db config - const dbConfigFilePath = path.join(storagePath, "workspace-databases.json"); - const dbConfig: DbConfig = await readJson(dbConfigFilePath); - expect(dbConfig.databases.remote.repositoryLists).toHaveLength(1); - expect(dbConfig.databases.remote.repositoryLists[0].name).toBe("my-list-1"); - }); -}); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts deleted file mode 100644 index eb1700ef9..000000000 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { CancellationTokenSource, commands, extensions } from "vscode"; -import { CodeQLExtensionInterface } from "../../../extension"; -import * as config from "../../../config"; - -import * as ghApiClient from "../../../remote-queries/gh-api/gh-api-client"; -import { VariantAnalysisMonitor } from "../../../remote-queries/variant-analysis-monitor"; -import { - VariantAnalysis as VariantAnalysisApiResponse, - VariantAnalysisFailureReason, - VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository, -} from "../../../remote-queries/gh-api/variant-analysis"; -import { - createFailedMockApiResponse, - createMockApiResponse, -} from "../../factories/remote-queries/gh-api/variant-analysis-api-response"; -import { - VariantAnalysis, - VariantAnalysisStatus, -} from "../../../remote-queries/shared/variant-analysis"; -import { createMockScannedRepos } from "../../factories/remote-queries/gh-api/scanned-repositories"; -import { - processFailureReason, - processScannedRepository, - processUpdatedVariantAnalysis, -} from "../../../remote-queries/variant-analysis-processor"; -import { Credentials } from "../../../authentication"; -import { createMockVariantAnalysis } from "../../factories/remote-queries/shared/variant-analysis"; -import { VariantAnalysisManager } from "../../../remote-queries/variant-analysis-manager"; - -jest.setTimeout(60_000); - -describe("Variant Analysis Monitor", () => { - let extension: CodeQLExtensionInterface | Record; - let mockGetVariantAnalysis: jest.SpiedFunction< - typeof ghApiClient.getVariantAnalysis - >; - let cancellationTokenSource: CancellationTokenSource; - 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(); - - beforeEach(async () => { - jest - .spyOn(config, "isVariantAnalysisLiveResultsEnabled") - .mockReturnValue(false); - - cancellationTokenSource = new CancellationTokenSource(); - - variantAnalysis = createMockVariantAnalysis({}); - - shouldCancelMonitor = jest.fn(); - - extension = await extensions - .getExtension>( - "GitHub.vscode-codeql", - )! - .activate(); - variantAnalysisMonitor = new VariantAnalysisMonitor( - extension.ctx, - shouldCancelMonitor, - ); - variantAnalysisMonitor.onVariantAnalysisChange(onVariantAnalysisChangeSpy); - - variantAnalysisManager = extension.variantAnalysisManager; - mockGetDownloadResult = jest - .spyOn(variantAnalysisManager, "autoDownloadVariantAnalysisResult") - .mockResolvedValue(undefined); - - mockGetVariantAnalysis = jest - .spyOn(ghApiClient, "getVariantAnalysis") - .mockRejectedValue(new Error("Not mocked")); - - limitNumberOfAttemptsToMonitor(); - }); - - describe("when credentials are invalid", () => { - beforeEach(async () => { - jest - .spyOn(Credentials, "initialize") - .mockResolvedValue(undefined as unknown as Credentials); - }); - - it("should return early if credentials are wrong", async () => { - try { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - } catch (error: any) { - expect(error.message).toBe("Error authenticating with GitHub"); - } - }); - }); - - describe("when credentials are valid", () => { - beforeEach(async () => { - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; - jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); - }); - - it("should return early if variant analysis is cancelled", async () => { - cancellationTokenSource.cancel(); - - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled(); - }); - - it("should return early if variant analysis should be cancelled", async () => { - shouldCancelMonitor.mockResolvedValue(true); - - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled(); - }); - - describe("when the variant analysis fails", () => { - let mockFailedApiResponse: VariantAnalysisApiResponse; - - beforeEach(async () => { - mockFailedApiResponse = createFailedMockApiResponse(); - mockGetVariantAnalysis.mockResolvedValue(mockFailedApiResponse); - }); - - it("should mark as failed and stop monitoring", async () => { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(mockGetVariantAnalysis).toHaveBeenCalledTimes(1); - - expect(onVariantAnalysisChangeSpy).toHaveBeenCalledWith( - expect.objectContaining({ - status: VariantAnalysisStatus.Failed, - failureReason: processFailureReason( - mockFailedApiResponse.failure_reason as VariantAnalysisFailureReason, - ), - }), - ); - }); - }); - - 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 () => { - scannedRepos = createMockScannedRepos([ - "pending", - "pending", - "in_progress", - "in_progress", - "succeeded", - "succeeded", - "succeeded", - ]); - 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, - cancellationTokenSource.token, - ); - - expect(commandSpy).toBeCalledTimes(succeededRepos.length); - - succeededRepos.forEach((succeededRepo, index) => { - expect(commandSpy).toHaveBeenNthCalledWith( - index + 1, - "codeQL.autoDownloadVariantAnalysisResult", - processScannedRepository(succeededRepo), - processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse), - ); - }); - }); - - it("should download all available results", async () => { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(mockGetDownloadResult).toBeCalledTimes(succeededRepos.length); - - succeededRepos.forEach((succeededRepo, index) => { - expect(mockGetDownloadResult).toHaveBeenNthCalledWith( - index + 1, - processScannedRepository(succeededRepo), - processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse), - undefined, - ); - }); - }); - }); - - describe("when there are only in progress repos", () => { - let scannedRepos: ApiVariantAnalysisScannedRepository[]; - - beforeEach(async () => { - scannedRepos = createMockScannedRepos(["pending", "in_progress"]); - mockApiResponse = createMockApiResponse("in_progress", scannedRepos); - mockGetVariantAnalysis.mockResolvedValue(mockApiResponse); - }); - - it("should succeed and not download any repos via a command", async () => { - const commandSpy = jest - .spyOn(commands, "executeCommand") - .mockResolvedValue(undefined); - - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(commandSpy).not.toHaveBeenCalled(); - }); - - it("should not try to download any repos", async () => { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(mockGetDownloadResult).not.toBeCalled(); - }); - }); - - describe("when the responses change", () => { - let scannedRepos: ApiVariantAnalysisScannedRepository[]; - - beforeEach(async () => { - scannedRepos = createMockScannedRepos([ - "pending", - "in_progress", - "in_progress", - "in_progress", - "pending", - "pending", - ]); - mockApiResponse = createMockApiResponse("in_progress", scannedRepos); - mockGetVariantAnalysis.mockResolvedValueOnce(mockApiResponse); - - let nextApiResponse = { - ...mockApiResponse, - scanned_repositories: [...scannedRepos.map((r) => ({ ...r }))], - }; - nextApiResponse.scanned_repositories[0].analysis_status = "succeeded"; - nextApiResponse.scanned_repositories[1].analysis_status = "succeeded"; - mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse); - - nextApiResponse = { - ...mockApiResponse, - scanned_repositories: [ - ...nextApiResponse.scanned_repositories.map((r) => ({ ...r })), - ], - }; - nextApiResponse.scanned_repositories[2].analysis_status = "succeeded"; - nextApiResponse.scanned_repositories[5].analysis_status = "succeeded"; - mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse); - - nextApiResponse = { - ...mockApiResponse, - scanned_repositories: [ - ...nextApiResponse.scanned_repositories.map((r) => ({ ...r })), - ], - }; - nextApiResponse.scanned_repositories[3].analysis_status = "succeeded"; - nextApiResponse.scanned_repositories[4].analysis_status = "failed"; - mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse); - }); - - it("should trigger a download extension command for each repo", async () => { - const commandSpy = jest - .spyOn(commands, "executeCommand") - .mockResolvedValue(undefined); - - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(mockGetVariantAnalysis).toBeCalledTimes(4); - expect(commandSpy).toBeCalledTimes(5); - }); - }); - - describe("when there are no repos to scan", () => { - beforeEach(async () => { - scannedRepos = []; - mockApiResponse = createMockApiResponse("succeeded", scannedRepos); - mockGetVariantAnalysis.mockResolvedValue(mockApiResponse); - }); - - it("should not try to download any repos", async () => { - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(mockGetDownloadResult).not.toBeCalled(); - }); - }); - }); - }); - - function limitNumberOfAttemptsToMonitor() { - VariantAnalysisMonitor.maxAttemptCount = 3; - VariantAnalysisMonitor.sleepTime = 1; - } -}); diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/qltest-discovery.test.ts b/extensions/ql-vscode/src/vscode-tests/minimal-workspace/qltest-discovery.test.ts deleted file mode 100644 index c03203371..000000000 --- a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/qltest-discovery.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Uri, WorkspaceFolder } from "vscode"; -import * as fs from "fs-extra"; - -import { QLTestDiscovery } from "../../qltest-discovery"; - -describe("qltest-discovery", () => { - describe("discoverTests", () => { - const baseUri = Uri.parse("file:/a/b"); - const baseDir = baseUri.fsPath; - const cDir = Uri.parse("file:/a/b/c").fsPath; - const dFile = Uri.parse("file:/a/b/c/d.ql").fsPath; - const eFile = Uri.parse("file:/a/b/c/e.ql").fsPath; - const hDir = Uri.parse("file:/a/b/c/f/g/h").fsPath; - const iFile = Uri.parse("file:/a/b/c/f/g/h/i.ql").fsPath; - let qlTestDiscover: QLTestDiscovery; - - beforeEach(() => { - qlTestDiscover = new QLTestDiscovery( - { - uri: baseUri, - name: "My tests", - } as unknown as WorkspaceFolder, - { - resolveTests() { - return [ - Uri.parse("file:/a/b/c/d.ql").fsPath, - Uri.parse("file:/a/b/c/e.ql").fsPath, - Uri.parse("file:/a/b/c/f/g/h/i.ql").fsPath, - ]; - }, - } as any, - ); - }); - - it("should run discovery", async () => { - jest - .spyOn(fs, "pathExists") - .mockImplementation(() => Promise.resolve(true)); - - const result = await (qlTestDiscover as any).discover(); - expect(result.watchPath).toBe(baseDir); - expect(result.testDirectory.path).toBe(baseDir); - expect(result.testDirectory.name).toBe("My tests"); - - let children = result.testDirectory.children; - expect(children[0].path).toBe(cDir); - expect(children[0].name).toBe("c"); - expect(children.length).toBe(1); - - children = children[0].children; - expect(children[0].path).toBe(dFile); - expect(children[0].name).toBe("d.ql"); - expect(children[1].path).toBe(eFile); - expect(children[1].name).toBe("e.ql"); - - // A merged foler - expect(children[2].path).toBe(hDir); - expect(children[2].name).toBe("f / g / h"); - expect(children.length).toBe(3); - - children = children[2].children; - expect(children[0].path).toBe(iFile); - expect(children[0].name).toBe("i.ql"); - }); - - it("should avoid discovery if a folder does not exist", async () => { - jest - .spyOn(fs, "pathExists") - .mockImplementation(() => Promise.resolve(false)); - - const result = await (qlTestDiscover as any).discover(); - expect(result.watchPath).toBe(baseDir); - expect(result.testDirectory.path).toBe(baseDir); - expect(result.testDirectory.name).toBe("My tests"); - - expect(result.testDirectory.children.length).toBe(0); - }); - }); -}); diff --git a/extensions/ql-vscode/src/vscode-tests/run-integration-tests.ts b/extensions/ql-vscode/src/vscode-tests/run-integration-tests.ts deleted file mode 100644 index b8ac58f11..000000000 --- a/extensions/ql-vscode/src/vscode-tests/run-integration-tests.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { resolve, join } from "path"; -import { platform } from "os"; -import { spawnSync } from "child_process"; -import { - runTests, - downloadAndUnzipVSCode, - resolveCliArgsFromVSCodeExecutablePath, -} from "@vscode/test-electron"; -import { assertNever } from "../pure/helpers-pure"; -import { dirSync } from "tmp-promise"; - -// For some reason, the following are not exported directly from `vscode-test`, -// but we can be tricky and import directly from the out file. -import { TestOptions } from "@vscode/test-electron/out/runTest"; - -// For CI purposes we want to leave this at 'stable' to catch any bugs -// that might show up with new vscode versions released, even though -// this makes testing not-quite-pure, but it can be changed for local -// testing against old versions if necessary. -const VSCODE_VERSION = "stable"; - -// List if test dirs -// - no-workspace - Tests with no workspace selected upon launch. -// - minimal-workspace - Tests with a simple workspace selected upon launch. -// - cli-integration - Tests that require a cli to invoke actual commands -enum TestDir { - NoWorksspace = "no-workspace", - MinimalWorksspace = "minimal-workspace", - CliIntegration = "cli-integration", -} - -/** - * Run an integration test suite `suite`, retrying if it segfaults, at - * most `tries` times. - */ -async function runTestsWithRetryOnSegfault( - suite: TestOptions, - tries: number, -): Promise { - for (let t = 0; t < tries; t++) { - try { - // Download and unzip VS Code if necessary, and run the integration test suite. - return await runTests(suite); - } catch (err) { - if (err === "SIGSEGV") { - console.error("Test runner segfaulted."); - if (t < tries - 1) console.error("Retrying..."); - } else if (platform() === "win32") { - console.error(`Test runner caught exception (${err})`); - if (t < tries - 1) console.error("Retrying..."); - } else { - throw err; - } - } - } - console.error( - `Tried running suite ${tries} time(s), still failed, giving up.`, - ); - process.exit(1); -} - -const tmpDir = dirSync({ unsafeCleanup: true }); - -/** - * Integration test runner. Launches the VSCode Extension Development Host with this extension installed. - * See https://github.com/microsoft/vscode-test/blob/master/sample/test/runTest.ts - */ -async function main() { - let exitCode = 0; - try { - const extensionDevelopmentPath = resolve(__dirname, "../.."); - const vscodeExecutablePath = await downloadAndUnzipVSCode(VSCODE_VERSION); - - // Which tests to run. Use a comma-separated list of directories. - const testDirsString = process.argv[2]; - const dirs = testDirsString - .split(",") - .map((dir) => dir.trim().toLocaleLowerCase()); - const extensionTestsEnv: Record = {}; - if (dirs.includes(TestDir.CliIntegration)) { - console.log("Installing required extensions"); - const [cli, ...args] = - resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath); - spawnSync( - cli, - [ - ...args, - "--install-extension", - "hbenl.vscode-test-explorer", - "--install-extension", - "ms-vscode.test-adapter-converter", - ], - { - encoding: "utf-8", - stdio: "inherit", - }, - ); - extensionTestsEnv.INTEGRATION_TEST_MODE = "true"; - } - - console.log(`Running integration tests in these directories: ${dirs}`); - for (const dir of dirs) { - const launchArgs = getLaunchArgs(dir as TestDir); - console.log(`Next integration test dir: ${dir}`); - console.log(`Launch args: ${launchArgs}`); - exitCode = await runTestsWithRetryOnSegfault( - { - version: VSCODE_VERSION, - vscodeExecutablePath, - extensionDevelopmentPath, - extensionTestsPath: resolve(__dirname, dir, "index"), - extensionTestsEnv, - launchArgs, - }, - 3, - ); - } - } catch (err) { - console.error(`Unexpected exception while running tests: ${err}`); - if (err instanceof Error) { - console.error(err.stack); - } - exitCode = 1; - } finally { - process.exit(exitCode); - } -} - -void main(); - -function getLaunchArgs(dir: TestDir) { - switch (dir) { - case TestDir.NoWorksspace: - return [ - "--disable-extensions", - "--disable-gpu", - "--disable-workspace-trust", - `--user-data-dir=${join(tmpDir.name, dir, "user-data")}`, - ]; - - case TestDir.MinimalWorksspace: - return [ - "--disable-extensions", - "--disable-gpu", - "--disable-workspace-trust", - `--user-data-dir=${join(tmpDir.name, dir, "user-data")}`, - resolve(__dirname, "../../test/data"), - ]; - - case TestDir.CliIntegration: - // CLI integration tests requires a multi-root workspace so that the data and the QL sources are accessible. - return [ - "--disable-workspace-trust", - "--disable-gpu", - resolve(__dirname, "../../test/data"), - - // explicitly disable extensions that are known to interfere with the CLI integration tests - "--disable-extension", - "eamodio.gitlens", - "--disable-extension", - "github.codespaces", - "--disable-extension", - "github.copilot", - `--user-data-dir=${join(tmpDir.name, dir, "user-data")}`, - ].concat( - process.env.TEST_CODEQL_PATH ? [process.env.TEST_CODEQL_PATH] : [], - ); - - default: - assertNever(dir); - } -} diff --git a/extensions/ql-vscode/test/__mocks__/appMock.ts b/extensions/ql-vscode/test/__mocks__/appMock.ts index 01f54ae85..cb8dd52d4 100644 --- a/extensions/ql-vscode/test/__mocks__/appMock.ts +++ b/extensions/ql-vscode/test/__mocks__/appMock.ts @@ -1,7 +1,9 @@ import { App, AppMode } from "../../src/common/app"; import { AppEvent, AppEventEmitter } from "../../src/common/events"; +import { Memento } from "../../src/common/memento"; import { Disposable } from "../../src/pure/disposable-object"; import { createMockLogger } from "./loggerMock"; +import { createMockMemento } from "../mock-memento"; export function createMockApp({ extensionPath = "/mock/extension/path", @@ -9,12 +11,14 @@ export function createMockApp({ globalStoragePath = "/mock/global/storage/path", createEventEmitter = () => new MockAppEventEmitter(), executeCommand = jest.fn(() => Promise.resolve()), + workspaceState = createMockMemento(), }: { extensionPath?: string; workspaceStoragePath?: string; globalStoragePath?: string; createEventEmitter?: () => AppEventEmitter; executeCommand?: () => Promise; + workspaceState?: Memento; }): App { return { mode: AppMode.Test, @@ -23,6 +27,7 @@ export function createMockApp({ extensionPath, workspaceStoragePath, globalStoragePath, + workspaceState, createEventEmitter, executeCommand, }; diff --git a/extensions/ql-vscode/src/vscode-tests/factories/db-config-factories.ts b/extensions/ql-vscode/test/factories/db-config-factories.ts similarity index 89% rename from extensions/ql-vscode/src/vscode-tests/factories/db-config-factories.ts rename to extensions/ql-vscode/test/factories/db-config-factories.ts index 48f523d66..618742de0 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/db-config-factories.ts +++ b/extensions/ql-vscode/test/factories/db-config-factories.ts @@ -1,12 +1,11 @@ import { faker } from "@faker-js/faker"; import { DbConfig, - ExpandedDbItem, LocalDatabase, LocalList, RemoteRepositoryList, SelectedDbItem, -} from "../../databases/config/db-config"; +} from "../../src/databases/config/db-config"; export function createDbConfig({ remoteLists = [], @@ -15,7 +14,6 @@ export function createDbConfig({ localLists = [], localDbs = [], selected = undefined, - expanded = [], }: { remoteLists?: RemoteRepositoryList[]; remoteOwners?: string[]; @@ -23,11 +21,10 @@ export function createDbConfig({ localLists?: LocalList[]; localDbs?: LocalDatabase[]; selected?: SelectedDbItem; - expanded?: ExpandedDbItem[]; } = {}): DbConfig { return { databases: { - remote: { + variantAnalysis: { repositoryLists: remoteLists, owners: remoteOwners, repositories: remoteRepos, @@ -37,7 +34,6 @@ export function createDbConfig({ databases: localDbs, }, }, - expanded, selected, }; } diff --git a/extensions/ql-vscode/test/factories/db-item-factories.ts b/extensions/ql-vscode/test/factories/db-item-factories.ts index ff2a97265..99718fc32 100644 --- a/extensions/ql-vscode/test/factories/db-item-factories.ts +++ b/extensions/ql-vscode/test/factories/db-item-factories.ts @@ -8,7 +8,7 @@ import { RemoteOwnerDbItem, RemoteRepoDbItem, RemoteSystemDefinedListDbItem, - RemoteUserDefinedListDbItem, + VariantAnalysisUserDefinedListDbItem, RootLocalDbItem, RootRemoteDbItem, } from "../../src/databases/db-item"; @@ -79,7 +79,7 @@ export function createRemoteSystemDefinedListDbItem({ }; } -export function createRemoteUserDefinedListDbItem({ +export function createVariantAnalysisUserDefinedListDbItem({ expanded = false, selected = false, listName = `list${faker.datatype.number()}`, @@ -93,9 +93,9 @@ export function createRemoteUserDefinedListDbItem({ expanded?: boolean; selected?: boolean; repos?: RemoteRepoDbItem[]; -} = {}): RemoteUserDefinedListDbItem { +} = {}): VariantAnalysisUserDefinedListDbItem { return { - kind: DbItemKind.RemoteUserDefinedList, + kind: DbItemKind.VariantAnalysisUserDefinedList, expanded, selected, listName, diff --git a/extensions/ql-vscode/src/vscode-tests/factories/extension-context.ts b/extensions/ql-vscode/test/factories/extension-context.ts similarity index 82% rename from extensions/ql-vscode/src/vscode-tests/factories/extension-context.ts rename to extensions/ql-vscode/test/factories/extension-context.ts index 52700a68a..d871e77d6 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/extension-context.ts +++ b/extensions/ql-vscode/test/factories/extension-context.ts @@ -1,4 +1,5 @@ import * as vscode from "vscode"; +import { createMockMemento } from "../mock-memento"; /** * Creates a partially implemented mock of vscode.ExtensionContext. @@ -11,10 +12,12 @@ export function createMockExtensionContext({ extensionPath?: string; workspaceStoragePath?: string; globalStoragePath?: string; + workspaceState?: vscode.Memento; }): vscode.ExtensionContext { return { extensionPath, globalStorageUri: vscode.Uri.file(globalStoragePath), storageUri: vscode.Uri.file(workspaceStoragePath), + workspaceState: createMockMemento(), } as any as vscode.ExtensionContext; } diff --git a/extensions/ql-vscode/src/vscode-tests/factories/local-queries/local-query-history-item.ts b/extensions/ql-vscode/test/factories/local-queries/local-query-history-item.ts similarity index 90% rename from extensions/ql-vscode/src/vscode-tests/factories/local-queries/local-query-history-item.ts rename to extensions/ql-vscode/test/factories/local-queries/local-query-history-item.ts index 8d4092c73..157f60f3b 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/local-queries/local-query-history-item.ts +++ b/extensions/ql-vscode/test/factories/local-queries/local-query-history-item.ts @@ -1,12 +1,12 @@ import { faker } from "@faker-js/faker"; -import { InitialQueryInfo, LocalQueryInfo } from "../../../query-results"; +import { InitialQueryInfo, LocalQueryInfo } from "../../../src/query-results"; import { QueryEvaluationInfo, QueryWithResults, -} from "../../../run-queries-shared"; +} from "../../../src/run-queries-shared"; import { CancellationTokenSource } from "vscode"; -import { QueryResultType } from "../../../pure/legacy-messages"; -import { QueryMetadata } from "../../../pure/interface-types"; +import { QueryResultType } from "../../../src/pure/legacy-messages"; +import { QueryMetadata } from "../../../src/pure/interface-types"; export function createMockLocalQueryInfo({ startTime = new Date(), diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/repository.ts b/extensions/ql-vscode/test/factories/remote-queries/gh-api/repository.ts similarity index 89% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/repository.ts rename to extensions/ql-vscode/test/factories/remote-queries/gh-api/repository.ts index 44f4f9b8e..07cbfae50 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/repository.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/gh-api/repository.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { Repository, RepositoryWithMetadata, -} from "../../../../remote-queries/gh-api/repository"; +} from "../../../../src/remote-queries/gh-api/repository"; export function createMockRepository(name = faker.random.word()): Repository { return { diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/scanned-repositories.ts b/extensions/ql-vscode/test/factories/remote-queries/gh-api/scanned-repositories.ts similarity index 93% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/scanned-repositories.ts rename to extensions/ql-vscode/test/factories/remote-queries/gh-api/scanned-repositories.ts index 14da8a069..84e5819c5 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/scanned-repositories.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/gh-api/scanned-repositories.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { VariantAnalysisRepoStatus, VariantAnalysisScannedRepository, -} from "../../../../remote-queries/gh-api/variant-analysis"; +} from "../../../../src/remote-queries/gh-api/variant-analysis"; import { createMockRepositoryWithMetadata } from "./repository"; export function createMockScannedRepo( diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/skipped-repositories.ts b/extensions/ql-vscode/test/factories/remote-queries/gh-api/skipped-repositories.ts similarity index 94% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/skipped-repositories.ts rename to extensions/ql-vscode/test/factories/remote-queries/gh-api/skipped-repositories.ts index 36f06b1e6..4ec66e180 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/skipped-repositories.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/gh-api/skipped-repositories.ts @@ -3,7 +3,7 @@ import { VariantAnalysisNotFoundRepositoryGroup, VariantAnalysisSkippedRepositories, VariantAnalysisSkippedRepositoryGroup, -} from "../../../../remote-queries/gh-api/variant-analysis"; +} from "../../../../src/remote-queries/gh-api/variant-analysis"; import { createMockRepositoryWithMetadata } from "./repository"; export function createMockSkippedRepos(): VariantAnalysisSkippedRepositories { diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/variant-analysis-api-response.ts b/extensions/ql-vscode/test/factories/remote-queries/gh-api/variant-analysis-api-response.ts similarity index 91% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/variant-analysis-api-response.ts rename to extensions/ql-vscode/test/factories/remote-queries/gh-api/variant-analysis-api-response.ts index cde28893a..9ae585ea6 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/variant-analysis-api-response.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/gh-api/variant-analysis-api-response.ts @@ -4,8 +4,8 @@ import { VariantAnalysisScannedRepository, VariantAnalysisSkippedRepositories, VariantAnalysisStatus, -} from "../../../../remote-queries/gh-api/variant-analysis"; -import { VariantAnalysisQueryLanguage } from "../../../../remote-queries/shared/variant-analysis"; +} from "../../../../src/remote-queries/gh-api/variant-analysis"; +import { VariantAnalysisQueryLanguage } from "../../../../src/remote-queries/shared/variant-analysis"; import { createMockScannedRepos } from "./scanned-repositories"; import { createMockSkippedRepos } from "./skipped-repositories"; import { createMockRepository } from "./repository"; diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/variant-analysis-repo-task.ts b/extensions/ql-vscode/test/factories/remote-queries/gh-api/variant-analysis-repo-task.ts similarity index 70% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/variant-analysis-repo-task.ts rename to extensions/ql-vscode/test/factories/remote-queries/gh-api/variant-analysis-repo-task.ts index 154da43f3..c06661941 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/gh-api/variant-analysis-repo-task.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/gh-api/variant-analysis-repo-task.ts @@ -1,6 +1,6 @@ import { faker } from "@faker-js/faker"; -import { VariantAnalysisRepoTask } from "../../../../remote-queries/gh-api/variant-analysis"; -import { VariantAnalysisRepoStatus } from "../../../../remote-queries/shared/variant-analysis"; +import { VariantAnalysisRepoTask } from "../../../../src/remote-queries/gh-api/variant-analysis"; +import { VariantAnalysisRepoStatus } from "../../../../src/remote-queries/shared/variant-analysis"; import { createMockRepository } from "./repository"; export function createMockVariantAnalysisRepoTask(): VariantAnalysisRepoTask { diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/remote-query-history-item.ts b/extensions/ql-vscode/test/factories/remote-queries/remote-query-history-item.ts similarity index 87% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/remote-query-history-item.ts rename to extensions/ql-vscode/test/factories/remote-queries/remote-query-history-item.ts index 52d61edb0..985199047 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/remote-query-history-item.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/remote-query-history-item.ts @@ -1,6 +1,6 @@ import { nanoid } from "nanoid"; -import { RemoteQueryHistoryItem } from "../../../remote-queries/remote-query-history-item"; -import { QueryStatus } from "../../../query-status"; +import { RemoteQueryHistoryItem } from "../../../src/remote-queries/remote-query-history-item"; +import { QueryStatus } from "../../../src/query-status"; export function createMockRemoteQueryHistoryItem({ date = new Date("2022-01-01T00:00:00.000Z"), diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/AnalysisMessage.ts b/extensions/ql-vscode/test/factories/remote-queries/shared/AnalysisMessage.ts similarity index 65% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/AnalysisMessage.ts rename to extensions/ql-vscode/test/factories/remote-queries/shared/AnalysisMessage.ts index 7fa63bf96..cdd462908 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/AnalysisMessage.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/shared/AnalysisMessage.ts @@ -1,4 +1,4 @@ -import { AnalysisMessage } from "../../../../remote-queries/shared/analysis-result"; +import { AnalysisMessage } from "../../../../src/remote-queries/shared/analysis-result"; export function createMockAnalysisMessage(): AnalysisMessage { return { diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/CodeFlow.ts b/extensions/ql-vscode/test/factories/remote-queries/shared/CodeFlow.ts similarity index 85% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/CodeFlow.ts rename to extensions/ql-vscode/test/factories/remote-queries/shared/CodeFlow.ts index 1949ef14b..eb1713d8e 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/CodeFlow.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/shared/CodeFlow.ts @@ -1,4 +1,4 @@ -import { CodeFlow } from "../../../../remote-queries/shared/analysis-result"; +import { CodeFlow } from "../../../../src/remote-queries/shared/analysis-result"; import { createMockAnalysisMessage } from "./AnalysisMessage"; export function createMockCodeFlows(): CodeFlow[] { diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/repository.ts b/extensions/ql-vscode/test/factories/remote-queries/shared/repository.ts similarity index 89% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/repository.ts rename to extensions/ql-vscode/test/factories/remote-queries/shared/repository.ts index bef5476de..7220c99e3 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/repository.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/shared/repository.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { Repository, RepositoryWithMetadata, -} from "../../../../remote-queries/shared/repository"; +} from "../../../../src/remote-queries/shared/repository"; export function createMockRepository(): Repository { return { diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/scanned-repositories.ts b/extensions/ql-vscode/test/factories/remote-queries/shared/scanned-repositories.ts similarity index 94% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/scanned-repositories.ts rename to extensions/ql-vscode/test/factories/remote-queries/shared/scanned-repositories.ts index edb8df102..f78506c5d 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/scanned-repositories.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/shared/scanned-repositories.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { VariantAnalysisRepoStatus, VariantAnalysisScannedRepository, -} from "../../../../remote-queries/shared/variant-analysis"; +} from "../../../../src/remote-queries/shared/variant-analysis"; import { createMockRepositoryWithMetadata } from "./repository"; export function createMockScannedRepo( diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/skipped-repositories.ts b/extensions/ql-vscode/test/factories/remote-queries/shared/skipped-repositories.ts similarity index 94% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/skipped-repositories.ts rename to extensions/ql-vscode/test/factories/remote-queries/shared/skipped-repositories.ts index 56f8e193c..090158f44 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/skipped-repositories.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/shared/skipped-repositories.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { VariantAnalysisSkippedRepositories, VariantAnalysisSkippedRepositoryGroup, -} from "../../../../remote-queries/shared/variant-analysis"; +} from "../../../../src/remote-queries/shared/variant-analysis"; import { createMockRepositoryWithMetadata } from "./repository"; export function createMockSkippedRepos(): VariantAnalysisSkippedRepositories { diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis-repo-tasks.ts b/extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis-repo-tasks.ts similarity index 91% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis-repo-tasks.ts rename to extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis-repo-tasks.ts index c23722281..dc610313c 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis-repo-tasks.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis-repo-tasks.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { VariantAnalysisRepositoryTask, VariantAnalysisRepoStatus, -} from "../../../../remote-queries/shared/variant-analysis"; +} from "../../../../src/remote-queries/shared/variant-analysis"; import { createMockRepositoryWithMetadata } from "./repository"; export function createMockVariantAnalysisRepositoryTask( diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis-submission.ts b/extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis-submission.ts similarity index 89% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis-submission.ts rename to extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis-submission.ts index f0dc8608a..fd12aa271 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis-submission.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis-submission.ts @@ -2,7 +2,7 @@ import { faker } from "@faker-js/faker"; import { VariantAnalysisQueryLanguage, VariantAnalysisSubmission, -} from "../../../../remote-queries/shared/variant-analysis"; +} from "../../../../src/remote-queries/shared/variant-analysis"; export function createMockSubmission(): VariantAnalysisSubmission { return { diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis.ts b/extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis.ts similarity index 95% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis.ts rename to extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis.ts index 580aae5fa..d2fed5dda 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/shared/variant-analysis.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/shared/variant-analysis.ts @@ -5,7 +5,7 @@ import { VariantAnalysisScannedRepository, VariantAnalysisSkippedRepositories, VariantAnalysisStatus, -} from "../../../../remote-queries/shared/variant-analysis"; +} from "../../../../src/remote-queries/shared/variant-analysis"; import { createMockScannedRepos } from "./scanned-repositories"; import { createMockSkippedRepos } from "./skipped-repositories"; import { createMockRepository } from "./repository"; diff --git a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/variant-analysis-history-item.ts b/extensions/ql-vscode/test/factories/remote-queries/variant-analysis-history-item.ts similarity index 77% rename from extensions/ql-vscode/src/vscode-tests/factories/remote-queries/variant-analysis-history-item.ts rename to extensions/ql-vscode/test/factories/remote-queries/variant-analysis-history-item.ts index aadcad576..db8a0361a 100644 --- a/extensions/ql-vscode/src/vscode-tests/factories/remote-queries/variant-analysis-history-item.ts +++ b/extensions/ql-vscode/test/factories/remote-queries/variant-analysis-history-item.ts @@ -1,6 +1,6 @@ -import { VariantAnalysisHistoryItem } from "../../../remote-queries/variant-analysis-history-item"; -import { QueryStatus } from "../../../query-status"; -import { VariantAnalysisStatus } from "../../../remote-queries/shared/variant-analysis"; +import { VariantAnalysisHistoryItem } from "../../../src/remote-queries/variant-analysis-history-item"; +import { QueryStatus } from "../../../src/query-status"; +import { VariantAnalysisStatus } from "../../../src/remote-queries/shared/variant-analysis"; import { createMockVariantAnalysis } from "./shared/variant-analysis"; export function createMockVariantAnalysisHistoryItem({ diff --git a/extensions/ql-vscode/test/matchers/toEqualPath.ts b/extensions/ql-vscode/test/matchers/toEqualPath.ts new file mode 100644 index 000000000..32264c884 --- /dev/null +++ b/extensions/ql-vscode/test/matchers/toEqualPath.ts @@ -0,0 +1,53 @@ +import { expect } from "@jest/globals"; +import type { MatcherFunction } from "expect"; +import { pathsEqual } from "../../src/pure/files"; + +// eslint-disable-next-line func-style -- We need to have access to this and specify the type of the function +const toEqualPath: MatcherFunction<[expectedPath: unknown]> = function ( + actual, + expectedPath, +) { + if (typeof actual !== "string" || typeof expectedPath !== "string") { + throw new Error("These must be of type string!"); + } + + const pass = pathsEqual(actual, expectedPath, process.platform); + if (pass) { + return { + message: () => + // eslint-disable-next-line @typescript-eslint/no-invalid-this + `expected ${this.utils.printReceived( + actual, + // eslint-disable-next-line @typescript-eslint/no-invalid-this + )} to equal path ${this.utils.printExpected(expectedPath)}`, + pass: true, + }; + } else { + return { + message: () => + // eslint-disable-next-line @typescript-eslint/no-invalid-this + `expected ${this.utils.printReceived( + actual, + // eslint-disable-next-line @typescript-eslint/no-invalid-this + )} to equal path ${this.utils.printExpected(expectedPath)}`, + pass: false, + }; + } +}; + +expect.extend({ + toEqualPath, +}); + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace -- We need to extend this global declaration + namespace jest { + interface AsymmetricMatchers { + toEqualPath(expectedPath: string): void; + } + + interface Matchers { + toEqualPath(expectedPath: string): R; + } + } +} diff --git a/extensions/ql-vscode/test/mock-memento.ts b/extensions/ql-vscode/test/mock-memento.ts new file mode 100644 index 000000000..88cbef8d4 --- /dev/null +++ b/extensions/ql-vscode/test/mock-memento.ts @@ -0,0 +1,28 @@ +import { Memento } from "../src/common/memento"; + +export function createMockMemento(): Memento { + return new MockMemento(); +} + +export class MockMemento implements Memento { + private readonly map: Map; + + constructor() { + this.map = new Map(); + } + + public keys(): readonly string[] { + return Array.from(this.map.keys()); + } + + public get(key: string): T | undefined; + public get(key: string, defaultValue: T): T; + public get(key: any, defaultValue?: any): T | T | undefined { + return this.map.get(key) || defaultValue; + } + + public update(key: string, value: any): Thenable { + this.map.set(key, value); + return Promise.resolve(); + } +} diff --git a/extensions/ql-vscode/test/pure-tests/databases/config/db-config-store.test.ts b/extensions/ql-vscode/test/pure-tests/databases/config/db-config-store.test.ts deleted file mode 100644 index 1083d6e7e..000000000 --- a/extensions/ql-vscode/test/pure-tests/databases/config/db-config-store.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { ensureDir, remove, pathExists } from "fs-extra"; -import { join } from "path"; -import { DbConfigStore } from "../../../../src/databases/config/db-config-store"; -import { createMockApp } from "../../../__mocks__/appMock"; - -describe("db config store", () => { - const extensionPath = join(__dirname, "../../../.."); - const tempWorkspaceStoragePath = join(__dirname, "test-workspace"); - const testDataStoragePath = join(__dirname, "data"); - - beforeEach(async () => { - await ensureDir(tempWorkspaceStoragePath); - }); - - afterEach(async () => { - await remove(tempWorkspaceStoragePath); - }); - - it("should create a new config if one does not exist", async () => { - const app = createMockApp({ - extensionPath, - workspaceStoragePath: tempWorkspaceStoragePath, - }); - - const configPath = join( - tempWorkspaceStoragePath, - "workspace-databases.json", - ); - - const configStore = new DbConfigStore(app); - await configStore.initialize(); - - expect(await pathExists(configPath)).toBe(true); - - const config = configStore.getConfig().value; - expect(config.databases.remote.repositoryLists).toHaveLength(0); - expect(config.databases.remote.owners).toHaveLength(0); - expect(config.databases.remote.repositories).toHaveLength(0); - expect(config.databases.local.lists).toHaveLength(0); - expect(config.databases.local.databases).toHaveLength(0); - expect(config.selected).toBeUndefined(); - - configStore.dispose(); - }); - - it("should load an existing config", async () => { - const app = createMockApp({ - extensionPath, - workspaceStoragePath: testDataStoragePath, - }); - const configStore = new DbConfigStore(app); - await configStore.initialize(); - - const config = configStore.getConfig().value; - expect(config.databases.remote.repositoryLists).toHaveLength(1); - expect(config.databases.remote.repositoryLists[0]).toEqual({ - name: "repoList1", - repositories: ["foo/bar", "foo/baz"], - }); - expect(config.databases.remote.owners).toHaveLength(0); - expect(config.databases.remote.repositories).toHaveLength(3); - expect(config.databases.remote.repositories).toEqual([ - "owner/repo1", - "owner/repo2", - "owner/repo3", - ]); - expect(config.databases.local.lists).toHaveLength(2); - expect(config.databases.local.lists[0]).toEqual({ - name: "localList1", - databases: [ - { - name: "foo/bar", - dateAdded: 1668096745193, - language: "go", - storagePath: "/path/to/database/", - }, - ], - }); - expect(config.databases.local.databases).toHaveLength(1); - expect(config.databases.local.databases[0]).toEqual({ - name: "example-db", - dateAdded: 1668096927267, - language: "ruby", - storagePath: "/path/to/database/", - }); - expect(config.selected).toEqual({ - kind: "remoteUserDefinedList", - listName: "repoList1", - }); - - configStore.dispose(); - }); - - it("should load an existing config without selected db", async () => { - const testDataStoragePathWithout = join( - __dirname, - "data", - "without-selected", - ); - - const app = createMockApp({ - extensionPath, - workspaceStoragePath: testDataStoragePathWithout, - }); - - const configStore = new DbConfigStore(app); - await configStore.initialize(); - - const config = configStore.getConfig().value; - expect(config.selected).toBeUndefined(); - - configStore.dispose(); - }); - - it("should not allow modification of the config", async () => { - const app = createMockApp({ - extensionPath, - workspaceStoragePath: testDataStoragePath, - }); - const configStore = new DbConfigStore(app); - await configStore.initialize(); - - const config = configStore.getConfig().value; - config.databases.remote.repositoryLists = []; - - const reRetrievedConfig = configStore.getConfig().value; - expect(reRetrievedConfig.databases.remote.repositoryLists).toHaveLength(1); - - configStore.dispose(); - }); - - it("should set codeQLDatabasesExperimental.configError to true when config has error", async () => { - const testDataStoragePathInvalid = join(__dirname, "data", "invalid"); - - const app = createMockApp({ - extensionPath, - workspaceStoragePath: testDataStoragePathInvalid, - }); - const configStore = new DbConfigStore(app); - await configStore.initialize(); - - expect(app.executeCommand).toBeCalledWith( - "setContext", - "codeQLDatabasesExperimental.configError", - true, - ); - configStore.dispose(); - }); - - it("should set codeQLDatabasesExperimental.configError to false when config is valid", async () => { - const app = createMockApp({ - extensionPath, - workspaceStoragePath: testDataStoragePath, - }); - const configStore = new DbConfigStore(app); - await configStore.initialize(); - - expect(app.executeCommand).toBeCalledWith( - "setContext", - "codeQLDatabasesExperimental.configError", - false, - ); - - configStore.dispose(); - }); -}); diff --git a/extensions/ql-vscode/test/pure-tests/databases/db-item-expansion.test.ts b/extensions/ql-vscode/test/pure-tests/databases/db-item-expansion.test.ts deleted file mode 100644 index a29a6655f..000000000 --- a/extensions/ql-vscode/test/pure-tests/databases/db-item-expansion.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - ExpandedDbItem, - ExpandedDbItemKind, -} from "../../../src/databases/config/db-config"; -import { - RemoteUserDefinedListDbItem, - RootRemoteDbItem, -} from "../../../src/databases/db-item"; -import { calculateNewExpandedState } from "../../../src/databases/db-item-expansion"; -import { - createRemoteUserDefinedListDbItem, - createRootRemoteDbItem, -} from "../../factories/db-item-factories"; - -describe("db item expansion", () => { - it("should add an expanded item to an existing list", () => { - const currentExpandedItems: ExpandedDbItem[] = [ - { - kind: ExpandedDbItemKind.RootRemote, - }, - { - kind: ExpandedDbItemKind.RemoteUserDefinedList, - listName: "list1", - }, - ]; - - const dbItem: RemoteUserDefinedListDbItem = - createRemoteUserDefinedListDbItem({ - listName: "list2", - }); - - const newExpandedItems = calculateNewExpandedState( - currentExpandedItems, - dbItem, - true, - ); - - expect(newExpandedItems).toEqual([ - ...currentExpandedItems, - { - kind: ExpandedDbItemKind.RemoteUserDefinedList, - listName: "list2", - }, - ]); - }); - - it("should add an expanded item to an empty list", () => { - const dbItem: RemoteUserDefinedListDbItem = - createRemoteUserDefinedListDbItem({ - listName: "list2", - }); - - const newExpandedItems = calculateNewExpandedState([], dbItem, true); - - expect(newExpandedItems).toEqual([ - { - kind: ExpandedDbItemKind.RemoteUserDefinedList, - listName: "list2", - }, - ]); - }); - - it("should remove a collapsed item from a list", () => { - const currentExpandedItems: ExpandedDbItem[] = [ - { - kind: ExpandedDbItemKind.RootRemote, - }, - { - kind: ExpandedDbItemKind.RemoteUserDefinedList, - listName: "list1", - }, - ]; - - const dbItem: RemoteUserDefinedListDbItem = - createRemoteUserDefinedListDbItem({ - listName: "list1", - }); - - const newExpandedItems = calculateNewExpandedState( - currentExpandedItems, - dbItem, - false, - ); - - expect(newExpandedItems).toEqual([ - { - kind: ExpandedDbItemKind.RootRemote, - }, - ]); - }); - - it("should remove a collapsed item from a list that becomes empty", () => { - const currentExpandedItems: ExpandedDbItem[] = [ - { - kind: ExpandedDbItemKind.RootRemote, - }, - ]; - - const dbItem: RootRemoteDbItem = createRootRemoteDbItem(); - - const newExpandedItems = calculateNewExpandedState( - currentExpandedItems, - dbItem, - false, - ); - - expect(newExpandedItems).toEqual([]); - }); -}); diff --git a/extensions/ql-vscode/test/tsconfig.json b/extensions/ql-vscode/test/tsconfig.json index ead73b331..bbc52dca9 100644 --- a/extensions/ql-vscode/test/tsconfig.json +++ b/extensions/ql-vscode/test/tsconfig.json @@ -1,13 +1,11 @@ { "extends": "../tsconfig.json", - "include": [ - "**/*.ts", - "../src/vscode-tests/factories/db-config-factories.ts" - ], + "include": ["**/*.ts", "../src/**/*.ts"], "exclude": [], "compilerOptions": { "noEmit": true, "resolveJsonModule": true, - "rootDir": ".." + "rootDir": "..", + "jsx": "react" } } diff --git a/extensions/ql-vscode/test/pure-tests/command-lint.test.ts b/extensions/ql-vscode/test/unit-tests/command-lint.test.ts similarity index 98% rename from extensions/ql-vscode/test/pure-tests/command-lint.test.ts rename to extensions/ql-vscode/test/unit-tests/command-lint.test.ts index c1fd4cd5b..01b0c7ce2 100644 --- a/extensions/ql-vscode/test/pure-tests/command-lint.test.ts +++ b/extensions/ql-vscode/test/unit-tests/command-lint.test.ts @@ -34,7 +34,7 @@ describe("commands declared in package.json", () => { commandTitles[command] = title!; } else if ( command.match(/^codeQLDatabases\./) || - command.match(/^codeQLDatabasesExperimental\./) || + command.match(/^codeQLVariantAnalysisRepositories\./) || command.match(/^codeQLQueryHistory\./) || command.match(/^codeQLAstViewer\./) || command.match(/^codeQLEvalLogViewer\./) || diff --git a/extensions/ql-vscode/test/unit-tests/common/github-url-identifier-helper.test.ts b/extensions/ql-vscode/test/unit-tests/common/github-url-identifier-helper.test.ts new file mode 100644 index 000000000..16d24af8d --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/common/github-url-identifier-helper.test.ts @@ -0,0 +1,71 @@ +import { + getNwoFromGitHubUrl, + getOwnerFromGitHubUrl, + isValidGitHubNwo, + isValidGitHubOwner, +} from "../../../src/common/github-url-identifier-helper"; + +describe("github url identifier helper", () => { + describe("valid GitHub Nwo Or Owner method", () => { + it("should return true for valid owner", () => { + expect(isValidGitHubOwner("github")).toBe(true); + }); + it("should return true for valid NWO", () => { + expect(isValidGitHubNwo("github/codeql")).toBe(true); + }); + it("should return false for invalid owner", () => { + expect(isValidGitHubOwner("github/codeql")).toBe(false); + }); + it("should return false for invalid NWO", () => { + expect(isValidGitHubNwo("githubl")).toBe(false); + }); + }); + + describe("getNwoFromGitHubUrl method", () => { + it("should handle invalid urls", () => { + expect(getNwoFromGitHubUrl("")).toBe(undefined); + expect(getNwoFromGitHubUrl("http://github.com/foo/bar")).toBe(undefined); + expect(getNwoFromGitHubUrl("https://ww.github.com/foo/bar")).toBe( + undefined, + ); + expect(getNwoFromGitHubUrl("https://www.github.com/foo")).toBe(undefined); + expect(getNwoFromGitHubUrl("foo")).toBe(undefined); + expect(getNwoFromGitHubUrl("foo/bar")).toBe(undefined); + }); + + it("should handle valid urls", () => { + expect(getNwoFromGitHubUrl("https://github.com/foo/bar")).toBe("foo/bar"); + expect(getNwoFromGitHubUrl("https://www.github.com/foo/bar")).toBe( + "foo/bar", + ); + expect(getNwoFromGitHubUrl("https://github.com/foo/bar/sub/pages")).toBe( + "foo/bar", + ); + }); + }); + + describe("getOwnerFromGitHubUrl method", () => { + it("should handle invalid urls", () => { + expect(getOwnerFromGitHubUrl("")).toBe(undefined); + expect(getOwnerFromGitHubUrl("http://github.com/foo/bar")).toBe( + undefined, + ); + expect(getOwnerFromGitHubUrl("https://ww.github.com/foo/bar")).toBe( + undefined, + ); + expect(getOwnerFromGitHubUrl("foo")).toBe(undefined); + expect(getOwnerFromGitHubUrl("foo/bar")).toBe(undefined); + }); + + it("should handle valid urls", () => { + expect(getOwnerFromGitHubUrl("https://github.com/foo/bar")).toBe("foo"); + expect(getOwnerFromGitHubUrl("https://www.github.com/foo/bar")).toBe( + "foo", + ); + expect( + getOwnerFromGitHubUrl("https://github.com/foo/bar/sub/pages"), + ).toBe("foo"); + expect(getOwnerFromGitHubUrl("https://www.github.com/foo")).toBe("foo"); + }); + }); +}); diff --git a/extensions/ql-vscode/test/pure-tests/evaluator-log-summaries/bad-join-order.jsonl b/extensions/ql-vscode/test/unit-tests/data/evaluator-log-summaries/bad-join-order.jsonl similarity index 100% rename from extensions/ql-vscode/test/pure-tests/evaluator-log-summaries/bad-join-order.jsonl rename to extensions/ql-vscode/test/unit-tests/data/evaluator-log-summaries/bad-join-order.jsonl diff --git a/extensions/ql-vscode/test/pure-tests/evaluator-log-summaries/invalid-header.jsonl b/extensions/ql-vscode/test/unit-tests/data/evaluator-log-summaries/invalid-header.jsonl similarity index 100% rename from extensions/ql-vscode/test/pure-tests/evaluator-log-summaries/invalid-header.jsonl rename to extensions/ql-vscode/test/unit-tests/data/evaluator-log-summaries/invalid-header.jsonl diff --git a/extensions/ql-vscode/test/pure-tests/evaluator-log-summaries/invalid-summary.jsonl b/extensions/ql-vscode/test/unit-tests/data/evaluator-log-summaries/invalid-summary.jsonl similarity index 100% rename from extensions/ql-vscode/test/pure-tests/evaluator-log-summaries/invalid-summary.jsonl rename to extensions/ql-vscode/test/unit-tests/data/evaluator-log-summaries/invalid-summary.jsonl diff --git a/extensions/ql-vscode/test/pure-tests/evaluator-log-summaries/valid-summary.jsonl b/extensions/ql-vscode/test/unit-tests/data/evaluator-log-summaries/valid-summary.jsonl similarity index 100% rename from extensions/ql-vscode/test/pure-tests/evaluator-log-summaries/valid-summary.jsonl rename to extensions/ql-vscode/test/unit-tests/data/evaluator-log-summaries/valid-summary.jsonl diff --git a/extensions/ql-vscode/test/pure-tests/databases/config/data/invalid/workspace-databases.json b/extensions/ql-vscode/test/unit-tests/databases/config/data/invalid/workspace-databases.json similarity index 100% rename from extensions/ql-vscode/test/pure-tests/databases/config/data/invalid/workspace-databases.json rename to extensions/ql-vscode/test/unit-tests/databases/config/data/invalid/workspace-databases.json diff --git a/extensions/ql-vscode/test/pure-tests/databases/config/data/without-selected/workspace-databases.json b/extensions/ql-vscode/test/unit-tests/databases/config/data/without-selected/workspace-databases.json similarity index 81% rename from extensions/ql-vscode/test/pure-tests/databases/config/data/without-selected/workspace-databases.json rename to extensions/ql-vscode/test/unit-tests/databases/config/data/without-selected/workspace-databases.json index 219be9c1b..6ac7db4ad 100644 --- a/extensions/ql-vscode/test/pure-tests/databases/config/data/without-selected/workspace-databases.json +++ b/extensions/ql-vscode/test/unit-tests/databases/config/data/without-selected/workspace-databases.json @@ -1,6 +1,6 @@ { "databases": { - "remote": { + "variantAnalysis": { "repositoryLists": [], "owners": [], "repositories": [] @@ -9,6 +9,5 @@ "lists": [], "databases": [] } - }, - "expanded": [] + } } diff --git a/extensions/ql-vscode/test/pure-tests/databases/config/data/workspace-databases.json b/extensions/ql-vscode/test/unit-tests/databases/config/data/workspace-databases.json similarity index 93% rename from extensions/ql-vscode/test/pure-tests/databases/config/data/workspace-databases.json rename to extensions/ql-vscode/test/unit-tests/databases/config/data/workspace-databases.json index e7f99fc99..3f296f9bd 100644 --- a/extensions/ql-vscode/test/pure-tests/databases/config/data/workspace-databases.json +++ b/extensions/ql-vscode/test/unit-tests/databases/config/data/workspace-databases.json @@ -1,6 +1,6 @@ { "databases": { - "remote": { + "variantAnalysis": { "repositoryLists": [ { "name": "repoList1", @@ -45,9 +45,8 @@ ] } }, - "expanded": [], "selected": { - "kind": "remoteUserDefinedList", + "kind": "variantAnalysisUserDefinedList", "listName": "repoList1" } } diff --git a/extensions/ql-vscode/test/unit-tests/databases/config/db-config-store.test.ts b/extensions/ql-vscode/test/unit-tests/databases/config/db-config-store.test.ts new file mode 100644 index 000000000..e5cf783e7 --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/databases/config/db-config-store.test.ts @@ -0,0 +1,778 @@ +import { ensureDir, remove, pathExists, writeJSON, readJSON } from "fs-extra"; +import { join } from "path"; +import { App } from "../../../../src/common/app"; +import { + DbConfig, + SelectedDbItem, + SelectedDbItemKind, +} from "../../../../src/databases/config/db-config"; +import { DbConfigStore } from "../../../../src/databases/config/db-config-store"; +import { + createDbConfig, + createLocalDbConfigItem, +} from "../../../factories/db-config-factories"; +import { + createLocalDatabaseDbItem, + createLocalListDbItem, + createRemoteOwnerDbItem, + createRemoteRepoDbItem, + createVariantAnalysisUserDefinedListDbItem, +} from "../../../factories/db-item-factories"; +import { createMockApp } from "../../../__mocks__/appMock"; + +describe("db config store", () => { + const extensionPath = join(__dirname, "../../../.."); + const tempWorkspaceStoragePath = join(__dirname, "test-workspace"); + const testDataStoragePath = join(__dirname, "data"); + + beforeEach(async () => { + await ensureDir(tempWorkspaceStoragePath); + }); + + afterEach(async () => { + await remove(tempWorkspaceStoragePath); + }); + + describe("initialize", () => { + it("should create a new config if one does not exist", async () => { + const app = createMockApp({ + extensionPath, + workspaceStoragePath: tempWorkspaceStoragePath, + }); + + const configPath = join( + tempWorkspaceStoragePath, + "workspace-databases.json", + ); + + const configStore = new DbConfigStore(app, false); + await configStore.initialize(); + + expect(await pathExists(configPath)).toBe(true); + + const config = configStore.getConfig().value; + expect(config.databases.variantAnalysis.repositoryLists).toHaveLength(0); + expect(config.databases.variantAnalysis.owners).toHaveLength(0); + expect(config.databases.variantAnalysis.repositories).toHaveLength(0); + expect(config.databases.local.lists).toHaveLength(0); + expect(config.databases.local.databases).toHaveLength(0); + expect(config.selected).toBeUndefined(); + + configStore.dispose(); + }); + + it("should load an existing config", async () => { + const app = createMockApp({ + extensionPath, + workspaceStoragePath: testDataStoragePath, + }); + const configStore = new DbConfigStore(app, false); + await configStore.initialize(); + + const config = configStore.getConfig().value; + expect(config.databases.variantAnalysis.repositoryLists).toHaveLength(1); + expect(config.databases.variantAnalysis.repositoryLists[0]).toEqual({ + name: "repoList1", + repositories: ["foo/bar", "foo/baz"], + }); + expect(config.databases.variantAnalysis.owners).toHaveLength(0); + expect(config.databases.variantAnalysis.repositories).toHaveLength(3); + expect(config.databases.variantAnalysis.repositories).toEqual([ + "owner/repo1", + "owner/repo2", + "owner/repo3", + ]); + expect(config.databases.local.lists).toHaveLength(2); + expect(config.databases.local.lists[0]).toEqual({ + name: "localList1", + databases: [ + { + name: "foo/bar", + dateAdded: 1668096745193, + language: "go", + storagePath: "/path/to/database/", + }, + ], + }); + expect(config.databases.local.databases).toHaveLength(1); + expect(config.databases.local.databases[0]).toEqual({ + name: "example-db", + dateAdded: 1668096927267, + language: "ruby", + storagePath: "/path/to/database/", + }); + expect(config.selected).toEqual({ + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: "repoList1", + }); + + configStore.dispose(); + }); + + it("should load an existing config without selected db", async () => { + const testDataStoragePathWithout = join( + __dirname, + "data", + "without-selected", + ); + + const app = createMockApp({ + extensionPath, + workspaceStoragePath: testDataStoragePathWithout, + }); + + const configStore = new DbConfigStore(app, false); + await configStore.initialize(); + + const config = configStore.getConfig().value; + expect(config.selected).toBeUndefined(); + + configStore.dispose(); + }); + + it("should not allow modification of the config", async () => { + const app = createMockApp({ + extensionPath, + workspaceStoragePath: testDataStoragePath, + }); + const configStore = new DbConfigStore(app, false); + await configStore.initialize(); + + const config = configStore.getConfig().value; + config.databases.variantAnalysis.repositoryLists = []; + + const reRetrievedConfig = configStore.getConfig().value; + expect( + reRetrievedConfig.databases.variantAnalysis.repositoryLists, + ).toHaveLength(1); + + configStore.dispose(); + }); + + it("should set codeQLVariantAnalysisRepositories.configError to true when config has error", async () => { + const testDataStoragePathInvalid = join(__dirname, "data", "invalid"); + + const app = createMockApp({ + extensionPath, + workspaceStoragePath: testDataStoragePathInvalid, + }); + const configStore = new DbConfigStore(app, false); + await configStore.initialize(); + + expect(app.executeCommand).toBeCalledWith( + "setContext", + "codeQLVariantAnalysisRepositories.configError", + true, + ); + configStore.dispose(); + }); + + it("should set codeQLVariantAnalysisRepositories.configError to false when config is valid", async () => { + const app = createMockApp({ + extensionPath, + workspaceStoragePath: testDataStoragePath, + }); + const configStore = new DbConfigStore(app, false); + await configStore.initialize(); + + expect(app.executeCommand).toBeCalledWith( + "setContext", + "codeQLVariantAnalysisRepositories.configError", + false, + ); + + configStore.dispose(); + }); + }); + + describe("db and list addition", () => { + let app: App; + let configPath: string; + + beforeEach(async () => { + app = createMockApp({ + extensionPath, + workspaceStoragePath: tempWorkspaceStoragePath, + }); + + configPath = join(tempWorkspaceStoragePath, "workspace-databases.json"); + }); + + it("should add a remote repository", async () => { + // Initial set up + const dbConfig = createDbConfig(); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Add + await configStore.addRemoteRepo("repo1"); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedRemoteDbs = updatedDbConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositories).toHaveLength(1); + expect(updatedRemoteDbs.repositories).toEqual(["repo1"]); + + configStore.dispose(); + }); + + it("should add a remote repository to the correct list", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: [], + }, + ], + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Add + await configStore.addRemoteRepo("repo1", "list1"); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedRemoteDbs = updatedDbConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositories).toHaveLength(0); + expect(updatedRemoteDbs.repositoryLists).toHaveLength(1); + expect(updatedRemoteDbs.repositoryLists[0]).toEqual({ + name: "list1", + repositories: ["repo1"], + }); + + configStore.dispose(); + }); + + it("should add a remote owner", async () => { + // Initial set up + const dbConfig = createDbConfig(); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Add + await configStore.addRemoteOwner("owner1"); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedRemoteDbs = updatedDbConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.owners).toHaveLength(1); + expect(updatedRemoteDbs.owners).toEqual(["owner1"]); + + configStore.dispose(); + }); + + it("should add a local list", async () => { + // Initial set up + const dbConfig = createDbConfig(); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Add + await configStore.addLocalList("list1"); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedLocalDbs = updatedDbConfig.databases.local; + expect(updatedLocalDbs.lists).toHaveLength(1); + expect(updatedLocalDbs.lists[0].name).toEqual("list1"); + + configStore.dispose(); + }); + + it("should add a remote list", async () => { + // Initial set up + const dbConfig = createDbConfig(); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Add + await configStore.addRemoteList("list1"); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedRemoteDbs = updatedDbConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositoryLists).toHaveLength(1); + expect(updatedRemoteDbs.repositoryLists[0].name).toEqual("list1"); + + configStore.dispose(); + }); + }); + + describe("db and list renaming", () => { + let app: App; + let configPath: string; + + beforeEach(async () => { + app = createMockApp({ + extensionPath, + workspaceStoragePath: tempWorkspaceStoragePath, + }); + + configPath = join(tempWorkspaceStoragePath, "workspace-databases.json"); + }); + + it("should allow renaming a remote list", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: ["owner/repo1", "owner/repo2"], + }, + ], + selected: { + kind: SelectedDbItemKind.VariantAnalysisRepository, + repositoryName: "owner/repo2", + listName: "list1", + }, + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Rename + const currentDbItem = createVariantAnalysisUserDefinedListDbItem({ + listName: "list1", + }); + await configStore.renameRemoteList(currentDbItem, "listRenamed"); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedRemoteDbs = updatedDbConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositoryLists).toHaveLength(1); + expect(updatedRemoteDbs.repositoryLists[0].name).toEqual("listRenamed"); + + expect(updatedDbConfig.selected).toEqual({ + kind: SelectedDbItemKind.VariantAnalysisRepository, + repositoryName: "owner/repo2", + listName: "listRenamed", + }); + + configStore.dispose(); + }); + + it("should allow renaming a local list", async () => { + // Initial set up + const dbConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [ + createLocalDbConfigItem(), + createLocalDbConfigItem(), + createLocalDbConfigItem(), + ], + }, + ], + selected: { + kind: SelectedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Rename + const currentDbItem = createLocalListDbItem({ + listName: "list1", + }); + await configStore.renameLocalList(currentDbItem, "listRenamed"); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedLocalDbs = updatedDbConfig.databases.local; + expect(updatedLocalDbs.lists).toHaveLength(1); + expect(updatedLocalDbs.lists[0].name).toEqual("listRenamed"); + + expect(updatedDbConfig.selected).toEqual({ + kind: SelectedDbItemKind.LocalUserDefinedList, + listName: "listRenamed", + }); + + configStore.dispose(); + }); + + it("should allow renaming of a local db", async () => { + // Initial set up + const dbConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + createLocalDbConfigItem({ name: "db3" }), + ], + }, + ], + selected: { + kind: SelectedDbItemKind.LocalDatabase, + databaseName: "db1", + listName: "list1", + }, + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Rename + const currentDbItem = createLocalDatabaseDbItem({ + databaseName: "db1", + }); + await configStore.renameLocalDb(currentDbItem, "dbRenamed", "list1"); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedLocalDbs = updatedDbConfig.databases.local; + expect(updatedLocalDbs.lists).toHaveLength(1); + expect(updatedLocalDbs.lists[0].name).toEqual("list1"); + expect(updatedLocalDbs.lists[0].databases.length).toEqual(3); + expect(updatedLocalDbs.lists[0].databases[0].name).toEqual("dbRenamed"); + expect(updatedDbConfig.selected).toEqual({ + kind: SelectedDbItemKind.LocalDatabase, + databaseName: "dbRenamed", + listName: "list1", + }); + + configStore.dispose(); + }); + + it("should throw if the name of a list is taken", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: ["owner/repo1", "owner/repo2"], + }, + { + name: "list2", + repositories: ["owner/repo1", "owner/repo2"], + }, + ], + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Rename + const currentDbItem = createVariantAnalysisUserDefinedListDbItem({ + listName: "list1", + }); + await expect( + configStore.renameRemoteList(currentDbItem, "list2"), + ).rejects.toThrow(`A remote list with the name 'list2' already exists`); + + configStore.dispose(); + }); + }); + + describe("db and list deletion", () => { + let app: App; + let configPath: string; + + beforeEach(async () => { + app = createMockApp({ + extensionPath, + workspaceStoragePath: tempWorkspaceStoragePath, + }); + + configPath = join(tempWorkspaceStoragePath, "workspace-databases.json"); + }); + + it("should remove a single db item", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteOwners: ["owner1", "owner2"], + selected: { + kind: SelectedDbItemKind.VariantAnalysisOwner, + ownerName: "owner1", + }, + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Remove + const currentDbItem = createRemoteOwnerDbItem({ + ownerName: "owner1", + }); + await configStore.removeDbItem(currentDbItem); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedRemoteDbs = updatedDbConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.owners).toHaveLength(1); + expect(updatedRemoteDbs.owners[0]).toEqual("owner2"); + + expect(updatedDbConfig.selected).toEqual(undefined); + + configStore.dispose(); + }); + + it("should remove a list db item", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: ["owner/repo1", "owner/repo2"], + }, + ], + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: "list1", + }, + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Remove + const currentDbItem = createVariantAnalysisUserDefinedListDbItem({ + listName: "list1", + }); + await configStore.removeDbItem(currentDbItem); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedRemoteDbs = updatedDbConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositoryLists).toHaveLength(0); + + expect(updatedDbConfig.selected).toEqual(undefined); + + configStore.dispose(); + }); + + it("should remove a db item in a list", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: ["owner/repo1", "owner/repo2"], + }, + ], + selected: { + kind: SelectedDbItemKind.VariantAnalysisRepository, + repositoryName: "owner/repo1", + listName: "list1", + }, + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Remove + const currentDbItem = createRemoteRepoDbItem({ + repoFullName: "owner/repo1", + parentListName: "list1", + }); + await configStore.removeDbItem(currentDbItem); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + const updatedRemoteDbs = updatedDbConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositoryLists[0].repositories).toHaveLength(1); + expect(updatedRemoteDbs.repositoryLists[0].repositories[0]).toEqual( + "owner/repo2", + ); + + expect(updatedDbConfig.selected).toEqual(undefined); + + configStore.dispose(); + }); + }); + + describe("set selected item", () => { + let app: App; + let configPath: string; + + beforeEach(async () => { + app = createMockApp({ + extensionPath, + workspaceStoragePath: tempWorkspaceStoragePath, + }); + + configPath = join(tempWorkspaceStoragePath, "workspace-databases.json"); + }); + + it("should set the selected item", async () => { + // Initial set up + const configStore = new DbConfigStore(app, false); + await configStore.initialize(); + + // Set selected + const selectedItem: SelectedDbItem = { + kind: SelectedDbItemKind.VariantAnalysisOwner, + ownerName: "owner2", + }; + + await configStore.setSelectedDbItem(selectedItem); + + // Read the config file + const updatedDbConfig = (await readJSON(configPath)) as DbConfig; + + // Check that the config file has been updated + expect(updatedDbConfig.selected).toEqual(selectedItem); + + configStore.dispose(); + }); + }); + + describe("existence checks", () => { + let app: App; + let configPath: string; + + beforeEach(async () => { + app = createMockApp({ + extensionPath, + workspaceStoragePath: tempWorkspaceStoragePath, + }); + + configPath = join(tempWorkspaceStoragePath, "workspace-databases.json"); + }); + + it("should return true if a remote owner exists", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteOwners: ["owner1", "owner2"], + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Check + const doesExist = await configStore.doesRemoteOwnerExist("owner1"); + expect(doesExist).toEqual(true); + + configStore.dispose(); + }); + + it("should return true if a remote list exists", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: ["owner/repo1", "owner/repo2"], + }, + ], + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Check + const doesExist = await configStore.doesRemoteListExist("list1"); + expect(doesExist).toEqual(true); + + configStore.dispose(); + }); + + it("should return true if a remote db exists", async () => { + // Initial set up + const dbConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: ["owner/repo1", "owner/repo2"], + }, + ], + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Check + const doesExist = await configStore.doesRemoteDbExist( + "owner/repo1", + "list1", + ); + expect(doesExist).toEqual(true); + + configStore.dispose(); + }); + + it("should return true if a local db and local list exists", async () => { + // Initial set up + const dbConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [createLocalDbConfigItem({ name: "db1" })], + }, + ], + }); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Check + const doesDbExist = await configStore.doesLocalDbExist("db1", "list1"); + expect(doesDbExist).toEqual(true); + const doesListExist = await configStore.doesLocalListExist("list1"); + expect(doesListExist).toEqual(true); + + configStore.dispose(); + }); + + it("should return false if items do not exist", async () => { + // Initial set up + const dbConfig = createDbConfig({}); + + const configStore = await initializeConfig(dbConfig, configPath, app); + + // Check + const doesLocalDbExist = await configStore.doesLocalDbExist( + "db1", + "list1", + ); + expect(doesLocalDbExist).toEqual(false); + const doesLocalListExist = await configStore.doesLocalListExist("list1"); + expect(doesLocalListExist).toEqual(false); + const doesRemoteDbExist = await configStore.doesRemoteDbExist( + "db1", + "list1", + ); + expect(doesRemoteDbExist).toEqual(false); + const doesRemoteListExist = await configStore.doesRemoteListExist( + "list1", + ); + expect(doesRemoteListExist).toEqual(false); + const doesRemoteOwnerExist = await configStore.doesRemoteOwnerExist( + "owner1", + ); + expect(doesRemoteOwnerExist).toEqual(false); + + configStore.dispose(); + }); + }); + + async function initializeConfig( + dbConfig: DbConfig, + configPath: string, + app: App, + ): Promise { + await writeJSON(configPath, dbConfig); + + const configStore = new DbConfigStore(app, false); + await configStore.initialize(); + + return configStore; + } +}); diff --git a/extensions/ql-vscode/test/pure-tests/databases/config/db-config-validator.test.ts b/extensions/ql-vscode/test/unit-tests/databases/config/db-config-validator.test.ts similarity index 95% rename from extensions/ql-vscode/test/pure-tests/databases/config/db-config-validator.test.ts rename to extensions/ql-vscode/test/unit-tests/databases/config/db-config-validator.test.ts index 6425ac068..3ae7da6e2 100644 --- a/extensions/ql-vscode/test/pure-tests/databases/config/db-config-validator.test.ts +++ b/extensions/ql-vscode/test/unit-tests/databases/config/db-config-validator.test.ts @@ -5,7 +5,7 @@ import { DbConfigValidationErrorKind } from "../../../../src/databases/db-valida import { createDbConfig, createLocalDbConfigItem, -} from "../../../../src/vscode-tests/factories/db-config-factories"; +} from "../../../factories/db-config-factories"; describe("db config validation", () => { const extensionPath = join(__dirname, "../../../.."); @@ -16,7 +16,7 @@ describe("db config validation", () => { // like to make sure validation errors are highlighted. const dbConfig = { databases: { - remote: { + variantAnalysis: { repositoryLists: [ { name: "repoList1", @@ -27,7 +27,6 @@ describe("db config validation", () => { somethingElse: "bar", }, }, - expanded: [], } as any as DbConfig; const validationOutput = configValidator.validate(dbConfig); @@ -40,11 +39,12 @@ describe("db config validation", () => { }); expect(validationOutput[1]).toEqual({ kind: DbConfigValidationErrorKind.InvalidConfig, - message: "/databases/remote must have required property 'owners'", + message: + "/databases/variantAnalysis must have required property 'owners'", }); expect(validationOutput[2]).toEqual({ kind: DbConfigValidationErrorKind.InvalidConfig, - message: "/databases/remote must NOT have additional properties", + message: "/databases/variantAnalysis must NOT have additional properties", }); }); diff --git a/extensions/ql-vscode/test/unit-tests/databases/config/db-config.test.ts b/extensions/ql-vscode/test/unit-tests/databases/config/db-config.test.ts new file mode 100644 index 000000000..52abaed54 --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/databases/config/db-config.test.ts @@ -0,0 +1,774 @@ +import { + LocalList, + RemoteRepositoryList, + removeLocalDb, + removeLocalList, + removeRemoteList, + removeRemoteOwner, + removeRemoteRepo, + renameLocalDb, + renameLocalList, + renameRemoteList, + SelectedDbItemKind, +} from "../../../../src/databases/config/db-config"; +import { + createDbConfig, + createLocalDbConfigItem, +} from "../../../factories/db-config-factories"; + +describe("db config", () => { + describe("renameLocalList", () => { + it("should rename a local list", () => { + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [], + }, + { + name: "list2", + databases: [], + }, + ], + }); + + const updatedConfig = renameLocalList( + originalConfig, + "list1", + "listRenamed", + ); + + expect(updatedConfig.databases.local.lists).toEqual([ + { + name: "listRenamed", + databases: [], + }, + { + name: "list2", + databases: [], + }, + ]); + }); + + it("should rename a selected local list", () => { + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [], + }, + { + name: "list2", + databases: [], + }, + ], + selected: { + kind: SelectedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + }); + + const updatedConfig = renameLocalList( + originalConfig, + "list1", + "listRenamed", + ); + + expect(updatedConfig.databases.local.lists).toEqual([ + { + name: "listRenamed", + databases: [], + }, + { + name: "list2", + databases: [], + }, + ]); + + expect(updatedConfig.selected).toEqual({ + kind: SelectedDbItemKind.LocalUserDefinedList, + listName: "listRenamed", + }); + }); + + it("should rename a local list with a db that is selected", () => { + const selectedLocalDb = createLocalDbConfigItem(); + const list1: LocalList = { + name: "list1", + databases: [ + createLocalDbConfigItem(), + selectedLocalDb, + createLocalDbConfigItem(), + ], + }; + const list2: LocalList = { + name: "list2", + databases: [], + }; + + const originalConfig = createDbConfig({ + localLists: [list1, list2], + selected: { + kind: SelectedDbItemKind.LocalDatabase, + databaseName: selectedLocalDb.name, + listName: list1.name, + }, + }); + + const updatedConfig = renameLocalList( + originalConfig, + list1.name, + "listRenamed", + ); + + expect(updatedConfig.databases.local.lists.length).toEqual(2); + expect(updatedConfig.databases.local.lists[0]).toEqual({ + ...list1, + name: "listRenamed", + }); + expect(updatedConfig.databases.local.lists[1]).toEqual(list2); + + expect(updatedConfig.selected).toEqual({ + kind: SelectedDbItemKind.LocalDatabase, + databaseName: selectedLocalDb.name, + listName: "listRenamed", + }); + }); + }); + + describe("renameRemoteList", () => { + it("should rename a remote list", () => { + const originalConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: [], + }, + { + name: "list2", + repositories: [], + }, + ], + }); + + const updatedConfig = renameRemoteList( + originalConfig, + "list1", + "listRenamed", + ); + + expect(updatedConfig.databases.variantAnalysis.repositoryLists).toEqual([ + { + name: "listRenamed", + repositories: [], + }, + { + name: "list2", + repositories: [], + }, + ]); + }); + + it("should rename a selected remote list", () => { + const originalConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: [], + }, + { + name: "list2", + repositories: [], + }, + ], + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: "list1", + }, + }); + + const updatedConfig = renameRemoteList( + originalConfig, + "list1", + "listRenamed", + ); + + expect(updatedConfig.databases.variantAnalysis.repositoryLists).toEqual([ + { + name: "listRenamed", + repositories: [], + }, + { + name: "list2", + repositories: [], + }, + ]); + + expect(updatedConfig.selected).toEqual({ + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: "listRenamed", + }); + }); + + it("should rename a remote list with a db that is selected", () => { + const selectedRemoteRepo = "owner/repo2"; + const originalConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: ["owner1/repo1", selectedRemoteRepo, "owner1/repo3"], + }, + { + name: "list2", + repositories: [], + }, + ], + selected: { + kind: SelectedDbItemKind.VariantAnalysisRepository, + repositoryName: selectedRemoteRepo, + listName: "list1", + }, + }); + + const updatedConfig = renameRemoteList( + originalConfig, + "list1", + "listRenamed", + ); + const updatedRepositoryLists = + updatedConfig.databases.variantAnalysis.repositoryLists; + + expect(updatedRepositoryLists.length).toEqual(2); + expect(updatedRepositoryLists[0]).toEqual({ + ...originalConfig.databases.variantAnalysis.repositoryLists[0], + name: "listRenamed", + }); + expect(updatedRepositoryLists[1]).toEqual( + originalConfig.databases.variantAnalysis.repositoryLists[1], + ); + + expect(updatedConfig.selected).toEqual({ + kind: SelectedDbItemKind.VariantAnalysisRepository, + repositoryName: selectedRemoteRepo, + listName: "listRenamed", + }); + }); + }); + + describe("renameLocalDb", () => { + it("should rename a local db", () => { + const db1 = createLocalDbConfigItem({ name: "db1" }); + const db2 = createLocalDbConfigItem({ name: "db2" }); + + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + ], + }, + ], + localDbs: [db1, db2], + }); + + const updatedConfig = renameLocalDb(originalConfig, "db1", "dbRenamed"); + + const updatedLocalDbs = updatedConfig.databases.local; + const originalLocalDbs = originalConfig.databases.local; + + expect(updatedLocalDbs.lists).toEqual(originalLocalDbs.lists); + expect(updatedLocalDbs.databases.length).toEqual(2); + expect(updatedLocalDbs.databases[0]).toEqual({ + ...db1, + name: "dbRenamed", + }); + expect(updatedLocalDbs.databases[1]).toEqual(db2); + }); + + it("should rename a local db inside a list", () => { + const db1List1 = createLocalDbConfigItem({ name: "db1" }); + const db2List1 = createLocalDbConfigItem({ name: "db2" }); + + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [db1List1, db2List1], + }, + { + name: "list2", + databases: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + ], + }, + ], + localDbs: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + ], + }); + + const updatedConfig = renameLocalDb( + originalConfig, + db1List1.name, + "dbRenamed", + "list1", + ); + + const updatedLocalDbs = updatedConfig.databases.local; + const originalLocalDbs = originalConfig.databases.local; + expect(updatedLocalDbs.databases).toEqual(originalLocalDbs.databases); + expect(updatedLocalDbs.lists.length).toEqual(2); + expect(updatedLocalDbs.lists[0].databases.length).toEqual(2); + expect(updatedLocalDbs.lists[0].databases[0]).toEqual({ + ...db1List1, + name: "dbRenamed", + }); + expect(updatedLocalDbs.lists[0].databases[1]).toEqual(db2List1); + expect(updatedLocalDbs.lists[1]).toEqual(originalLocalDbs.lists[1]); + }); + + it("should rename a local db that is selected", () => { + const db1 = createLocalDbConfigItem({ name: "db1" }); + const db2 = createLocalDbConfigItem({ name: "db2" }); + + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + ], + }, + ], + localDbs: [db1, db2], + selected: { + kind: SelectedDbItemKind.LocalDatabase, + databaseName: "db1", + }, + }); + + const updatedConfig = renameLocalDb(originalConfig, "db1", "dbRenamed"); + + const updatedLocalDbs = updatedConfig.databases.local; + const originalLocalDbs = originalConfig.databases.local; + + expect(updatedLocalDbs.lists).toEqual(originalLocalDbs.lists); + expect(updatedLocalDbs.databases.length).toEqual(2); + expect(updatedLocalDbs.databases[0]).toEqual({ + ...db1, + name: "dbRenamed", + }); + expect(updatedLocalDbs.databases[1]).toEqual(db2); + }); + }); + + describe("removeLocalList", () => { + it("should remove a local list", () => { + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [], + }, + { + name: "list2", + databases: [], + }, + ], + }); + + const updatedConfig = removeLocalList(originalConfig, "list1"); + + expect(updatedConfig.databases.local.lists).toEqual([ + { + name: "list2", + databases: [], + }, + ]); + }); + + it("should remove a selected local list", () => { + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [], + }, + { + name: "list2", + databases: [], + }, + ], + selected: { + kind: SelectedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + }); + + const updatedConfig = removeLocalList(originalConfig, "list1"); + + expect(updatedConfig.databases.local.lists).toEqual([ + { + name: "list2", + databases: [], + }, + ]); + + expect(updatedConfig.selected).toBeUndefined(); + }); + + it("should remove a local list with a db that is selected", () => { + const selectedLocalDb = createLocalDbConfigItem(); + const list1: LocalList = { + name: "list1", + databases: [ + createLocalDbConfigItem(), + selectedLocalDb, + createLocalDbConfigItem(), + ], + }; + const list2: LocalList = { + name: "list2", + databases: [], + }; + + const originalConfig = createDbConfig({ + localLists: [list1, list2], + selected: { + kind: SelectedDbItemKind.LocalDatabase, + databaseName: selectedLocalDb.name, + listName: list1.name, + }, + }); + + const updatedConfig = removeLocalList(originalConfig, list1.name); + + expect(updatedConfig.databases.local.lists.length).toEqual(1); + expect(updatedConfig.databases.local.lists[0]).toEqual(list2); + expect(updatedConfig.selected).toBeUndefined(); + }); + }); + + describe("removeRemoteList", () => { + it("should remove a remote list", () => { + const originalConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: [], + }, + { + name: "list2", + repositories: [], + }, + ], + }); + + const updatedConfig = removeRemoteList(originalConfig, "list1"); + + expect(updatedConfig.databases.variantAnalysis.repositoryLists).toEqual([ + { + name: "list2", + repositories: [], + }, + ]); + }); + + it("should remove a selected remote list", () => { + const originalConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: [], + }, + { + name: "list2", + repositories: [], + }, + ], + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: "list1", + }, + }); + + const updatedConfig = removeRemoteList(originalConfig, "list1"); + + expect(updatedConfig.databases.variantAnalysis.repositoryLists).toEqual([ + { + name: "list2", + repositories: [], + }, + ]); + + expect(updatedConfig.selected).toBeUndefined(); + }); + + it("should remove a remote list with a db that is selected", () => { + const selectedRemoteRepo = "owner/repo2"; + const originalConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: ["owner1/repo1", selectedRemoteRepo, "owner1/repo3"], + }, + { + name: "list2", + repositories: [], + }, + ], + selected: { + kind: SelectedDbItemKind.VariantAnalysisRepository, + repositoryName: selectedRemoteRepo, + listName: "list1", + }, + }); + + const updatedConfig = removeRemoteList(originalConfig, "list1"); + const updatedRepositoryLists = + updatedConfig.databases.variantAnalysis.repositoryLists; + + expect(updatedRepositoryLists.length).toEqual(1); + expect(updatedRepositoryLists[0]).toEqual( + originalConfig.databases.variantAnalysis.repositoryLists[1], + ); + expect(updatedConfig.selected).toBeUndefined(); + }); + }); + + describe("removeLocalDb", () => { + it("should remove a local db", () => { + const db1 = createLocalDbConfigItem({ name: "db1" }); + const db2 = createLocalDbConfigItem({ name: "db2" }); + + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + ], + }, + ], + localDbs: [db1, db2], + }); + + const updatedConfig = renameLocalDb(originalConfig, "db1", "dbRenamed"); + + const updatedLocalDbs = updatedConfig.databases.local; + const originalLocalDbs = originalConfig.databases.local; + + expect(updatedLocalDbs.lists).toEqual(originalLocalDbs.lists); + expect(updatedLocalDbs.databases.length).toEqual(2); + expect(updatedLocalDbs.databases[0]).toEqual({ + ...db1, + name: "dbRenamed", + }); + expect(updatedLocalDbs.databases[1]).toEqual(db2); + }); + + it("should remove a local db inside a list", () => { + const db1List1 = createLocalDbConfigItem({ name: "db1" }); + const db2List1 = createLocalDbConfigItem({ name: "db2" }); + + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [db1List1, db2List1], + }, + { + name: "list2", + databases: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + ], + }, + ], + localDbs: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + ], + }); + + const updatedConfig = removeLocalDb( + originalConfig, + db1List1.name, + "list1", + ); + + const updatedLocalDbs = updatedConfig.databases.local; + const originalLocalDbs = originalConfig.databases.local; + expect(updatedLocalDbs.databases).toEqual(originalLocalDbs.databases); + expect(updatedLocalDbs.lists.length).toEqual(2); + expect(updatedLocalDbs.lists[0].databases.length).toEqual(1); + expect(updatedLocalDbs.lists[0].databases[0]).toEqual(db2List1); + expect(updatedLocalDbs.lists[1]).toEqual(originalLocalDbs.lists[1]); + }); + + it("should remove a local db that is selected", () => { + const db1 = createLocalDbConfigItem({ name: "db1" }); + const db2 = createLocalDbConfigItem({ name: "db2" }); + + const originalConfig = createDbConfig({ + localLists: [ + { + name: "list1", + databases: [ + createLocalDbConfigItem({ name: "db1" }), + createLocalDbConfigItem({ name: "db2" }), + ], + }, + ], + localDbs: [db1, db2], + selected: { + kind: SelectedDbItemKind.LocalDatabase, + databaseName: "db1", + }, + }); + + const updatedConfig = removeLocalDb(originalConfig, "db1"); + + const updatedLocalDbs = updatedConfig.databases.local; + const originalLocalDbs = originalConfig.databases.local; + + expect(updatedLocalDbs.lists).toEqual(originalLocalDbs.lists); + expect(updatedLocalDbs.databases.length).toEqual(1); + expect(updatedLocalDbs.databases[0]).toEqual(db2); + }); + }); + + describe("removeRemoteRepo", () => { + it("should remove a remote repo", () => { + const repo1 = "owner1/repo1"; + const repo2 = "owner1/repo2"; + + const originalConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: [repo1, repo2], + }, + ], + remoteRepos: [repo1, repo2], + }); + + const updatedConfig = removeRemoteRepo(originalConfig, repo1); + + const updatedRemoteDbs = updatedConfig.databases.variantAnalysis; + const originalRemoteDbs = originalConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositories.length).toEqual(1); + expect(updatedRemoteDbs.repositories[0]).toEqual(repo2); + expect(updatedRemoteDbs.repositoryLists).toEqual( + originalRemoteDbs.repositoryLists, + ); + }); + + it("should remove a remote repo inside a list", () => { + const repo1 = "owner1/repo1"; + const repo2 = "owner1/repo2"; + + const list1: RemoteRepositoryList = { + name: "list1", + repositories: [repo1, repo2], + }; + const list2: RemoteRepositoryList = { + name: "list2", + repositories: [repo1, repo2], + }; + + const originalConfig = createDbConfig({ + remoteLists: [list1, list2], + remoteRepos: [repo1, repo2], + }); + + const updatedConfig = removeRemoteRepo(originalConfig, repo1, list1.name); + const updatedRemoteDbs = updatedConfig.databases.variantAnalysis; + const originalRemoteDbs = originalConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositories).toEqual( + originalRemoteDbs.repositories, + ); + expect(updatedRemoteDbs.repositoryLists.length).toEqual(2); + expect(updatedRemoteDbs.repositoryLists[0].repositories.length).toEqual( + 1, + ); + expect(updatedRemoteDbs.repositoryLists[0].repositories[0]).toEqual( + repo2, + ); + expect(updatedRemoteDbs.repositoryLists[1]).toEqual( + originalRemoteDbs.repositoryLists[1], + ); + }); + + it("should remove a remote repo that is selected", () => { + const repo1 = "owner1/repo1"; + const repo2 = "owner1/repo2"; + + const originalConfig = createDbConfig({ + remoteLists: [ + { + name: "list1", + repositories: [repo1, repo2], + }, + ], + remoteRepos: [repo1, repo2], + selected: { + kind: SelectedDbItemKind.VariantAnalysisRepository, + repositoryName: repo1, + }, + }); + + const updatedConfig = removeRemoteRepo(originalConfig, repo1); + + const updatedRemoteDbs = updatedConfig.databases.variantAnalysis; + const originalRemoteDbs = originalConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.repositories.length).toEqual(1); + expect(updatedRemoteDbs.repositories[0]).toEqual(repo2); + expect(updatedRemoteDbs.repositoryLists).toEqual( + originalRemoteDbs.repositoryLists, + ); + expect(updatedConfig.selected).toBeUndefined(); + }); + }); + + describe("removeOwner", () => { + it("should remove a remote owner", () => { + const owner1 = "owner1"; + const owner2 = "owner2"; + + const originalConfig = createDbConfig({ + remoteOwners: [owner1, owner2], + }); + + const updatedConfig = removeRemoteOwner(originalConfig, owner1); + + const updatedRemoteDbs = updatedConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.owners).toEqual([owner2]); + }); + + it("should remove a remote owner that is selected", () => { + const owner1 = "owner1"; + const owner2 = "owner2"; + + const originalConfig = createDbConfig({ + remoteOwners: [owner1, owner2], + selected: { + kind: SelectedDbItemKind.VariantAnalysisOwner, + ownerName: owner1, + }, + }); + + const updatedConfig = removeRemoteOwner(originalConfig, owner1); + + const updatedRemoteDbs = updatedConfig.databases.variantAnalysis; + expect(updatedRemoteDbs.owners).toEqual([owner2]); + expect(updatedConfig.selected).toBeUndefined(); + }); + }); +}); diff --git a/extensions/ql-vscode/test/unit-tests/databases/db-item-expansion.test.ts b/extensions/ql-vscode/test/unit-tests/databases/db-item-expansion.test.ts new file mode 100644 index 000000000..86a0f101d --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/databases/db-item-expansion.test.ts @@ -0,0 +1,221 @@ +import { + updateExpandedItem, + ExpandedDbItem, + ExpandedDbItemKind, + replaceExpandedItem, + cleanNonExistentExpandedItems, +} from "../../../src/databases/db-item-expansion"; +import { + createLocalListDbItem, + createVariantAnalysisUserDefinedListDbItem, + createRootLocalDbItem, + createRootRemoteDbItem, +} from "../../factories/db-item-factories"; + +describe("db item expansion", () => { + describe("updateExpandedItem", () => { + it("should add an expanded item to an existing list", () => { + const currentExpandedItems: ExpandedDbItem[] = [ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list1", + }, + ]; + + const dbItem = createVariantAnalysisUserDefinedListDbItem({ + listName: "list2", + }); + + const newExpandedItems = updateExpandedItem( + currentExpandedItems, + dbItem, + true, + ); + + expect(newExpandedItems).toEqual([ + ...currentExpandedItems, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list2", + }, + ]); + }); + + it("should add an expanded item to an empty list", () => { + const dbItem = createVariantAnalysisUserDefinedListDbItem({ + listName: "list2", + }); + + const newExpandedItems = updateExpandedItem([], dbItem, true); + + expect(newExpandedItems).toEqual([ + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list2", + }, + ]); + }); + + it("should remove a collapsed item from a list", () => { + const currentExpandedItems: ExpandedDbItem[] = [ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list1", + }, + ]; + + const dbItem = createVariantAnalysisUserDefinedListDbItem({ + listName: "list1", + }); + + const newExpandedItems = updateExpandedItem( + currentExpandedItems, + dbItem, + false, + ); + + expect(newExpandedItems).toEqual([ + { + kind: ExpandedDbItemKind.RootRemote, + }, + ]); + }); + + it("should remove a collapsed item from a list that becomes empty", () => { + const currentExpandedItems: ExpandedDbItem[] = [ + { + kind: ExpandedDbItemKind.RootRemote, + }, + ]; + + const dbItem = createRootRemoteDbItem(); + + const newExpandedItems = updateExpandedItem( + currentExpandedItems, + dbItem, + false, + ); + + expect(newExpandedItems).toEqual([]); + }); + }); + + describe("replaceExpandedItem", () => { + it("should replace the db item", () => { + const currentExpandedItems: ExpandedDbItem[] = [ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list1", + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list2", + }, + { + kind: ExpandedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + ]; + + const currentDbItem = createVariantAnalysisUserDefinedListDbItem({ + listName: "list1", + }); + + const newDbItem = { + ...currentDbItem, + listName: "list1 (renamed)", + }; + + const newExpandedItems = replaceExpandedItem( + currentExpandedItems, + currentDbItem, + newDbItem, + ); + + expect(newExpandedItems).toEqual([ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list1 (renamed)", + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list2", + }, + { + kind: ExpandedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + ]); + }); + }); + + describe("cleanNonExistentExpandedItems", () => { + it("should remove non-existent items", () => { + const currentExpandedItems: ExpandedDbItem[] = [ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list1", + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list2", + }, + { + kind: ExpandedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + ]; + + const dbItems = [ + createRootRemoteDbItem({ + children: [ + createVariantAnalysisUserDefinedListDbItem({ + listName: "list2", + }), + ], + }), + createRootLocalDbItem({ + children: [ + createLocalListDbItem({ + listName: "list1", + }), + ], + }), + ]; + + const newExpandedItems = cleanNonExistentExpandedItems( + currentExpandedItems, + dbItems, + ); + + expect(newExpandedItems).toEqual([ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list2", + }, + { + kind: ExpandedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + ]); + }); + }); +}); diff --git a/extensions/ql-vscode/test/unit-tests/databases/db-item-naming.test.ts b/extensions/ql-vscode/test/unit-tests/databases/db-item-naming.test.ts new file mode 100644 index 000000000..7f8f64e44 --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/databases/db-item-naming.test.ts @@ -0,0 +1,79 @@ +import { getDbItemName } from "../../../src/databases/db-item-naming"; +import { + createLocalDatabaseDbItem, + createLocalListDbItem, + createRemoteOwnerDbItem, + createRemoteRepoDbItem, + createRemoteSystemDefinedListDbItem, + createVariantAnalysisUserDefinedListDbItem, + createRootLocalDbItem, + createRootRemoteDbItem, +} from "../../factories/db-item-factories"; + +describe("db item naming", () => { + describe("getDbItemName", () => { + it("return undefined for root local db item", () => { + const dbItem = createRootLocalDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toBeUndefined(); + }); + + it("return undefined for root remote db item", () => { + const dbItem = createRootRemoteDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toBeUndefined(); + }); + + it("return list name for local list db item", () => { + const dbItem = createLocalListDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.listName); + }); + + it("return list name for remote user defined list db item", () => { + const dbItem = createVariantAnalysisUserDefinedListDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.listName); + }); + + it("return list name for remote system defined list db item", () => { + const dbItem = createRemoteSystemDefinedListDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.listName); + }); + + it("return owner name for owner db item", () => { + const dbItem = createRemoteOwnerDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.ownerName); + }); + + it("return database name for local db item", () => { + const dbItem = createLocalDatabaseDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.databaseName); + }); + + it("return repo name for remote repo db item", () => { + const dbItem = createRemoteRepoDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.repoFullName); + }); + }); +}); diff --git a/extensions/ql-vscode/test/pure-tests/databases/db-item-selection.test.ts b/extensions/ql-vscode/test/unit-tests/databases/db-item-selection.test.ts similarity index 91% rename from extensions/ql-vscode/test/pure-tests/databases/db-item-selection.test.ts rename to extensions/ql-vscode/test/unit-tests/databases/db-item-selection.test.ts index 296eb8d1e..08ae1e727 100644 --- a/extensions/ql-vscode/test/pure-tests/databases/db-item-selection.test.ts +++ b/extensions/ql-vscode/test/unit-tests/databases/db-item-selection.test.ts @@ -6,7 +6,7 @@ import { createRemoteOwnerDbItem, createRemoteRepoDbItem, createRemoteSystemDefinedListDbItem, - createRemoteUserDefinedListDbItem, + createVariantAnalysisUserDefinedListDbItem, createRootLocalDbItem, createRootRemoteDbItem, } from "../../factories/db-item-factories"; @@ -18,7 +18,7 @@ describe("db item selection", () => { children: [ createRemoteSystemDefinedListDbItem(), createRemoteOwnerDbItem(), - createRemoteUserDefinedListDbItem(), + createVariantAnalysisUserDefinedListDbItem(), ], }), createRootLocalDbItem({ @@ -67,7 +67,7 @@ describe("db item selection", () => { children: [ createRemoteSystemDefinedListDbItem(), createRemoteOwnerDbItem(), - createRemoteUserDefinedListDbItem({ + createVariantAnalysisUserDefinedListDbItem({ listName: "my list", selected: true, repos: [ @@ -80,7 +80,7 @@ describe("db item selection", () => { ]; expect(getSelectedDbItem(dbItems)).toEqual({ - kind: DbItemKind.RemoteUserDefinedList, + kind: DbItemKind.VariantAnalysisUserDefinedList, expanded: false, listName: "my list", repos: [ @@ -105,7 +105,7 @@ describe("db item selection", () => { children: [ createRemoteSystemDefinedListDbItem(), createRemoteOwnerDbItem(), - createRemoteUserDefinedListDbItem(), + createVariantAnalysisUserDefinedListDbItem(), ], }), createRemoteSystemDefinedListDbItem({ @@ -131,7 +131,7 @@ describe("db item selection", () => { children: [ createRemoteSystemDefinedListDbItem(), createRemoteOwnerDbItem(), - createRemoteUserDefinedListDbItem({ + createVariantAnalysisUserDefinedListDbItem({ repos: [], selected: true, listName: "list84", @@ -141,7 +141,7 @@ describe("db item selection", () => { ]; expect(getSelectedDbItem(dbItems)).toEqual({ expanded: false, - kind: DbItemKind.RemoteUserDefinedList, + kind: DbItemKind.VariantAnalysisUserDefinedList, listName: "list84", repos: [], selected: true, diff --git a/extensions/ql-vscode/test/unit-tests/databases/db-item.test.ts b/extensions/ql-vscode/test/unit-tests/databases/db-item.test.ts new file mode 100644 index 000000000..4c612bcbf --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/databases/db-item.test.ts @@ -0,0 +1,144 @@ +import { + DbItem, + DbItemKind, + flattenDbItems, +} from "../../../src/databases/db-item"; +import { + createLocalDatabaseDbItem, + createLocalListDbItem, + createRemoteOwnerDbItem, + createRemoteRepoDbItem, + createRemoteSystemDefinedListDbItem, + createVariantAnalysisUserDefinedListDbItem, + createRootLocalDbItem, + createRootRemoteDbItem, +} from "../../factories/db-item-factories"; + +describe("DbItem", () => { + describe("flattenDbItems", () => { + it("should flatten a list of DbItems", () => { + const dbItems = [ + createRootRemoteDbItem({ + children: [ + createRemoteSystemDefinedListDbItem({ listName: "top10" }), + createRemoteSystemDefinedListDbItem({ listName: "top100" }), + createVariantAnalysisUserDefinedListDbItem({ + listName: "remote-list1", + repos: [ + createRemoteRepoDbItem({ repoFullName: "owner1/repo1" }), + createRemoteRepoDbItem({ repoFullName: "owner1/repo2" }), + ], + }), + createVariantAnalysisUserDefinedListDbItem({ + listName: "remote-list2", + repos: [ + createRemoteRepoDbItem({ repoFullName: "owner2/repo1" }), + createRemoteRepoDbItem({ repoFullName: "owner2/repo2" }), + ], + }), + createRemoteOwnerDbItem({ ownerName: "owner1" }), + createRemoteRepoDbItem({ repoFullName: "owner3/repo3" }), + ], + }), + createRootLocalDbItem({ + children: [ + createLocalListDbItem({ + listName: "local-list1", + databases: [ + createLocalDatabaseDbItem({ databaseName: "local-db1" }), + ], + }), + createLocalDatabaseDbItem({ databaseName: "local-db2" }), + ], + }), + ]; + + const flattenedItems = flattenDbItems(dbItems); + + expect(flattenedItems.length).toEqual(15); + checkRootRemoteExists(flattenedItems); + checkSystemDefinedListExists(flattenedItems, "top10"); + checkSystemDefinedListExists(flattenedItems, "top100"); + checkUserDefinedListExists(flattenedItems, "remote-list1"); + checkRemoteRepoExists(flattenedItems, "owner1/repo1"); + checkRemoteRepoExists(flattenedItems, "owner1/repo2"); + checkRemoteRepoExists(flattenedItems, "owner2/repo1"); + checkRemoteRepoExists(flattenedItems, "owner2/repo2"); + checkRemoteOwnerExists(flattenedItems, "owner1"); + checkRemoteRepoExists(flattenedItems, "owner3/repo3"); + checkRootLocalExists(flattenedItems); + checkLocalListExists(flattenedItems, "local-list1"); + checkLocalDbExists(flattenedItems, "local-db1"); + checkLocalDbExists(flattenedItems, "local-db2"); + }); + + function checkRootRemoteExists(items: DbItem[]): void { + expect( + items.find((item) => item.kind === DbItemKind.RootRemote), + ).toBeDefined(); + } + + function checkUserDefinedListExists(items: DbItem[], name: string): void { + expect( + items.find( + (item) => + item.kind === DbItemKind.VariantAnalysisUserDefinedList && + item.listName === name, + ), + ).toBeDefined(); + } + + function checkSystemDefinedListExists(items: DbItem[], name: string): void { + expect( + items.find( + (item) => + item.kind === DbItemKind.RemoteSystemDefinedList && + item.listName === name, + ), + ).toBeDefined(); + } + + function checkRemoteOwnerExists(items: DbItem[], name: string): void { + expect( + items.find( + (item) => + item.kind === DbItemKind.RemoteOwner && item.ownerName === name, + ), + ).toBeDefined(); + } + + function checkRemoteRepoExists(items: DbItem[], name: string): void { + expect( + items.find( + (item) => + item.kind === DbItemKind.RemoteRepo && item.repoFullName === name, + ), + ).toBeDefined(); + } + + function checkRootLocalExists(items: DbItem[]): void { + expect( + items.find((item) => item.kind === DbItemKind.RootLocal), + ).toBeDefined(); + } + + function checkLocalListExists(items: DbItem[], name: string): void { + expect( + items.find( + (item) => + item.kind === DbItemKind.LocalList && item.listName === name, + ), + ).toBeDefined(); + } + + function checkLocalDbExists(items: DbItem[], name: string): void { + expect( + items.find( + (item) => + item.kind === DbItemKind.LocalDatabase && + item.databaseName === name, + ), + ).toBeDefined(); + } + }); +}); diff --git a/extensions/ql-vscode/test/unit-tests/databases/db-manager.test.ts b/extensions/ql-vscode/test/unit-tests/databases/db-manager.test.ts new file mode 100644 index 000000000..2653f5ee0 --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/databases/db-manager.test.ts @@ -0,0 +1,441 @@ +import { ensureDir, readJSON, remove, writeJson } from "fs-extra"; +import { join } from "path"; +import { + DbConfig, + SelectedDbItemKind, +} from "../../../src/databases/config/db-config"; +import { DbConfigStore } from "../../../src/databases/config/db-config-store"; +import { + flattenDbItems, + isLocalDatabaseDbItem, + isLocalListDbItem, + isRemoteOwnerDbItem, + isRemoteRepoDbItem, + isVariantAnalysisUserDefinedListDbItem, + LocalDatabaseDbItem, + LocalListDbItem, + RemoteOwnerDbItem, + RemoteRepoDbItem, + VariantAnalysisUserDefinedListDbItem, +} from "../../../src/databases/db-item"; +import { DbManager } from "../../../src/databases/db-manager"; +import { + createDbConfig, + createLocalDbConfigItem, +} from "../../factories/db-config-factories"; +import { createMockApp } from "../../__mocks__/appMock"; + +// Note: Although these are "unit tests" (i.e. not integrating with VS Code), they do +// test the interaction/"integration" between the DbManager and the DbConfigStore. +describe("db manager", () => { + let dbManager: DbManager; + let dbConfigStore: DbConfigStore; + let tempWorkspaceStoragePath: string; + let dbConfigFilePath: string; + + beforeEach(async () => { + tempWorkspaceStoragePath = join(__dirname, "db-manager-test-workspace"); + + const extensionPath = join(__dirname, "../../.."); + const app = createMockApp({ + extensionPath, + workspaceStoragePath: tempWorkspaceStoragePath, + }); + + // We don't need to watch changes to the config file in these tests, so we + // pass `false` to the dbConfigStore constructor. + dbConfigStore = new DbConfigStore(app, false); + dbManager = new DbManager(app, dbConfigStore); + await ensureDir(tempWorkspaceStoragePath); + + dbConfigFilePath = join( + tempWorkspaceStoragePath, + "workspace-databases.json", + ); + }); + + afterEach(async () => { + await remove(tempWorkspaceStoragePath); + dbConfigStore.dispose(); + }); + + describe("renaming items", () => { + const remoteList = { + name: "my-list-1", + repositories: ["owner1/repo1", "owner1/repo2"], + }; + const localDb = createLocalDbConfigItem({ name: "db1" }); + const localList = { + name: "my-list-1", + databases: [localDb], + }; + + it("should rename remote user-defined list", async () => { + const dbConfig = createDbConfig({ + remoteLists: [remoteList], + localLists: [localList], + }); + + await saveDbConfig(dbConfig); + + const remoteListDbItems = + getVariantAnalysisUserDefinedListDbItems("my-list-1"); + expect(remoteListDbItems.length).toEqual(1); + + await dbManager.renameList(remoteListDbItems[0], "my-list-2"); + + const dbConfigFileContents = await readDbConfigDirectly(); + + // Check that the remote list has been renamed + const remoteLists = + dbConfigFileContents.databases.variantAnalysis.repositoryLists; + expect(remoteLists.length).toBe(1); + expect(remoteLists[0]).toEqual({ + name: "my-list-2", + repositories: ["owner1/repo1", "owner1/repo2"], + }); + + // Check that the local list has not been renamed + const localLists = dbConfigFileContents.databases.local.lists; + expect(localLists.length).toBe(1); + expect(localLists[0]).toEqual({ + name: "my-list-1", + databases: [localDb], + }); + }); + + it("should rename local db list", async () => { + const dbConfig = createDbConfig({ + remoteLists: [remoteList], + localLists: [localList], + }); + + await saveDbConfig(dbConfig); + + const localListDbItems = getLocalListDbItems("my-list-1"); + expect(localListDbItems.length).toEqual(1); + + await dbManager.renameList(localListDbItems[0], "my-list-2"); + + const dbConfigFileContents = await readDbConfigDirectly(); + + // Check that the local list has been renamed + const localLists = dbConfigFileContents.databases.local.lists; + expect(localLists.length).toBe(1); + expect(localLists[0]).toEqual({ + name: "my-list-2", + databases: [localDb], + }); + + // Check that the remote list has not been renamed + const remoteLists = + dbConfigFileContents.databases.variantAnalysis.repositoryLists; + expect(remoteLists.length).toBe(1); + expect(remoteLists[0]).toEqual({ + name: "my-list-1", + repositories: ["owner1/repo1", "owner1/repo2"], + }); + }); + + it("should rename local db outside a list", async () => { + const dbConfig = createDbConfig({ + localLists: [localList], + localDbs: [localDb], + }); + + await saveDbConfig(dbConfig); + + const localDbItems = getLocalDatabaseDbItems("db1"); + expect(localDbItems.length).toEqual(1); + + await dbManager.renameLocalDb(localDbItems[0], "db2"); + + const dbConfigFileContents = await readDbConfigDirectly(); + + // Check that the db outside of the list has been renamed + const localDbs = dbConfigFileContents.databases.local.databases; + expect(localDbs.length).toBe(1); + expect(localDbs[0].name).toEqual("db2"); + + // Check that the db inside the list has not been renamed + const localLists = dbConfigFileContents.databases.local.lists; + expect(localLists.length).toBe(1); + expect(localLists[0]).toEqual({ + name: "my-list-1", + databases: [localDb], + }); + }); + + it("should rename local db inside a list", async () => { + const dbConfig = createDbConfig({ + localLists: [localList], + localDbs: [localDb], + }); + + await saveDbConfig(dbConfig); + + const localDbItems = getLocalDatabaseDbItems("db1", "my-list-1"); + expect(localDbItems.length).toEqual(1); + + await dbManager.renameLocalDb(localDbItems[0], "db2"); + + const dbConfigFileContents = await readDbConfigDirectly(); + + // Check that the db inside the list has been renamed + const localListDbs = + dbConfigFileContents.databases.local.lists[0].databases; + expect(localListDbs.length).toBe(1); + expect(localListDbs[0].name).toEqual("db2"); + + // Check that the db outside of the list has not been renamed + const localDbs = dbConfigFileContents.databases.local.databases; + expect(localDbs.length).toBe(1); + expect(localDbs[0]).toEqual(localDb); + }); + }); + + describe("removing items", () => { + const remoteRepo1 = "owner1/repo1"; + const remoteRepo2 = "owner1/repo2"; + const remoteList = { + name: "my-list-1", + repositories: [remoteRepo1, remoteRepo2], + }; + const remoteOwner = "owner1"; + const localDb = createLocalDbConfigItem({ name: "db1" }); + const localList = { + name: "my-list-1", + databases: [localDb], + }; + const dbConfig = createDbConfig({ + remoteLists: [remoteList], + remoteOwners: [remoteOwner], + remoteRepos: [remoteRepo1, remoteRepo2], + localLists: [localList], + localDbs: [localDb], + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: remoteList.name, + }, + }); + + it("should remove remote user-defined list", async () => { + await saveDbConfig(dbConfig); + + const remoteListDbItems = + getVariantAnalysisUserDefinedListDbItems("my-list-1"); + expect(remoteListDbItems.length).toEqual(1); + + await dbManager.removeDbItem(remoteListDbItems[0]); + + const dbConfigFileContents = await readDbConfigDirectly(); + + expect(dbConfigFileContents).toEqual({ + databases: { + variantAnalysis: { + repositoryLists: [], + repositories: [remoteRepo1, remoteRepo2], + owners: [remoteOwner], + }, + local: { + lists: [localList], + databases: [localDb], + }, + }, + }); + }); + + it("should remove remote repo", async () => { + await saveDbConfig(dbConfig); + + const remoteRepoDbItems = getRemoteRepoDbItems("owner1/repo1"); + expect(remoteRepoDbItems.length).toBe(1); + + await dbManager.removeDbItem(remoteRepoDbItems[0]); + + const dbConfigFileContents = await readDbConfigDirectly(); + + expect(dbConfigFileContents).toEqual({ + databases: { + variantAnalysis: { + repositoryLists: [remoteList], + repositories: [remoteRepo2], + owners: [remoteOwner], + }, + local: { + lists: [localList], + databases: [localDb], + }, + }, + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: remoteList.name, + }, + }); + }); + + it("should remove remote owner", async () => { + await saveDbConfig(dbConfig); + + const remoteOwnerDbItems = getRemoteOwnerDbItems("owner1"); + expect(remoteOwnerDbItems.length).toEqual(1); + + await dbManager.removeDbItem(remoteOwnerDbItems[0]); + + const dbConfigFileContents = await readDbConfigDirectly(); + + expect(dbConfigFileContents).toEqual({ + databases: { + variantAnalysis: { + repositoryLists: [remoteList], + repositories: [remoteRepo1, remoteRepo2], + owners: [], + }, + local: { + lists: [localList], + databases: [localDb], + }, + }, + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: remoteList.name, + }, + }); + }); + + it("should remove local db list", async () => { + await saveDbConfig(dbConfig); + + const localListDbItems = getLocalListDbItems("my-list-1"); + expect(localListDbItems.length).toEqual(1); + + await dbManager.removeDbItem(localListDbItems[0]); + + const dbConfigFileContents = await readDbConfigDirectly(); + + expect(dbConfigFileContents).toEqual({ + databases: { + variantAnalysis: { + repositoryLists: [remoteList], + repositories: [remoteRepo1, remoteRepo2], + owners: [remoteOwner], + }, + local: { + lists: [], + databases: [localDb], + }, + }, + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: remoteList.name, + }, + }); + }); + + it("should remove local database", async () => { + await saveDbConfig(dbConfig); + + const localDbItems = getLocalDatabaseDbItems("db1"); + expect(localDbItems.length).toEqual(1); + + await dbManager.removeDbItem(localDbItems[0]); + + const dbConfigFileContents = await readDbConfigDirectly(); + + expect(dbConfigFileContents).toEqual({ + databases: { + variantAnalysis: { + repositoryLists: [remoteList], + repositories: [remoteRepo1, remoteRepo2], + owners: [remoteOwner], + }, + local: { + lists: [localList], + databases: [], + }, + }, + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: remoteList.name, + }, + }); + }); + }); + + async function saveDbConfig(dbConfig: DbConfig): Promise { + await writeJson(dbConfigFilePath, dbConfig); + + // Ideally we would just initialize the db config store at the start + // of each test and then rely on the file watcher to update the config. + // However, this requires adding sleep to the tests to allow for the + // file watcher to catch up, so we instead initialize the config store here. + await dbConfigStore.initialize(); + } + + async function readDbConfigDirectly(): Promise { + return (await readJSON(dbConfigFilePath)) as DbConfig; + } + + function getLocalListDbItems(listName: string): LocalListDbItem[] { + const dbItemsResult = dbManager.getDbItems(); + const dbItems = flattenDbItems(dbItemsResult.value); + const listDbItems = dbItems + .filter(isLocalListDbItem) + .filter((i) => i.listName === listName); + + return listDbItems; + } + + function getLocalDatabaseDbItems( + dbName: string, + parentListName?: string, + ): LocalDatabaseDbItem[] { + const dbItemsResult = dbManager.getDbItems(); + const dbItems = flattenDbItems(dbItemsResult.value); + const localDbItems = dbItems + .filter(isLocalDatabaseDbItem) + .filter( + (i) => i.databaseName === dbName && i.parentListName === parentListName, + ); + + return localDbItems; + } + + function getRemoteRepoDbItems( + repoName: string, + parentListName?: string, + ): RemoteRepoDbItem[] { + const dbItemsResult = dbManager.getDbItems(); + const dbItems = flattenDbItems(dbItemsResult.value); + const repoDbItems = dbItems + .filter(isRemoteRepoDbItem) + .filter( + (i) => + i.repoFullName === repoName && i.parentListName === parentListName, + ); + + return repoDbItems; + } + + function getRemoteOwnerDbItems(ownerName: string): RemoteOwnerDbItem[] { + const dbItemsResult = dbManager.getDbItems(); + const dbItems = flattenDbItems(dbItemsResult.value); + const ownerDbItems = dbItems + .filter(isRemoteOwnerDbItem) + .filter((i) => i.ownerName === ownerName); + + return ownerDbItems; + } + + function getVariantAnalysisUserDefinedListDbItems( + listName: string, + ): VariantAnalysisUserDefinedListDbItem[] { + const dbItemsResult = dbManager.getDbItems(); + const dbItems = flattenDbItems(dbItemsResult.value); + const listDbItems = dbItems + .filter(isVariantAnalysisUserDefinedListDbItem) + .filter((i) => i.listName === listName); + + return listDbItems; + } +}); diff --git a/extensions/ql-vscode/test/pure-tests/databases/db-tree-creator.test.ts b/extensions/ql-vscode/test/unit-tests/databases/db-tree-creator.test.ts similarity index 78% rename from extensions/ql-vscode/test/pure-tests/databases/db-tree-creator.test.ts rename to extensions/ql-vscode/test/unit-tests/databases/db-tree-creator.test.ts index dc79ed245..a82f59a80 100644 --- a/extensions/ql-vscode/test/pure-tests/databases/db-tree-creator.test.ts +++ b/extensions/ql-vscode/test/unit-tests/databases/db-tree-creator.test.ts @@ -1,26 +1,29 @@ import { DbConfig, - ExpandedDbItemKind, SelectedDbItemKind, } from "../../../src/databases/config/db-config"; import { DbItemKind, isRemoteOwnerDbItem, isRemoteRepoDbItem, - isRemoteUserDefinedListDbItem, + isVariantAnalysisUserDefinedListDbItem, } from "../../../src/databases/db-item"; +import { + ExpandedDbItem, + ExpandedDbItemKind, +} from "../../../src/databases/db-item-expansion"; import { createLocalTree, createRemoteTree, } from "../../../src/databases/db-tree-creator"; -import { createDbConfig } from "../../../src/vscode-tests/factories/db-config-factories"; +import { createDbConfig } from "../../factories/db-config-factories"; describe("db tree creator", () => { describe("createRemoteTree", () => { it("should build root node and system defined lists", () => { const dbConfig = createDbConfig(); - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); @@ -63,42 +66,46 @@ describe("db tree creator", () => { ], }); - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); const repositoryListNodes = dbTreeRoot.children.filter( - isRemoteUserDefinedListDbItem, + isVariantAnalysisUserDefinedListDbItem, ); expect(repositoryListNodes.length).toBe(2); expect(repositoryListNodes[0]).toEqual({ - kind: DbItemKind.RemoteUserDefinedList, + kind: DbItemKind.VariantAnalysisUserDefinedList, selected: false, expanded: false, - listName: dbConfig.databases.remote.repositoryLists[0].name, - repos: dbConfig.databases.remote.repositoryLists[0].repositories.map( - (repo) => ({ - kind: DbItemKind.RemoteRepo, - selected: false, - repoFullName: repo, - parentListName: dbConfig.databases.remote.repositoryLists[0].name, - }), - ), + listName: dbConfig.databases.variantAnalysis.repositoryLists[0].name, + repos: + dbConfig.databases.variantAnalysis.repositoryLists[0].repositories.map( + (repo) => ({ + kind: DbItemKind.RemoteRepo, + selected: false, + repoFullName: repo, + parentListName: + dbConfig.databases.variantAnalysis.repositoryLists[0].name, + }), + ), }); expect(repositoryListNodes[1]).toEqual({ - kind: DbItemKind.RemoteUserDefinedList, + kind: DbItemKind.VariantAnalysisUserDefinedList, selected: false, expanded: false, - listName: dbConfig.databases.remote.repositoryLists[1].name, - repos: dbConfig.databases.remote.repositoryLists[1].repositories.map( - (repo) => ({ - kind: DbItemKind.RemoteRepo, - selected: false, - repoFullName: repo, - parentListName: dbConfig.databases.remote.repositoryLists[1].name, - }), - ), + listName: dbConfig.databases.variantAnalysis.repositoryLists[1].name, + repos: + dbConfig.databases.variantAnalysis.repositoryLists[1].repositories.map( + (repo) => ({ + kind: DbItemKind.RemoteRepo, + selected: false, + repoFullName: repo, + parentListName: + dbConfig.databases.variantAnalysis.repositoryLists[1].name, + }), + ), }); }); @@ -107,7 +114,7 @@ describe("db tree creator", () => { remoteOwners: ["owner1", "owner2"], }); - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); @@ -117,12 +124,12 @@ describe("db tree creator", () => { expect(ownerNodes[0]).toEqual({ kind: DbItemKind.RemoteOwner, selected: false, - ownerName: dbConfig.databases.remote.owners[0], + ownerName: dbConfig.databases.variantAnalysis.owners[0], }); expect(ownerNodes[1]).toEqual({ kind: DbItemKind.RemoteOwner, selected: false, - ownerName: dbConfig.databases.remote.owners[1], + ownerName: dbConfig.databases.variantAnalysis.owners[1], }); }); @@ -131,7 +138,7 @@ describe("db tree creator", () => { remoteRepos: ["owner1/repo1", "owner1/repo2", "owner2/repo1"], }); - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); @@ -141,17 +148,17 @@ describe("db tree creator", () => { expect(repoNodes[0]).toEqual({ kind: DbItemKind.RemoteRepo, selected: false, - repoFullName: dbConfig.databases.remote.repositories[0], + repoFullName: dbConfig.databases.variantAnalysis.repositories[0], }); expect(repoNodes[1]).toEqual({ kind: DbItemKind.RemoteRepo, selected: false, - repoFullName: dbConfig.databases.remote.repositories[1], + repoFullName: dbConfig.databases.variantAnalysis.repositories[1], }); expect(repoNodes[2]).toEqual({ kind: DbItemKind.RemoteRepo, selected: false, - repoFullName: dbConfig.databases.remote.repositories[2], + repoFullName: dbConfig.databases.variantAnalysis.repositories[2], }); }); @@ -165,17 +172,17 @@ describe("db tree creator", () => { }, ], selected: { - kind: SelectedDbItemKind.RemoteUserDefinedList, + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, listName: "my-list-1", }, }); - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); const repositoryListNodes = dbTreeRoot.children.filter( - (child) => child.kind === DbItemKind.RemoteUserDefinedList, + (child) => child.kind === DbItemKind.VariantAnalysisUserDefinedList, ); expect(repositoryListNodes.length).toBe(1); @@ -186,12 +193,12 @@ describe("db tree creator", () => { const dbConfig = createDbConfig({ remoteOwners: ["owner1", "owner2"], selected: { - kind: SelectedDbItemKind.RemoteOwner, + kind: SelectedDbItemKind.VariantAnalysisOwner, ownerName: "owner1", }, }); - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); @@ -208,12 +215,12 @@ describe("db tree creator", () => { const dbConfig = createDbConfig({ remoteRepos: ["owner1/repo1", "owner1/repo2"], selected: { - kind: SelectedDbItemKind.RemoteRepository, + kind: SelectedDbItemKind.VariantAnalysisRepository, repositoryName: "owner1/repo2", }, }); - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); @@ -234,18 +241,18 @@ describe("db tree creator", () => { ], remoteRepos: ["owner1/repo2"], selected: { - kind: SelectedDbItemKind.RemoteRepository, + kind: SelectedDbItemKind.VariantAnalysisRepository, listName: "my-list-1", repositoryName: "owner1/repo1", }, }); - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); const listNodes = dbTreeRoot.children.filter( - isRemoteUserDefinedListDbItem, + isVariantAnalysisUserDefinedListDbItem, ); expect(listNodes.length).toBe(1); @@ -258,15 +265,14 @@ describe("db tree creator", () => { describe("expanded db items", () => { it("should allow expanding the root remote list node", () => { - const dbConfig = createDbConfig({ - expanded: [ - { - kind: ExpandedDbItemKind.RootRemote, - }, - ], - }); + const dbConfig = createDbConfig(); + const expanded: ExpandedDbItem[] = [ + { + kind: ExpandedDbItemKind.RootRemote, + }, + ]; - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, expanded); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); @@ -281,24 +287,24 @@ describe("db tree creator", () => { repositories: ["owner1/repo1", "owner1/repo2", "owner2/repo1"], }, ], - expanded: [ - { - kind: ExpandedDbItemKind.RootRemote, - }, - { - kind: ExpandedDbItemKind.RemoteUserDefinedList, - listName: "my-list-1", - }, - ], }); + const expanded: ExpandedDbItem[] = [ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "my-list-1", + }, + ]; - const dbTreeRoot = createRemoteTree(dbConfig); + const dbTreeRoot = createRemoteTree(dbConfig, expanded); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote); expect(dbTreeRoot.expanded).toBe(true); const repositoryListNodes = dbTreeRoot.children.filter( - isRemoteUserDefinedListDbItem, + isVariantAnalysisUserDefinedListDbItem, ); expect(repositoryListNodes.length).toBe(1); @@ -311,7 +317,7 @@ describe("db tree creator", () => { it("should build root node", () => { const dbConfig = createDbConfig(); - const dbTreeRoot = createLocalTree(dbConfig); + const dbTreeRoot = createLocalTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootLocal); @@ -353,7 +359,7 @@ describe("db tree creator", () => { ], }); - const dbTreeRoot = createLocalTree(dbConfig); + const dbTreeRoot = createLocalTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootLocal); @@ -412,7 +418,7 @@ describe("db tree creator", () => { ], }); - const dbTreeRoot = createLocalTree(dbConfig); + const dbTreeRoot = createLocalTree(dbConfig, []); expect(dbTreeRoot).toBeTruthy(); expect(dbTreeRoot.kind).toBe(DbItemKind.RootLocal); diff --git a/extensions/ql-vscode/test/unit-tests/databases/ui/db-tree-view-item-action.test.ts b/extensions/ql-vscode/test/unit-tests/databases/ui/db-tree-view-item-action.test.ts new file mode 100644 index 000000000..88787ed22 --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/databases/ui/db-tree-view-item-action.test.ts @@ -0,0 +1,162 @@ +import { + getDbItemActions, + getGitHubUrl, +} from "../../../../src/databases/ui/db-tree-view-item-action"; +import { + createLocalDatabaseDbItem, + createLocalListDbItem, + createRemoteOwnerDbItem, + createRemoteRepoDbItem, + createRemoteSystemDefinedListDbItem, + createVariantAnalysisUserDefinedListDbItem, + createRootLocalDbItem, + createRootRemoteDbItem, +} from "../../../factories/db-item-factories"; + +describe("getDbItemActions", () => { + it("should return an empty array for root remote node", () => { + const dbItem = createRootRemoteDbItem(); + + const actions = getDbItemActions(dbItem); + + expect(actions).toEqual([]); + }); + + it("should return an empty array for root local node", () => { + const dbItem = createRootLocalDbItem(); + + const actions = getDbItemActions(dbItem); + + expect(actions).toEqual([]); + }); + + it("should set canBeSelected, canBeRemoved and canBeRenamed for local user defined db list", () => { + const dbItem = createLocalListDbItem(); + + const actions = getDbItemActions(dbItem); + + expect(actions).toEqual(["canBeSelected", "canBeRemoved", "canBeRenamed"]); + }); + + it("should set canBeSelected, canBeRemoved and canBeRenamed for local db", () => { + const dbItem = createLocalDatabaseDbItem(); + + const actions = getDbItemActions(dbItem); + + expect(actions).toEqual(["canBeSelected", "canBeRemoved", "canBeRenamed"]); + }); + + it("should set canBeSelected for remote system defined db list", () => { + const dbItem = createRemoteSystemDefinedListDbItem(); + + const actions = getDbItemActions(dbItem); + + expect(actions).toEqual(["canBeSelected"]); + }); + + it("should not set canBeSelected for remote system defined list that is already selected", () => { + const dbItem = createRemoteSystemDefinedListDbItem({ selected: true }); + + const actions = getDbItemActions(dbItem); + + expect(actions.length).toEqual(0); + }); + + it("should set canBeSelected, canBeRemoved and canBeRenamed for remote user defined db list", () => { + const dbItem = createVariantAnalysisUserDefinedListDbItem(); + + const actions = getDbItemActions(dbItem); + + expect(actions).toEqual(["canBeSelected", "canBeRemoved", "canBeRenamed"]); + }); + + it("should not set canBeSelected for remote user defined db list that is already selected", () => { + const dbItem = createVariantAnalysisUserDefinedListDbItem({ + selected: true, + }); + + const actions = getDbItemActions(dbItem); + + expect(actions.includes("canBeSelected")).toBeFalsy(); + }); + + it("should set canBeSelected, canBeRemoved, canBeOpenedOnGitHub for remote owner", () => { + const dbItem = createRemoteOwnerDbItem(); + + const actions = getDbItemActions(dbItem); + + expect(actions).toEqual([ + "canBeSelected", + "canBeRemoved", + "canBeOpenedOnGitHub", + ]); + }); + + it("should set canBeSelected, canBeRemoved, canBeOpenedOnGitHub for remote db", () => { + const dbItem = createRemoteRepoDbItem(); + + const actions = getDbItemActions(dbItem); + + expect(actions).toEqual([ + "canBeSelected", + "canBeRemoved", + "canBeOpenedOnGitHub", + ]); + }); + + it("should not set canBeSelected for remote db that is already selected", () => { + const dbItem = createRemoteRepoDbItem({ selected: true }); + + const actions = getDbItemActions(dbItem); + + expect(actions.includes("canBeSelected")).toBeFalsy(); + }); +}); + +describe("getGitHubUrl", () => { + it("should return the correct url for a remote owner", () => { + const dbItem = createRemoteOwnerDbItem(); + + const actualUrl = getGitHubUrl(dbItem); + const expectedUrl = `https://github.com/${dbItem.ownerName}`; + + expect(actualUrl).toEqual(expectedUrl); + }); + + it("should return the correct url for a remote repo", () => { + const dbItem = createRemoteRepoDbItem(); + + const actualUrl = getGitHubUrl(dbItem); + const expectedUrl = `https://github.com/${dbItem.repoFullName}`; + + expect(actualUrl).toEqual(expectedUrl); + }); + + it("should return undefined for other remote db items", () => { + const dbItem0 = createRootRemoteDbItem(); + const dbItem1 = createRemoteSystemDefinedListDbItem(); + const dbItem2 = createVariantAnalysisUserDefinedListDbItem(); + + const actualUrl0 = getGitHubUrl(dbItem0); + const actualUrl1 = getGitHubUrl(dbItem1); + const actualUrl2 = getGitHubUrl(dbItem2); + + expect(actualUrl0).toBeUndefined(); + expect(actualUrl1).toBeUndefined(); + expect(actualUrl2).toBeUndefined(); + }); + + it("should return undefined for local db items", () => { + const dbItem0 = createRootLocalDbItem(); + const dbItem1 = createLocalDatabaseDbItem(); + const dbItem2 = createLocalListDbItem(); + + const actualUrl0 = getGitHubUrl(dbItem0); + const actualUrl1 = getGitHubUrl(dbItem1); + const actualUrl2 = getGitHubUrl(dbItem2); + + expect(actualUrl0).toBeUndefined(); + expect(actualUrl1).toBeUndefined(); + expect(actualUrl2).toBeUndefined(); + }); +}); diff --git a/extensions/ql-vscode/test/jest.config.ts b/extensions/ql-vscode/test/unit-tests/jest.config.ts similarity index 99% rename from extensions/ql-vscode/test/jest.config.ts rename to extensions/ql-vscode/test/unit-tests/jest.config.ts index 457136625..2e73bfeed 100644 --- a/extensions/ql-vscode/test/jest.config.ts +++ b/extensions/ql-vscode/test/unit-tests/jest.config.ts @@ -167,7 +167,7 @@ const config: Config = { "^.+\\.tsx?$": [ "ts-jest", { - tsconfig: "/tsconfig.json", + tsconfig: "/../tsconfig.json", }, ], node_modules: [ diff --git a/extensions/ql-vscode/test/jest.setup.ts b/extensions/ql-vscode/test/unit-tests/jest.setup.ts similarity index 100% rename from extensions/ql-vscode/test/jest.setup.ts rename to extensions/ql-vscode/test/unit-tests/jest.setup.ts diff --git a/extensions/ql-vscode/test/pure-tests/log-scanner.test.ts b/extensions/ql-vscode/test/unit-tests/log-scanner.test.ts similarity index 96% rename from extensions/ql-vscode/test/pure-tests/log-scanner.test.ts rename to extensions/ql-vscode/test/unit-tests/log-scanner.test.ts index 9acb80b62..44d035921 100644 --- a/extensions/ql-vscode/test/pure-tests/log-scanner.test.ts +++ b/extensions/ql-vscode/test/unit-tests/log-scanner.test.ts @@ -40,7 +40,7 @@ describe("log scanners", () => { scanners.registerLogScannerProvider(new JoinOrderScannerProvider(() => 50)); const summaryPath = join( __dirname, - "evaluator-log-summaries/bad-join-order.jsonl", + "data/evaluator-log-summaries/bad-join-order.jsonl", ); const problemReporter = new TestProblemReporter(); await scanners.scanLog(summaryPath, problemReporter); diff --git a/extensions/ql-vscode/test/pure-tests/date.test.ts b/extensions/ql-vscode/test/unit-tests/pure/date.test.ts similarity index 54% rename from extensions/ql-vscode/test/pure-tests/date.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/date.test.ts index b2b7c03ae..e05824dd5 100644 --- a/extensions/ql-vscode/test/pure-tests/date.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/date.test.ts @@ -1,8 +1,8 @@ -import { formatDate } from "../../src/pure/date"; +import { formatDate } from "../../../src/pure/date"; describe("Date", () => { it("should return a formatted date", () => { - expect(formatDate(new Date(1663326904000))).toBe("Sep 16, 11:15 AM"); + expect(formatDate(new Date(1663326904000))).toBe("Sep 16, 2022, 11:15 AM"); expect(formatDate(new Date(1631783704000))).toBe("Sep 16, 2021, 9:15 AM"); }); }); diff --git a/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts b/extensions/ql-vscode/test/unit-tests/pure/disposable-object.test.ts similarity index 97% rename from extensions/ql-vscode/test/pure-tests/disposable-object.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/disposable-object.test.ts index edaaba1de..bd63302f9 100644 --- a/extensions/ql-vscode/test/pure-tests/disposable-object.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/disposable-object.test.ts @@ -1,4 +1,4 @@ -import { DisposableObject } from "../../src/pure/disposable-object"; +import { DisposableObject } from "../../../src/pure/disposable-object"; describe("DisposableObject and DisposeHandler", () => { const disposable1 = { diff --git a/extensions/ql-vscode/test/pure-tests/files.test.ts b/extensions/ql-vscode/test/unit-tests/pure/files.test.ts similarity index 51% rename from extensions/ql-vscode/test/pure-tests/files.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/files.test.ts index 8c3dfd897..8f3ddec60 100644 --- a/extensions/ql-vscode/test/pure-tests/files.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/files.test.ts @@ -1,13 +1,14 @@ -import { join, dirname } from "path"; +import { join } from "path"; import { gatherQlFiles, getDirectoryNamesInsidePath, -} from "../../src/pure/files"; + pathsEqual, +} from "../../../src/pure/files"; describe("files", () => { - const dataDir = join(dirname(__dirname), "data"); - const data2Dir = join(dirname(__dirname), "data2"); + const dataDir = join(__dirname, "../../data"); + const data2Dir = join(__dirname, "../../data2"); describe("gatherQlFiles", () => { it("should find one file", async () => { @@ -100,3 +101,90 @@ describe("files", () => { }); }); }); + +describe("pathsEqual", () => { + const testCases: Array<{ + path1: string; + path2: string; + platform: NodeJS.Platform; + expected: boolean; + }> = [ + { + path1: + "/home/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + path2: + "/home/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + platform: "linux", + expected: true, + }, + { + path1: + "/HOME/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + path2: + "/home/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + platform: "linux", + expected: false, + }, + { + path1: + "/home/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + path2: + "\\home\\github\\projects\\vscode-codeql-starter\\codeql-custom-queries-javascript\\example.ql", + platform: "linux", + expected: false, + }, + { + path1: + "C:/Users/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + path2: + "C:/Users/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + platform: "win32", + expected: true, + }, + { + path1: + "C:/Users/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + path2: + "c:/Users/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + platform: "win32", + expected: true, + }, + { + path1: + "C:/Users/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + path2: + "D:/Users/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + platform: "win32", + expected: false, + }, + { + path1: + "C:/Users/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + path2: + "C:\\Users\\github\\projects\\vscode-codeql-starter\\codeql-custom-queries-javascript\\example.ql", + platform: "win32", + expected: true, + }, + { + path1: + "C:/Users/github/projects/vscode-codeql-starter/codeql-custom-queries-javascript/example.ql", + path2: + "D:\\Users\\github\\projects\\vscode-codeql-starter\\codeql-custom-queries-javascript\\example.ql", + platform: "win32", + expected: false, + }, + ]; + + test.each(testCases)( + "$path1 and $path2 are equal on $platform = $expected", + ({ path1, path2, platform, expected }) => { + if (platform !== process.platform) { + // We're using the platform-specific path.resolve, so we can't really run + // these tests on all platforms. + return; + } + + expect(pathsEqual(path1, path2, platform)).toEqual(expected); + }, + ); +}); diff --git a/extensions/ql-vscode/test/pure-tests/helpers-pure.test.ts b/extensions/ql-vscode/test/unit-tests/pure/helpers-pure.test.ts similarity index 87% rename from extensions/ql-vscode/test/pure-tests/helpers-pure.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/helpers-pure.test.ts index b60636ece..d0d773ca3 100644 --- a/extensions/ql-vscode/test/pure-tests/helpers-pure.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/helpers-pure.test.ts @@ -1,4 +1,4 @@ -import { asyncFilter, getErrorMessage } from "../../src/pure/helpers-pure"; +import { asyncFilter, getErrorMessage } from "../../../src/pure/helpers-pure"; describe("helpers-pure", () => { it("should filter asynchronously", async () => { diff --git a/extensions/ql-vscode/test/pure-tests/location.test.ts b/extensions/ql-vscode/test/unit-tests/pure/location.test.ts similarity index 98% rename from extensions/ql-vscode/test/pure-tests/location.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/location.test.ts index d085aa02c..c5d3cbfc3 100644 --- a/extensions/ql-vscode/test/pure-tests/location.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/location.test.ts @@ -1,7 +1,7 @@ import { tryGetRemoteLocation, tryGetResolvableLocation, -} from "../../src/pure/bqrs-utils"; +} from "../../../src/pure/bqrs-utils"; describe("processing string locations", () => { it("should detect Windows whole-file locations", () => { diff --git a/extensions/ql-vscode/test/pure-tests/log-summary-parser.test.ts b/extensions/ql-vscode/test/unit-tests/pure/log-summary-parser.test.ts similarity index 85% rename from extensions/ql-vscode/test/pure-tests/log-summary-parser.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/log-summary-parser.test.ts index a6baa878d..43069b13f 100644 --- a/extensions/ql-vscode/test/pure-tests/log-summary-parser.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/log-summary-parser.test.ts @@ -1,13 +1,13 @@ import { join } from "path"; -import { parseViewerData } from "../../src/pure/log-summary-parser"; +import { parseViewerData } from "../../../src/pure/log-summary-parser"; describe("Evaluator log summary tests", () => { describe("for a valid summary text", () => { it("should return only valid EvalLogData objects", async () => { const validSummaryPath = join( __dirname, - "evaluator-log-summaries/valid-summary.jsonl", + "../data/evaluator-log-summaries/valid-summary.jsonl", ); const logDataItems = await parseViewerData(validSummaryPath); expect(logDataItems).toBeDefined(); @@ -29,7 +29,7 @@ describe("Evaluator log summary tests", () => { it("should not parse a summary header object", async () => { const invalidHeaderPath = join( __dirname, - "evaluator-log-summaries/invalid-header.jsonl", + "../data/evaluator-log-summaries/invalid-header.jsonl", ); const logDataItems = await parseViewerData(invalidHeaderPath); expect(logDataItems.length).toBe(0); @@ -38,7 +38,7 @@ describe("Evaluator log summary tests", () => { it("should not parse a log event missing RA or millis fields", async () => { const invalidSummaryPath = join( __dirname, - "evaluator-log-summaries/invalid-summary.jsonl", + "../data/evaluator-log-summaries/invalid-summary.jsonl", ); const logDataItems = await parseViewerData(invalidSummaryPath); expect(logDataItems.length).toBe(0); diff --git a/extensions/ql-vscode/test/pure-tests/number.test.ts b/extensions/ql-vscode/test/unit-tests/pure/number.test.ts similarity index 80% rename from extensions/ql-vscode/test/pure-tests/number.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/number.test.ts index b74bb6d09..5e568e108 100644 --- a/extensions/ql-vscode/test/pure-tests/number.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/number.test.ts @@ -1,4 +1,4 @@ -import { formatDecimal } from "../../src/pure/number"; +import { formatDecimal } from "../../../src/pure/number"; describe("Number", () => { it("should return a formatted decimal", () => { diff --git a/extensions/ql-vscode/test/pure-tests/sarif-utils.test.ts b/extensions/ql-vscode/test/unit-tests/pure/sarif-utils.test.ts similarity index 99% rename from extensions/ql-vscode/test/pure-tests/sarif-utils.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/sarif-utils.test.ts index a6a3c0c44..713f716af 100644 --- a/extensions/ql-vscode/test/pure-tests/sarif-utils.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/sarif-utils.test.ts @@ -5,7 +5,7 @@ import { parseSarifLocation, parseSarifPlainTextMessage, unescapeSarifText, -} from "../../src/pure/sarif-utils"; +} from "../../../src/pure/sarif-utils"; describe("parsing sarif", () => { it("should be able to parse a simple message from the spec", async () => { diff --git a/extensions/ql-vscode/test/pure-tests/time.test.ts b/extensions/ql-vscode/test/unit-tests/pure/time.test.ts similarity index 98% rename from extensions/ql-vscode/test/pure-tests/time.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/time.test.ts index ba549f6bb..474903ccd 100644 --- a/extensions/ql-vscode/test/pure-tests/time.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/time.test.ts @@ -1,4 +1,4 @@ -import { humanizeRelativeTime, humanizeUnit } from "../../src/pure/time"; +import { humanizeRelativeTime, humanizeUnit } from "../../../src/pure/time"; describe("Time", () => { it("should return a humanized unit", () => { diff --git a/extensions/ql-vscode/test/pure-tests/word.test.ts b/extensions/ql-vscode/test/unit-tests/pure/word.test.ts similarity index 92% rename from extensions/ql-vscode/test/pure-tests/word.test.ts rename to extensions/ql-vscode/test/unit-tests/pure/word.test.ts index 5107edd0f..3075ccbed 100644 --- a/extensions/ql-vscode/test/pure-tests/word.test.ts +++ b/extensions/ql-vscode/test/unit-tests/pure/word.test.ts @@ -1,4 +1,4 @@ -import { pluralize } from "../../src/pure/word"; +import { pluralize } from "../../../src/pure/word"; describe("word helpers", () => { describe("pluralize", () => { diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/gh-api/gh-api-client.test.ts b/extensions/ql-vscode/test/unit-tests/remote-queries/gh-api/gh-api-client.test.ts similarity index 96% rename from extensions/ql-vscode/test/pure-tests/remote-queries/gh-api/gh-api-client.test.ts rename to extensions/ql-vscode/test/unit-tests/remote-queries/gh-api/gh-api-client.test.ts index 3bca3b3c5..930a12302 100644 --- a/extensions/ql-vscode/test/pure-tests/remote-queries/gh-api/gh-api-client.test.ts +++ b/extensions/ql-vscode/test/unit-tests/remote-queries/gh-api/gh-api-client.test.ts @@ -11,7 +11,7 @@ import { submitVariantAnalysis, } from "../../../../src/remote-queries/gh-api/gh-api-client"; import { Credentials } from "../../../../src/authentication"; -import { createMockSubmission } from "../../../../src/vscode-tests/factories/remote-queries/shared/variant-analysis-submission"; +import { createMockSubmission } from "../../../factories/remote-queries/shared/variant-analysis-submission"; import { MockGitHubApiServer } from "../../../../src/mocks/mock-gh-api-server"; import { response } from "../../../../src/mocks/scenarios/problem-query-success/0-getRepo.json"; diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/analyses-results.json b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/analyses-results.json similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/analyses-results.json rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/analyses-results.json diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/_summary.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/_summary.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/_summary.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/_summary.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/github-codeql.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/github-codeql.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/github-codeql.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/github-codeql.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/meteor-meteor.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/meteor-meteor.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/meteor-meteor.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/expected/meteor-meteor.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/path-problem-query.json b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/path-problem-query.json similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/path-problem-query.json rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/path-problem/path-problem-query.json diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/analyses-results.json b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/analyses-results.json similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/analyses-results.json rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/analyses-results.json diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/_summary.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/_summary.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/_summary.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/_summary.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/github-codeql.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/github-codeql.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/github-codeql.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/github-codeql.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/meteor-meteor.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/meteor-meteor.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/meteor-meteor.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/expected/meteor-meteor.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/problem-query.json b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/problem-query.json similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/interpreted-results/problem/problem-query.json rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/interpreted-results/problem/problem-query.json diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/analyses-results.json b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/analyses-results.json similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/analyses-results.json rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/analyses-results.json diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/expected/_summary.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/expected/_summary.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/expected/_summary.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/expected/_summary.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/expected/github-codeql.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/expected/github-codeql.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/expected/github-codeql.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/expected/github-codeql.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/expected/meteor-meteor.md b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/expected/meteor-meteor.md similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/expected/meteor-meteor.md rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/expected/meteor-meteor.md diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/query.json b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/query.json similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/data/raw-results/query.json rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/data/raw-results/query.json diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/markdown-generation.test.ts b/extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/markdown-generation.test.ts similarity index 100% rename from extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/markdown-generation.test.ts rename to extensions/ql-vscode/test/unit-tests/remote-queries/markdown-generation/markdown-generation.test.ts diff --git a/extensions/ql-vscode/test/pure-tests/remote-queries/variant-analysis-processor.test.ts b/extensions/ql-vscode/test/unit-tests/remote-queries/variant-analysis-processor.test.ts similarity index 92% rename from extensions/ql-vscode/test/pure-tests/remote-queries/variant-analysis-processor.test.ts rename to extensions/ql-vscode/test/unit-tests/remote-queries/variant-analysis-processor.test.ts index 8204daa7a..cc5fcbf46 100644 --- a/extensions/ql-vscode/test/pure-tests/remote-queries/variant-analysis-processor.test.ts +++ b/extensions/ql-vscode/test/unit-tests/remote-queries/variant-analysis-processor.test.ts @@ -13,11 +13,11 @@ import { import { createMockScannedRepo, createMockScannedRepos, -} from "../../../src/vscode-tests/factories/remote-queries/gh-api/scanned-repositories"; -import { createMockSkippedRepos } from "../../../src/vscode-tests/factories/remote-queries/gh-api/skipped-repositories"; -import { createMockApiResponse } from "../../../src/vscode-tests/factories/remote-queries/gh-api/variant-analysis-api-response"; -import { createMockSubmission } from "../../../src/vscode-tests/factories/remote-queries/shared/variant-analysis-submission"; -import { createMockVariantAnalysisRepoTask } from "../../../src/vscode-tests/factories/remote-queries/gh-api/variant-analysis-repo-task"; +} from "../../factories/remote-queries/gh-api/scanned-repositories"; +import { createMockSkippedRepos } from "../../factories/remote-queries/gh-api/skipped-repositories"; +import { createMockApiResponse } from "../../factories/remote-queries/gh-api/variant-analysis-api-response"; +import { createMockSubmission } from "../../factories/remote-queries/shared/variant-analysis-submission"; +import { createMockVariantAnalysisRepoTask } from "../../factories/remote-queries/gh-api/variant-analysis-repo-task"; describe(processVariantAnalysis.name, () => { const scannedRepos = createMockScannedRepos(); diff --git a/extensions/ql-vscode/test/pure-tests/sarif-processing.test.ts b/extensions/ql-vscode/test/unit-tests/sarif-processing.test.ts similarity index 100% rename from extensions/ql-vscode/test/pure-tests/sarif-processing.test.ts rename to extensions/ql-vscode/test/unit-tests/sarif-processing.test.ts diff --git a/extensions/ql-vscode/test/pure-tests/text-utils.test.ts b/extensions/ql-vscode/test/unit-tests/text-utils.test.ts similarity index 100% rename from extensions/ql-vscode/test/pure-tests/text-utils.test.ts rename to extensions/ql-vscode/test/unit-tests/text-utils.test.ts diff --git a/extensions/ql-vscode/test/pure-tests/variant-analysis-filter-sort.test.ts b/extensions/ql-vscode/test/unit-tests/variant-analysis-filter-sort.test.ts similarity index 100% rename from extensions/ql-vscode/test/pure-tests/variant-analysis-filter-sort.test.ts rename to extensions/ql-vscode/test/unit-tests/variant-analysis-filter-sort.test.ts diff --git a/extensions/ql-vscode/test/pure-tests/variant-analysis.test.ts b/extensions/ql-vscode/test/unit-tests/variant-analysis.test.ts similarity index 95% rename from extensions/ql-vscode/test/pure-tests/variant-analysis.test.ts rename to extensions/ql-vscode/test/unit-tests/variant-analysis.test.ts index 71b5bb04e..80297fc52 100644 --- a/extensions/ql-vscode/test/pure-tests/variant-analysis.test.ts +++ b/extensions/ql-vscode/test/unit-tests/variant-analysis.test.ts @@ -7,8 +7,8 @@ import { VariantAnalysisRepoStatus, getActionsWorkflowRunUrl, } from "../../src/remote-queries/shared/variant-analysis"; -import { createMockScannedRepo } from "../../src/vscode-tests/factories/remote-queries/shared/scanned-repositories"; -import { createMockVariantAnalysis } from "../../src/vscode-tests/factories/remote-queries/shared/variant-analysis"; +import { createMockScannedRepo } from "../factories/remote-queries/shared/scanned-repositories"; +import { createMockVariantAnalysis } from "../factories/remote-queries/shared/variant-analysis"; describe("parseVariantAnalysisQueryLanguage", () => { it("parses a valid language", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/.eslintrc.js b/extensions/ql-vscode/test/vscode-tests/.eslintrc.js similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/.eslintrc.js rename to extensions/ql-vscode/test/vscode-tests/.eslintrc.js diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-invalid-pack/qlpack.yml b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-invalid-pack/qlpack.yml similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-invalid-pack/qlpack.yml rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-invalid-pack/qlpack.yml diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-no-qlpack/in-pack.ql b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-no-qlpack/in-pack.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-no-qlpack/in-pack.ql rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-no-qlpack/in-pack.ql diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-no-qlpack/lib.qll b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-no-qlpack/lib.qll similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-no-qlpack/lib.qll rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-no-qlpack/lib.qll diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-no-qlpack/not-in-pack.ql b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-no-qlpack/not-in-pack.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-no-qlpack/not-in-pack.ql rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-no-qlpack/not-in-pack.ql diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/qlpack.yml b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/codeql-pack.yml similarity index 54% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/qlpack.yml rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/codeql-pack.yml index 76550f432..d43fadff5 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/qlpack.yml +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/codeql-pack.yml @@ -1,5 +1,4 @@ name: github/remote-query-pack version: 0.0.0 dependencies: - # The workspace reference will be removed before creating the MRVA pack. codeql/javascript-all: '*' diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/not-in-pack.ql b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/not-in-pack.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/not-in-pack.ql rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/not-in-pack.ql diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/otherfolder/lib.qll b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/otherfolder/lib.qll similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/otherfolder/lib.qll rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/otherfolder/lib.qll diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/subfolder/in-pack.ql b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/subfolder/in-pack.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack-nested/subfolder/in-pack.ql rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack-nested/subfolder/in-pack.ql diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/in-pack.ql b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/in-pack.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/in-pack.ql rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/in-pack.ql diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/lib.qll b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/lib.qll similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/lib.qll rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/lib.qll diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/not-in-pack.ql b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/not-in-pack.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/not-in-pack.ql rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/not-in-pack.ql diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/qlpack.yml b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/qlpack.yml similarity index 60% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/qlpack.yml rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/qlpack.yml index 7bb6e9740..1b3f20eee 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/data-remote-qlpack/qlpack.yml +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-remote-qlpack/qlpack.yml @@ -1,4 +1,4 @@ name: github/remote-query-pack version: 0.0.0 dependencies: - codeql/javascript-all: '${workspace}' + codeql/javascript-all: ${workspace} diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data/codeql-pack.lock.yml b/extensions/ql-vscode/test/vscode-tests/cli-integration/data/codeql-pack.lock.yml similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data/codeql-pack.lock.yml rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data/codeql-pack.lock.yml diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data/qlpack.yml b/extensions/ql-vscode/test/vscode-tests/cli-integration/data/qlpack.yml similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data/qlpack.yml rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data/qlpack.yml diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data/simple-javascript-query.ql b/extensions/ql-vscode/test/vscode-tests/cli-integration/data/simple-javascript-query.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data/simple-javascript-query.ql rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data/simple-javascript-query.ql diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data/simple-query.ql b/extensions/ql-vscode/test/vscode-tests/cli-integration/data/simple-query.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data/simple-query.ql rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data/simple-query.ql diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/data/variant-analysis-results.zip b/extensions/ql-vscode/test/vscode-tests/cli-integration/data/variant-analysis-results.zip similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/data/variant-analysis-results.zip rename to extensions/ql-vscode/test/vscode-tests/cli-integration/data/variant-analysis-results.zip diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/databases.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/databases.test.ts similarity index 91% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/databases.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/databases.test.ts index 15fd96e01..d8c4a99bc 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/databases.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/databases.test.ts @@ -1,13 +1,13 @@ import { join } from "path"; import { extensions, CancellationToken, Uri, window } from "vscode"; -import { CodeQLExtensionInterface } from "../../extension"; -import { CodeQLCliServer } from "../../cli"; -import { DatabaseManager } from "../../databases"; +import { CodeQLExtensionInterface } from "../../../src/extension"; +import { CodeQLCliServer } from "../../../src/cli"; +import { DatabaseManager } from "../../../src/databases"; import { importArchiveDatabase, promptImportInternetDatabase, -} from "../../databaseFetcher"; +} from "../../../src/databaseFetcher"; import { cleanDatabases, dbLoc, DB_URL, storagePath } from "./global.helper"; jest.setTimeout(60_000); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/databases/db-panel.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/databases/db-panel.test.ts new file mode 100644 index 000000000..6756cdb5f --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/databases/db-panel.test.ts @@ -0,0 +1,131 @@ +import { commands, extensions, window } from "vscode"; + +import { CodeQLExtensionInterface } from "../../../../src/extension"; +import { readJson } from "fs-extra"; +import * as path from "path"; +import { + DbConfig, + SelectedDbItemKind, +} from "../../../../src/databases/config/db-config"; +import { + AddListQuickPickItem, + RemoteDatabaseQuickPickItem, +} from "../../../../src/databases/ui/db-panel"; +import { DbListKind } from "../../../../src/databases/db-item"; +import { createDbTreeViewItemSystemDefinedList } from "../../../../src/databases/ui/db-tree-view-item"; +import { createRemoteSystemDefinedListDbItem } from "../../../factories/db-item-factories"; + +jest.setTimeout(60_000); + +describe("Db panel UI commands", () => { + let extension: CodeQLExtensionInterface | Record; + let storagePath: string; + + beforeEach(async () => { + extension = await extensions + .getExtension>( + "GitHub.vscode-codeql", + )! + .activate(); + + storagePath = + extension.ctx.storageUri?.fsPath || extension.ctx.globalStorageUri.fsPath; + }); + + it("should add new remote db list", async () => { + // Add db list + jest.spyOn(window, "showQuickPick").mockResolvedValue({ + kind: DbListKind.Remote, + } as AddListQuickPickItem); + jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1"); + await commands.executeCommand( + "codeQLVariantAnalysisRepositories.addNewList", + ); + + // Check db config + const dbConfigFilePath = path.join(storagePath, "workspace-databases.json"); + const dbConfig: DbConfig = await readJson(dbConfigFilePath); + expect(dbConfig.databases.variantAnalysis.repositoryLists).toHaveLength(1); + expect(dbConfig.databases.variantAnalysis.repositoryLists[0].name).toBe( + "my-list-1", + ); + }); + + it("should add new local db list", async () => { + // Add db list + jest.spyOn(window, "showQuickPick").mockResolvedValue({ + kind: DbListKind.Local, + } as AddListQuickPickItem); + jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1"); + await commands.executeCommand( + "codeQLVariantAnalysisRepositories.addNewList", + ); + + // Check db config + const dbConfigFilePath = path.join(storagePath, "workspace-databases.json"); + const dbConfig: DbConfig = await readJson(dbConfigFilePath); + expect(dbConfig.databases.local.lists).toHaveLength(1); + expect(dbConfig.databases.local.lists[0].name).toBe("my-list-1"); + }); + + it("should add new remote repository", async () => { + // Add db + jest.spyOn(window, "showQuickPick").mockResolvedValue({ + kind: "repo", + } as RemoteDatabaseQuickPickItem); + + jest.spyOn(window, "showInputBox").mockResolvedValue("owner1/repo1"); + await commands.executeCommand( + "codeQLVariantAnalysisRepositories.addNewDatabase", + ); + + // Check db config + const dbConfigFilePath = path.join(storagePath, "workspace-databases.json"); + const dbConfig: DbConfig = await readJson(dbConfigFilePath); + expect(dbConfig.databases.variantAnalysis.repositories).toHaveLength(1); + expect(dbConfig.databases.variantAnalysis.repositories[0]).toBe( + "owner1/repo1", + ); + }); + + it("should add new remote owner", async () => { + // Add owner + jest.spyOn(window, "showQuickPick").mockResolvedValue({ + kind: "owner", + } as RemoteDatabaseQuickPickItem); + + jest.spyOn(window, "showInputBox").mockResolvedValue("owner1"); + await commands.executeCommand( + "codeQLVariantAnalysisRepositories.addNewDatabase", + ); + + // Check db config + const dbConfigFilePath = path.join(storagePath, "workspace-databases.json"); + const dbConfig: DbConfig = await readJson(dbConfigFilePath); + expect(dbConfig.databases.variantAnalysis.owners).toHaveLength(1); + expect(dbConfig.databases.variantAnalysis.owners[0]).toBe("owner1"); + }); + + it("should select db item", async () => { + const listName = "top n repos"; + const treeViewItem = createDbTreeViewItemSystemDefinedList( + createRemoteSystemDefinedListDbItem({ listName }), + "label", + "tooltip", + ); + + await commands.executeCommand( + "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu", + treeViewItem, + ); + + // Check db config + const dbConfigFilePath = path.join(storagePath, "workspace-databases.json"); + const dbConfig: DbConfig = await readJson(dbConfigFilePath); + expect(dbConfig.selected).toBeDefined(); + expect(dbConfig.selected).toEqual({ + kind: SelectedDbItemKind.VariantAnalysisSystemDefinedList, + listName, + }); + }); +}); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/global.helper.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/global.helper.ts similarity index 93% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/global.helper.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/global.helper.ts index 7341b3f2b..73a7a69a8 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/global.helper.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/global.helper.ts @@ -2,9 +2,9 @@ import { join } from "path"; import { load, dump } from "js-yaml"; import { realpathSync, readFileSync, writeFileSync } from "fs-extra"; import { commands } from "vscode"; -import { DatabaseManager } from "../../databases"; -import { CodeQLCliServer } from "../../cli"; -import { removeWorkspaceRefs } from "../../remote-queries/run-remote-query"; +import { DatabaseManager } from "../../../src/databases"; +import { CodeQLCliServer } from "../../../src/cli"; +import { removeWorkspaceRefs } from "../../../src/remote-queries/run-remote-query"; // This file contains helpers shared between actual tests. diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts similarity index 84% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/helpers.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts index a9763ccc3..954d053db 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts @@ -1,15 +1,15 @@ import { join } from "path"; import { extensions } from "vscode"; -import { CodeQLCliServer } from "../../cli"; -import { CodeQLExtensionInterface } from "../../extension"; -import { tryGetQueryMetadata } from "../../helpers"; +import { CodeQLCliServer } from "../../../src/cli"; +import { CodeQLExtensionInterface } from "../../../src/extension"; +import { tryGetQueryMetadata } from "../../../src/helpers"; // up to 3 minutes per test jest.setTimeout(3 * 60 * 1000); describe("helpers (with CLI)", () => { - const baseDir = join(__dirname, "../../../src/vscode-tests/cli-integration"); + const baseDir = __dirname; let cli: CodeQLCliServer; diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/jest-runner-cli-integration.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/jest-runner-cli-integration.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/jest-runner-cli-integration.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/jest-runner-cli-integration.ts diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/jest-runner-vscode.config.js b/extensions/ql-vscode/test/vscode-tests/cli-integration/jest-runner-vscode.config.js similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/jest-runner-vscode.config.js rename to extensions/ql-vscode/test/vscode-tests/cli-integration/jest-runner-vscode.config.js diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/jest.config.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/jest.config.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/jest.config.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/jest.config.ts diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/jest.setup.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/jest.setup.ts similarity index 94% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/jest.setup.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/jest.setup.ts index 2ac82cf07..7d13163da 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/jest.setup.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/jest.setup.ts @@ -9,7 +9,7 @@ import fetch from "node-fetch"; import { DB_URL, dbLoc, setStoragePath, storagePath } from "./global.helper"; import * as tmp from "tmp"; import { getTestSetting } from "../test-config"; -import { CUSTOM_CODEQL_PATH_SETTING } from "../../config"; +import { CUSTOM_CODEQL_PATH_SETTING } from "../../../src/config"; import { extensions, workspace } from "vscode"; import baseJestSetup from "../jest.setup"; @@ -64,7 +64,9 @@ beforeAll(async () => { 'No workspace folders found.\nYou will need a local copy of the codeql repo.\nMake sure you specify the path to it in launch.json.\nIt should be something along the lines of "${workspaceRoot}/../codeql" depending on where you have your local copy of the codeql repo.', ); } else { - const codeqlFolder = folders.find((folder) => folder.name === "codeql"); + const codeqlFolder = folders.find((folder) => + ["codeql", "ql"].includes(folder.name), + ); if (!codeqlFolder) { throw new Error( 'No workspace folders found.\nYou will need a local copy of the codeql repo.\nMake sure you specify the path to it in launch.json.\nIt should be something along the lines of "${workspaceRoot}/../codeql" depending on where you have your local copy of the codeql repo.\n\n\n', diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/legacy-query.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts similarity index 93% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/legacy-query.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts index 586fd235b..fa7312ae2 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/legacy-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts @@ -3,15 +3,15 @@ import { join, basename } from "path"; import { dirSync } from "tmp"; import { pathToFileURL } from "url"; import { CancellationTokenSource } from "vscode-jsonrpc"; -import * as messages from "../../pure/legacy-messages"; -import * as qsClient from "../../legacy-query-server/queryserver-client"; -import * as cli from "../../cli"; -import { CellValue } from "../../pure/bqrs-cli-types"; +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 "../../extension"; +import { CodeQLExtensionInterface } from "../../../src/extension"; import { describeWithCodeQL } from "../cli"; -import { QueryServerClient } from "../../legacy-query-server/queryserver-client"; -import { extLogger, ProgressReporter } from "../../common"; +import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client"; +import { extLogger, ProgressReporter } from "../../../src/common"; const baseDir = join(__dirname, "../../../test/data"); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/new-query.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts similarity index 91% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/new-query.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts index 1f7a92cc8..ea448f931 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/new-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts @@ -1,18 +1,18 @@ import { join, basename } from "path"; import { dirSync } from "tmp"; import { CancellationTokenSource } from "vscode-jsonrpc"; -import * as messages from "../../pure/new-messages"; -import * as qsClient from "../../query-server/queryserver-client"; -import * as cli from "../../cli"; -import { CellValue } from "../../pure/bqrs-cli-types"; +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 "../../extension"; +import { CodeQLExtensionInterface } from "../../../src/extension"; import { describeWithCodeQL } from "../cli"; -import { QueryServerClient } from "../../query-server/queryserver-client"; -import { extLogger, ProgressReporter } from "../../common"; -import { QueryResultType } from "../../pure/new-messages"; +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 { importArchiveDatabase } from "../../databaseFetcher"; +import { importArchiveDatabase } from "../../../src/databaseFetcher"; const baseDir = join(__dirname, "../../../test/data"); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/packaging.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts similarity index 89% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/packaging.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts index 6e16a17c3..e368f81b6 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/packaging.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts @@ -1,15 +1,15 @@ import { extensions, QuickPickItem, window } from "vscode"; import { join } from "path"; -import { CodeQLCliServer } from "../../cli"; -import { CodeQLExtensionInterface } from "../../extension"; -import { getErrorMessage } from "../../pure/helpers-pure"; +import { CodeQLCliServer } from "../../../src/cli"; +import { CodeQLExtensionInterface } from "../../../src/extension"; +import { getErrorMessage } from "../../../src/pure/helpers-pure"; -import * as helpers from "../../helpers"; +import * as helpers from "../../../src/helpers"; import { handleDownloadPacks, handleInstallPackDependencies, -} from "../../packaging"; +} from "../../../src/packaging"; // up to 3 minutes per test jest.setTimeout(3 * 60 * 1000); @@ -91,10 +91,7 @@ describe("Packaging commands", () => { }); it("should install valid workspace pack", async () => { - const rootDir = join( - __dirname, - "../../../src/vscode-tests/cli-integration/data", - ); + const rootDir = join(__dirname, "./data"); quickPickSpy.mockResolvedValue([ { label: "integration-test-queries-javascript", @@ -109,10 +106,7 @@ describe("Packaging commands", () => { }); it("should throw an error when installing invalid workspace pack", async () => { - const rootDir = join( - __dirname, - "../../../src/vscode-tests/cli-integration/data-invalid-pack", - ); + const rootDir = join(__dirname, "../data-invalid-pack"); quickPickSpy.mockResolvedValue([ { label: "foo/bar", diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/queries.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts similarity index 92% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/queries.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts index 00a8578a6..889f09b02 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts @@ -15,15 +15,15 @@ import { } from "fs-extra"; import { load, dump } from "js-yaml"; -import { DatabaseItem, DatabaseManager } from "../../databases"; -import { CodeQLExtensionInterface } from "../../extension"; +import { DatabaseItem, DatabaseManager } from "../../../src/databases"; +import { CodeQLExtensionInterface } from "../../../src/extension"; import { cleanDatabases, dbLoc, storagePath } from "./global.helper"; -import { importArchiveDatabase } from "../../databaseFetcher"; -import { CodeQLCliServer } from "../../cli"; +import { importArchiveDatabase } from "../../../src/databaseFetcher"; +import { CodeQLCliServer } from "../../../src/cli"; import { describeWithCodeQL } from "../cli"; -import { tmpDir } from "../../helpers"; -import { createInitialQueryInfo } from "../../run-queries-shared"; -import { QueryRunner } from "../../queryRunner"; +import { tmpDir } from "../../../src/helpers"; +import { createInitialQueryInfo } from "../../../src/run-queries-shared"; +import { QueryRunner } from "../../../src/queryRunner"; jest.setTimeout(20_000); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts similarity index 87% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts index 835a5de7a..57267a29a 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/remote-queries-manager.test.ts @@ -10,22 +10,22 @@ import { } from "vscode"; import { load } from "js-yaml"; -import { QlPack } from "../../../remote-queries/run-remote-query"; -import { CodeQLCliServer } from "../../../cli"; -import { CodeQLExtensionInterface } from "../../../extension"; +import { QlPack } from "../../../../src/remote-queries/run-remote-query"; +import { CodeQLCliServer } from "../../../../src/cli"; +import { CodeQLExtensionInterface } from "../../../../src/extension"; import { setRemoteControllerRepo, setRemoteRepositoryLists, -} from "../../../config"; -import { UserCancellationException } from "../../../commandRunner"; -import * as ghApiClient from "../../../remote-queries/gh-api/gh-api-client"; -import { Repository } from "../../../remote-queries/gh-api/repository"; +} from "../../../../src/config"; +import { UserCancellationException } from "../../../../src/commandRunner"; +import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client"; +import { Repository } from "../../../../src/remote-queries/gh-api/repository"; import { createMockExtensionContext } from "../../no-workspace"; -import { OutputChannelLogger } from "../../../common"; -import { RemoteQueriesSubmission } from "../../../remote-queries/shared/remote-queries"; +import { OutputChannelLogger } from "../../../../src/common"; +import { RemoteQueriesSubmission } from "../../../../src/remote-queries/shared/remote-queries"; import { readBundledPack } from "../../utils/bundled-pack-helpers"; -import { RemoteQueriesManager } from "../../../remote-queries/remote-queries-manager"; -import { Credentials } from "../../../authentication"; +import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager"; +import { Credentials } from "../../../../src/authentication"; import { fixWorkspaceReferences, restoreWorkspaceReferences, @@ -35,10 +35,7 @@ import { jest.setTimeout(3 * 60 * 1000); describe("Remote queries", () => { - const baseDir = join( - __dirname, - "../../../../src/vscode-tests/cli-integration", - ); + const baseDir = join(__dirname, ".."); const qlpackFileWithWorkspaceRefs = getFile( "data-remote-qlpack/qlpack.yml", @@ -194,7 +191,6 @@ describe("Remote queries", () => { // check dependencies. expect(packNames).toContain("javascript-all"); - expect(packNames.length).toBeLessThan(3); }); it("should run a remote query that is not part of a qlpack", async () => { @@ -251,7 +247,6 @@ describe("Remote queries", () => { // check dependencies. expect(packNames).toContain("javascript-all"); - expect(packNames.length).toBeLessThan(3); }); it("should run a remote query that is nested inside a qlpack", async () => { @@ -282,7 +277,7 @@ describe("Remote queries", () => { // check a few files that we know should exist and others that we know should not expect(packFS.fileExists("subfolder/in-pack.ql")).toBe(true); - expect(packFS.fileExists("qlpack.yml")).toBe(true); + expect(packFS.fileExists("codeql-pack.yml")).toBe(true); // depending on the cli version, we should have one of these files expect( packFS.fileExists("qlpack.lock.yml") || @@ -294,13 +289,13 @@ describe("Remote queries", () => { // the compiled pack verifyQlPack( "subfolder/in-pack.ql", - packFS.fileContents("qlpack.yml"), + packFS.fileContents("codeql-pack.yml"), "0.0.0", ); // should have generated a correct qlpack file const qlpackContents: any = load( - packFS.fileContents("qlpack.yml").toString("utf-8"), + packFS.fileContents("codeql-pack.yml").toString("utf-8"), ); expect(qlpackContents.name).toBe("codeql-remote/query"); expect(qlpackContents.version).toBe("0.0.0"); @@ -311,7 +306,6 @@ describe("Remote queries", () => { // check dependencies. expect(packNames).toContain("javascript-all"); - expect(packNames.length).toBeLessThan(3); }); it("should cancel a run before uploading", async () => { @@ -339,22 +333,27 @@ describe("Remote queries", () => { // don't check the build metadata since it is variable delete (qlPack as any).buildMetadata; - expect(qlPack).toEqual({ - name: "codeql-remote/query", - version: packVersion, - dependencies: { - "codeql/javascript-all": "*", - }, - library: false, - defaultSuite: [ - { - description: "Query suite for variant analysis", + expect(qlPack).toEqual( + expect.objectContaining({ + name: "codeql-remote/query", + version: packVersion, + dependencies: { + "codeql/javascript-all": "*", }, - { - query: queryPath, - }, - ], - }); + defaultSuite: [ + { + description: "Query suite for variant analysis", + }, + { + query: queryPath, + }, + ], + }), + ); + + // v2.11.6 and later set this to false. + // Earlier versions don't set it at all. + expect(qlPack.library).toBeFalsy(); } function getFile(file: string): Uri { diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts similarity index 56% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts index 3a2a3863e..808427a57 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts @@ -10,63 +10,59 @@ import { window, workspace, } from "vscode"; -import { CodeQLExtensionInterface } from "../../../extension"; -import { extLogger } from "../../../common"; -import * as config from "../../../config"; +import { CodeQLExtensionInterface } from "../../../../src/extension"; +import { extLogger } from "../../../../src/common"; +import * as config from "../../../../src/config"; import { setRemoteControllerRepo, setRemoteRepositoryLists, -} from "../../../config"; -import * as ghApiClient from "../../../remote-queries/gh-api/gh-api-client"; -import * as ghActionsApiClient from "../../../remote-queries/gh-api/gh-actions-api-client"; -import { Credentials } from "../../../authentication"; +} from "../../../../src/config"; +import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client"; +import * as ghActionsApiClient from "../../../../src/remote-queries/gh-api/gh-actions-api-client"; +import { Credentials } from "../../../../src/authentication"; import * as fs from "fs-extra"; import { join } from "path"; -import { VariantAnalysisManager } from "../../../remote-queries/variant-analysis-manager"; -import { CodeQLCliServer } from "../../../cli"; +import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager"; +import { CodeQLCliServer } from "../../../../src/cli"; import { fixWorkspaceReferences, restoreWorkspaceReferences, storagePath, } from "../global.helper"; -import { VariantAnalysisResultsManager } from "../../../remote-queries/variant-analysis-results-manager"; -import { createMockVariantAnalysis } from "../../factories/remote-queries/shared/variant-analysis"; -import * as VariantAnalysisModule from "../../../remote-queries/shared/variant-analysis"; +import { VariantAnalysisResultsManager } from "../../../../src/remote-queries/variant-analysis-results-manager"; +import { createMockVariantAnalysis } from "../../../factories/remote-queries/shared/variant-analysis"; +import * as VariantAnalysisModule from "../../../../src/remote-queries/shared/variant-analysis"; import { createMockScannedRepo, createMockScannedRepos, -} from "../../factories/remote-queries/shared/scanned-repositories"; +} from "../../../factories/remote-queries/shared/scanned-repositories"; import { VariantAnalysis, VariantAnalysisScannedRepository, VariantAnalysisScannedRepositoryDownloadStatus, VariantAnalysisScannedRepositoryState, VariantAnalysisStatus, -} from "../../../remote-queries/shared/variant-analysis"; -import { createTimestampFile } from "../../../helpers"; -import { createMockVariantAnalysisRepoTask } from "../../factories/remote-queries/gh-api/variant-analysis-repo-task"; +} from "../../../../src/remote-queries/shared/variant-analysis"; +import { createTimestampFile } from "../../../../src/helpers"; +import { createMockVariantAnalysisRepoTask } from "../../../factories/remote-queries/gh-api/variant-analysis-repo-task"; import { VariantAnalysis as VariantAnalysisApiResponse, VariantAnalysisRepoTask, -} from "../../../remote-queries/gh-api/variant-analysis"; -import { createMockApiResponse } from "../../factories/remote-queries/gh-api/variant-analysis-api-response"; -import { UserCancellationException } from "../../../commandRunner"; -import { Repository } from "../../../remote-queries/gh-api/repository"; +} from "../../../../src/remote-queries/gh-api/variant-analysis"; +import { createMockApiResponse } from "../../../factories/remote-queries/gh-api/variant-analysis-api-response"; +import { UserCancellationException } from "../../../../src/commandRunner"; +import { Repository } from "../../../../src/remote-queries/gh-api/repository"; import { defaultFilterSortState, SortKey, -} from "../../../pure/variant-analysis-filter-sort"; -import { DbManager } from "../../../databases/db-manager"; +} from "../../../../src/pure/variant-analysis-filter-sort"; +import { DbManager } from "../../../../src/databases/db-manager"; // up to 3 minutes per test jest.setTimeout(3 * 60 * 1000); describe("Variant Analysis Manager", () => { - let pathExistsStub: jest.SpiedFunction; - let readJsonStub: jest.SpiedFunction; - let outputJsonStub: jest.SpiedFunction; - let writeFileStub: jest.SpiedFunction; let cli: CodeQLCliServer; let cancellationTokenSource: CancellationTokenSource; let variantAnalysisManager: VariantAnalysisManager; @@ -76,16 +72,10 @@ describe("Variant Analysis Manager", () => { let scannedRepos: VariantAnalysisScannedRepository[]; beforeEach(async () => { - pathExistsStub = jest.spyOn(fs, "pathExists"); - readJsonStub = jest.spyOn(fs, "readJson"); - outputJsonStub = jest.spyOn(fs, "outputJson").mockReturnValue(undefined); - writeFileStub = jest.spyOn(fs, "writeFile").mockReturnValue(undefined); - jest.spyOn(extLogger, "log").mockResolvedValue(undefined); jest .spyOn(config, "isVariantAnalysisLiveResultsEnabled") .mockReturnValue(false); - jest.spyOn(fs, "mkdirSync").mockReturnValue(undefined); cancellationTokenSource = new CancellationTokenSource(); @@ -127,10 +117,7 @@ describe("Variant Analysis Manager", () => { let originalDeps: Record | undefined; let executeCommandSpy: jest.SpiedFunction; - const baseDir = join( - __dirname, - "../../../../src/vscode-tests/cli-integration", - ); + const baseDir = join(__dirname, ".."); const qlpackFileWithWorkspaceRefs = getFile( "data-remote-qlpack/qlpack.yml", ).fsPath; @@ -140,7 +127,13 @@ describe("Variant Analysis Manager", () => { } beforeEach(async () => { - writeFileStub.mockRestore(); + const mockCredentials = { + getOctokit: () => + Promise.resolve({ + request: jest.fn(), + }), + } as unknown as Credentials; + jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); // Should not have asked for a language showQuickPickSpy = jest @@ -273,10 +266,6 @@ describe("Variant Analysis Manager", () => { const variantAnalysis = createMockVariantAnalysis({}); describe("when the directory does not exist", () => { - beforeEach(() => { - pathExistsStub.mockImplementation(() => false); - }); - it("should fire the removed event if the file does not exist", async () => { const stub = jest.fn(); variantAnalysisManager.onVariantAnalysisRemoved(stub); @@ -284,16 +273,12 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis); expect(stub).toBeCalledTimes(1); - expect(pathExistsStub).toHaveBeenCalledTimes(1); - expect(pathExistsStub).toBeCalledWith( - join(storagePath, variantAnalysis.id.toString()), - ); }); }); describe("when the directory exists", () => { - beforeEach(() => { - pathExistsStub.mockImplementation(() => true); + beforeEach(async () => { + await fs.ensureDir(join(storagePath, variantAnalysis.id.toString())); }); it("should store the variant analysis", async () => { @@ -302,31 +287,20 @@ describe("Variant Analysis Manager", () => { expect( await variantAnalysisManager.getVariantAnalysis(variantAnalysis.id), ).toEqual(variantAnalysis); - - expect(pathExistsStub).toBeCalledWith( - join(storagePath, variantAnalysis.id.toString()), - ); }); it("should not error if the repo states file does not exist", async () => { - readJsonStub.mockImplementation(() => - Promise.reject(new Error("File does not exist")), - ); - await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis); - expect(readJsonStub).toHaveBeenCalledTimes(1); - expect(readJsonStub).toHaveBeenCalledWith( - join(storagePath, variantAnalysis.id.toString(), "repo_states.json"), - ); expect( await variantAnalysisManager.getRepoStates(variantAnalysis.id), ).toEqual([]); }); it("should read in the repo states if it exists", async () => { - readJsonStub.mockImplementation(() => - Promise.resolve({ + await fs.writeJson( + join(storagePath, variantAnalysis.id.toString(), "repo_states.json"), + { [scannedRepos[0].repository.id]: { repositoryId: scannedRepos[0].repository.id, downloadStatus: @@ -337,15 +311,11 @@ describe("Variant Analysis Manager", () => { downloadStatus: VariantAnalysisScannedRepositoryDownloadStatus.InProgress, }, - }), + }, ); await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis); - expect(readJsonStub).toHaveBeenCalledTimes(1); - expect(readJsonStub).toHaveBeenCalledWith( - join(storagePath, variantAnalysis.id.toString(), "repo_states.json"), - ); expect( await variantAnalysisManager.getRepoStates(variantAnalysis.id), ).toEqual( @@ -367,339 +337,277 @@ describe("Variant Analysis Manager", () => { }); describe("autoDownloadVariantAnalysisResult", () => { - describe("when credentials are invalid", () => { + let arrayBuffer: ArrayBuffer; + + let getVariantAnalysisRepoStub: jest.SpiedFunction< + typeof ghApiClient.getVariantAnalysisRepo + >; + let getVariantAnalysisRepoResultStub: jest.SpiedFunction< + typeof ghApiClient.getVariantAnalysisRepoResult + >; + + let repoStatesPath: string; + + beforeEach(async () => { + const mockCredentials = { + getOctokit: () => + Promise.resolve({ + request: jest.fn(), + }), + } as unknown as Credentials; + jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); + + const sourceFilePath = join( + __dirname, + "../data/variant-analysis-results.zip", + ); + arrayBuffer = fs.readFileSync(sourceFilePath).buffer; + + getVariantAnalysisRepoStub = jest.spyOn( + ghApiClient, + "getVariantAnalysisRepo", + ); + getVariantAnalysisRepoResultStub = jest.spyOn( + ghApiClient, + "getVariantAnalysisRepoResult", + ); + + repoStatesPath = join( + storagePath, + variantAnalysis.id.toString(), + "repo_states.json", + ); + }); + + describe("when the artifact_url is missing", () => { beforeEach(async () => { - jest - .spyOn(Credentials, "initialize") - .mockResolvedValue(undefined as unknown as Credentials); + const dummyRepoTask = createMockVariantAnalysisRepoTask(); + delete dummyRepoTask.artifact_url; + + getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask); + getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer); }); - it("should return early if credentials are wrong", async () => { - try { - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); - } catch (error: any) { - expect(error.message).toBe("Error authenticating with GitHub"); - } + it("should not try to download the result", async () => { + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(getVariantAnalysisRepoResultStub).not.toHaveBeenCalled(); }); }); - describe("when credentials are valid", () => { - let arrayBuffer: ArrayBuffer; - - let getVariantAnalysisRepoStub: jest.SpiedFunction< - typeof ghApiClient.getVariantAnalysisRepo - >; - let getVariantAnalysisRepoResultStub: jest.SpiedFunction< - typeof ghApiClient.getVariantAnalysisRepoResult - >; + describe("when the artifact_url is present", () => { + let dummyRepoTask: VariantAnalysisRepoTask; beforeEach(async () => { - const mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; - jest - .spyOn(Credentials, "initialize") - .mockResolvedValue(mockCredentials); + dummyRepoTask = createMockVariantAnalysisRepoTask(); - const sourceFilePath = join( - __dirname, - "../../../../src/vscode-tests/cli-integration/data/variant-analysis-results.zip", - ); - arrayBuffer = fs.readFileSync(sourceFilePath).buffer; - - getVariantAnalysisRepoStub = jest.spyOn( - ghApiClient, - "getVariantAnalysisRepo", - ); - getVariantAnalysisRepoResultStub = jest.spyOn( - ghApiClient, - "getVariantAnalysisRepoResult", - ); + getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask); + getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer); }); - describe("when the artifact_url is missing", () => { - beforeEach(async () => { - const dummyRepoTask = createMockVariantAnalysisRepoTask(); - delete dummyRepoTask.artifact_url; + it("should return early if variant analysis is cancelled", async () => { + cancellationTokenSource.cancel(); - getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask); - getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer); - }); + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ); - it("should not try to download the result", async () => { - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); + expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled(); + }); - expect(getVariantAnalysisRepoResultStub).not.toHaveBeenCalled(); + it("should fetch a repo task", async () => { + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(getVariantAnalysisRepoStub).toHaveBeenCalled(); + }); + + it("should fetch a repo result", async () => { + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(getVariantAnalysisRepoResultStub).toHaveBeenCalled(); + }); + + it("should skip the download if the repository has already been downloaded", async () => { + // First, do a download so it is downloaded. This avoids having to mock the repo states. + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ); + + getVariantAnalysisRepoStub.mockClear(); + + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled(); + }); + + it("should write the repo state when the download is successful", async () => { + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ); + + await expect(fs.readJson(repoStatesPath)).resolves.toEqual({ + [scannedRepos[0].repository.id]: { + repositoryId: scannedRepos[0].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, + }, }); }); - describe("when the artifact_url is present", () => { - let dummyRepoTask: VariantAnalysisRepoTask; + it("should not write the repo state when the download fails", async () => { + getVariantAnalysisRepoResultStub.mockRejectedValue( + new Error("Failed to download"), + ); - beforeEach(async () => { - dummyRepoTask = createMockVariantAnalysisRepoTask(); - - getVariantAnalysisRepoStub.mockResolvedValue(dummyRepoTask); - getVariantAnalysisRepoResultStub.mockResolvedValue(arrayBuffer); - }); - - it("should return early if variant analysis is cancelled", async () => { - cancellationTokenSource.cancel(); - - await variantAnalysisManager.autoDownloadVariantAnalysisResult( + await expect( + variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, cancellationTokenSource.token, - ); + ), + ).rejects.toThrow(); - expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled(); - }); - - it("should fetch a repo task", async () => { - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(getVariantAnalysisRepoStub).toHaveBeenCalled(); - }); - - it("should fetch a repo result", async () => { - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(getVariantAnalysisRepoResultStub).toHaveBeenCalled(); - }); - - it("should skip the download if the repository has already been downloaded", async () => { - // First, do a download so it is downloaded. This avoids having to mock the repo states. - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); - - getVariantAnalysisRepoStub.mockClear(); - - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled(); - }); - - it("should write the repo state when the download is successful", async () => { - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(outputJsonStub).toHaveBeenCalledWith( - join( - storagePath, - variantAnalysis.id.toString(), - "repo_states.json", - ), - { - [scannedRepos[0].repository.id]: { - repositoryId: scannedRepos[0].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, - }, - }, - ); - }); - - it("should not write the repo state when the download fails", async () => { - getVariantAnalysisRepoResultStub.mockRejectedValue( - new Error("Failed to download"), - ); - - await expect( - variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ), - ).rejects.toThrow(); - - expect(outputJsonStub).not.toHaveBeenCalled(); - }); - - it("should have a failed repo state when the repo task API fails", async () => { - getVariantAnalysisRepoStub.mockRejectedValueOnce( - new Error("Failed to download"), - ); - - await expect( - variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ), - ).rejects.toThrow(); - - expect(outputJsonStub).not.toHaveBeenCalled(); - - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[1], - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(outputJsonStub).toHaveBeenCalledWith( - join( - storagePath, - variantAnalysis.id.toString(), - "repo_states.json", - ), - { - [scannedRepos[0].repository.id]: { - repositoryId: scannedRepos[0].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.Failed, - }, - [scannedRepos[1].repository.id]: { - repositoryId: scannedRepos[1].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, - }, - }, - ); - }); - - it("should have a failed repo state when the download fails", async () => { - getVariantAnalysisRepoResultStub.mockRejectedValueOnce( - new Error("Failed to download"), - ); - - await expect( - variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ), - ).rejects.toThrow(); - - expect(outputJsonStub).not.toHaveBeenCalled(); - - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[1], - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(outputJsonStub).toHaveBeenCalledWith( - join( - storagePath, - variantAnalysis.id.toString(), - "repo_states.json", - ), - { - [scannedRepos[0].repository.id]: { - repositoryId: scannedRepos[0].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.Failed, - }, - [scannedRepos[1].repository.id]: { - repositoryId: scannedRepos[1].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, - }, - }, - ); - }); - - it("should update the repo state correctly", async () => { - mockRepoStates({ - [scannedRepos[1].repository.id]: { - repositoryId: scannedRepos[1].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, - }, - [scannedRepos[2].repository.id]: { - repositoryId: scannedRepos[2].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.InProgress, - }, - }); - - await variantAnalysisManager.rehydrateVariantAnalysis( - variantAnalysis, - ); - - expect(pathExistsStub).toBeCalledWith( - join(storagePath, variantAnalysis.id.toString()), - ); - expect(readJsonStub).toHaveBeenCalledTimes(1); - expect(readJsonStub).toHaveBeenCalledWith( - join( - storagePath, - variantAnalysis.id.toString(), - "repo_states.json", - ), - ); - - pathExistsStub.mockRestore(); - - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(outputJsonStub).toHaveBeenCalledWith( - join( - storagePath, - variantAnalysis.id.toString(), - "repo_states.json", - ), - { - [scannedRepos[1].repository.id]: { - repositoryId: scannedRepos[1].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, - }, - [scannedRepos[2].repository.id]: { - repositoryId: scannedRepos[2].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.InProgress, - }, - [scannedRepos[0].repository.id]: { - repositoryId: scannedRepos[0].repository.id, - downloadStatus: - VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, - }, - }, - ); - }); - - function mockRepoStates( - repoStates: Record, - ) { - pathExistsStub.mockImplementation(() => true); - // This will read in the correct repo states - readJsonStub.mockImplementation(() => Promise.resolve(repoStates)); - } + await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false); }); + + it("should have a failed repo state when the repo task API fails", async () => { + getVariantAnalysisRepoStub.mockRejectedValueOnce( + new Error("Failed to download"), + ); + + await expect( + variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ), + ).rejects.toThrow(); + + await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false); + + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[1], + variantAnalysis, + cancellationTokenSource.token, + ); + + await expect(fs.readJson(repoStatesPath)).resolves.toEqual({ + [scannedRepos[0].repository.id]: { + repositoryId: scannedRepos[0].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.Failed, + }, + [scannedRepos[1].repository.id]: { + repositoryId: scannedRepos[1].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, + }, + }); + }); + + it("should have a failed repo state when the download fails", async () => { + getVariantAnalysisRepoResultStub.mockRejectedValueOnce( + new Error("Failed to download"), + ); + + await expect( + variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ), + ).rejects.toThrow(); + + await expect(fs.pathExists(repoStatesPath)).resolves.toBe(false); + + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[1], + variantAnalysis, + cancellationTokenSource.token, + ); + + await expect(fs.readJson(repoStatesPath)).resolves.toEqual({ + [scannedRepos[0].repository.id]: { + repositoryId: scannedRepos[0].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.Failed, + }, + [scannedRepos[1].repository.id]: { + repositoryId: scannedRepos[1].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, + }, + }); + }); + + it("should update the repo state correctly", async () => { + await mockRepoStates({ + [scannedRepos[1].repository.id]: { + repositoryId: scannedRepos[1].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, + }, + [scannedRepos[2].repository.id]: { + repositoryId: scannedRepos[2].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.InProgress, + }, + }); + + await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis); + + await variantAnalysisManager.autoDownloadVariantAnalysisResult( + scannedRepos[0], + variantAnalysis, + cancellationTokenSource.token, + ); + + await expect(fs.readJson(repoStatesPath)).resolves.toEqual({ + [scannedRepos[1].repository.id]: { + repositoryId: scannedRepos[1].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, + }, + [scannedRepos[2].repository.id]: { + repositoryId: scannedRepos[2].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.InProgress, + }, + [scannedRepos[0].repository.id]: { + repositoryId: scannedRepos[0].repository.id, + downloadStatus: + VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, + }, + }); + }); + + async function mockRepoStates( + repoStates: Record, + ) { + await fs.outputJson(repoStatesPath, repoStates); + } }); }); @@ -744,7 +652,6 @@ describe("Variant Analysis Manager", () => { let removeAnalysisResultsStub: jest.SpiedFunction< typeof variantAnalysisResultsManager.removeAnalysisResults >; - let removeStorageStub: jest.SpiedFunction; let dummyVariantAnalysis: VariantAnalysis; beforeEach(async () => { @@ -753,25 +660,24 @@ describe("Variant Analysis Manager", () => { removeAnalysisResultsStub = jest .spyOn(variantAnalysisResultsManager, "removeAnalysisResults") .mockReturnValue(undefined); - - removeStorageStub = jest.spyOn(fs, "remove").mockReturnValue(undefined); }); it("should remove variant analysis", async () => { - pathExistsStub.mockImplementation(() => true); + await fs.ensureDir(join(storagePath, dummyVariantAnalysis.id.toString())); + await variantAnalysisManager.rehydrateVariantAnalysis( dummyVariantAnalysis, ); - expect(pathExistsStub).toBeCalledWith( - join(storagePath, dummyVariantAnalysis.id.toString()), - ); expect(variantAnalysisManager.variantAnalysesSize).toBe(1); await variantAnalysisManager.removeVariantAnalysis(dummyVariantAnalysis); expect(removeAnalysisResultsStub).toBeCalledTimes(1); - expect(removeStorageStub).toBeCalledTimes(1); expect(variantAnalysisManager.variantAnalysesSize).toBe(0); + + await expect( + fs.pathExists(join(storagePath, dummyVariantAnalysis.id.toString())), + ).resolves.toBe(false); }); }); @@ -876,6 +782,8 @@ describe("Variant Analysis Manager", () => { let variantAnalysisStorageLocation: string; + let mockCredentials: Credentials; + beforeEach(async () => { variantAnalysis = createMockVariantAnalysis({}); @@ -889,82 +797,54 @@ describe("Variant Analysis Manager", () => { ); await createTimestampFile(variantAnalysisStorageLocation); await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis); + + mockCredentials = { + getOctokit: () => + Promise.resolve({ + request: jest.fn(), + }), + } as unknown as Credentials; + jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); }); afterEach(() => { fs.rmSync(variantAnalysisStorageLocation, { recursive: true }); }); - describe("when the credentials are invalid", () => { - beforeEach(async () => { - jest - .spyOn(Credentials, "initialize") - .mockResolvedValue(undefined as unknown as Credentials); - }); - - it("should return early", async () => { - try { - await variantAnalysisManager.cancelVariantAnalysis( - variantAnalysis.id, - ); - } catch (error: any) { - expect(error.message).toBe("Error authenticating with GitHub"); - } - }); + it("should return early if the variant analysis is not found", async () => { + try { + await variantAnalysisManager.cancelVariantAnalysis( + variantAnalysis.id + 100, + ); + } catch (error: any) { + expect(error.message).toBe( + `No variant analysis with id: ${variantAnalysis.id + 100}`, + ); + } }); - describe("when the credentials are valid", () => { - let mockCredentials: Credentials; - - beforeEach(async () => { - mockCredentials = { - getOctokit: () => - Promise.resolve({ - request: jest.fn(), - }), - } as unknown as Credentials; - jest - .spyOn(Credentials, "initialize") - .mockResolvedValue(mockCredentials); + it("should return early if the variant analysis does not have an actions workflow run id", async () => { + await variantAnalysisManager.onVariantAnalysisUpdated({ + ...variantAnalysis, + actionsWorkflowRunId: undefined, }); - it("should return early if the variant analysis is not found", async () => { - try { - await variantAnalysisManager.cancelVariantAnalysis( - variantAnalysis.id + 100, - ); - } catch (error: any) { - expect(error.message).toBe( - `No variant analysis with id: ${variantAnalysis.id + 100}`, - ); - } - }); - - it("should return early if the variant analysis does not have an actions workflow run id", async () => { - await variantAnalysisManager.onVariantAnalysisUpdated({ - ...variantAnalysis, - actionsWorkflowRunId: undefined, - }); - - try { - await variantAnalysisManager.cancelVariantAnalysis( - variantAnalysis.id, - ); - } catch (error: any) { - expect(error.message).toBe( - `No workflow run id for variant analysis with id: ${variantAnalysis.id}`, - ); - } - }); - - it("should return cancel if valid", async () => { + try { await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id); - - expect(mockCancelVariantAnalysis).toBeCalledWith( - mockCredentials, - variantAnalysis, + } catch (error: any) { + expect(error.message).toBe( + `No workflow run id for variant analysis with id: ${variantAnalysis.id}`, ); - }); + } + }); + + it("should return cancel if valid", async () => { + await variantAnalysisManager.cancelVariantAnalysis(variantAnalysis.id); + + expect(mockCancelVariantAnalysis).toBeCalledWith( + mockCredentials, + variantAnalysis, + ); }); }); @@ -1076,61 +956,131 @@ describe("Variant Analysis Manager", () => { expect(writeTextStub).toBeCalledTimes(1); }); - it("should be valid JSON when put in object", async () => { - await variantAnalysisManager.copyRepoListToClipboard( - variantAnalysis.id, - ); + describe("variantAnalysisReposPanel true", () => { + beforeEach(() => { + jest + .spyOn(config, "isVariantAnalysisReposPanelEnabled") + .mockReturnValue(true); + }); - const text = writeTextStub.mock.calls[0][0]; + it("should be valid JSON when put in object", async () => { + await variantAnalysisManager.copyRepoListToClipboard( + variantAnalysis.id, + ); - const parsed = JSON.parse(`{${text}}`); + const text = writeTextStub.mock.calls[0][0]; - expect(parsed).toEqual({ - "new-repo-list": [ - scannedRepos[4].repository.fullName, - scannedRepos[2].repository.fullName, - scannedRepos[0].repository.fullName, - ], + const parsed = JSON.parse(`${text}`); + + expect(parsed).toEqual({ + name: "new-repo-list", + repositories: [ + scannedRepos[4].repository.fullName, + scannedRepos[2].repository.fullName, + scannedRepos[0].repository.fullName, + ], + }); + }); + + it("should use the sort key", async () => { + await variantAnalysisManager.copyRepoListToClipboard( + variantAnalysis.id, + { + ...defaultFilterSortState, + sortKey: SortKey.ResultsCount, + }, + ); + + const text = writeTextStub.mock.calls[0][0]; + + const parsed = JSON.parse(`${text}`); + + expect(parsed).toEqual({ + name: "new-repo-list", + repositories: [ + scannedRepos[2].repository.fullName, + scannedRepos[0].repository.fullName, + scannedRepos[4].repository.fullName, + ], + }); + }); + + it("should use the search value", async () => { + await variantAnalysisManager.copyRepoListToClipboard( + variantAnalysis.id, + { + ...defaultFilterSortState, + searchValue: "ban", + }, + ); + + const text = writeTextStub.mock.calls[0][0]; + + const parsed = JSON.parse(`${text}`); + + expect(parsed).toEqual({ + name: "new-repo-list", + repositories: [scannedRepos[4].repository.fullName], + }); }); }); + describe("variantAnalysisReposPanel false", () => { + it("should be valid JSON when put in object", async () => { + await variantAnalysisManager.copyRepoListToClipboard( + variantAnalysis.id, + ); - it("should use the sort key", async () => { - await variantAnalysisManager.copyRepoListToClipboard( - variantAnalysis.id, - { - ...defaultFilterSortState, - sortKey: SortKey.ResultsCount, - }, - ); + const text = writeTextStub.mock.calls[0][0]; - const text = writeTextStub.mock.calls[0][0]; + const parsed = JSON.parse(`{${text}}`); - const parsed = JSON.parse(`{${text}}`); - - expect(parsed).toEqual({ - "new-repo-list": [ - scannedRepos[2].repository.fullName, - scannedRepos[0].repository.fullName, - scannedRepos[4].repository.fullName, - ], + expect(parsed).toEqual({ + "new-repo-list": [ + scannedRepos[4].repository.fullName, + scannedRepos[2].repository.fullName, + scannedRepos[0].repository.fullName, + ], + }); }); - }); - it("should use the search value", async () => { - await variantAnalysisManager.copyRepoListToClipboard( - variantAnalysis.id, - { - ...defaultFilterSortState, - searchValue: "ban", - }, - ); + it("should use the sort key", async () => { + await variantAnalysisManager.copyRepoListToClipboard( + variantAnalysis.id, + { + ...defaultFilterSortState, + sortKey: SortKey.ResultsCount, + }, + ); - const text = writeTextStub.mock.calls[0][0]; + const text = writeTextStub.mock.calls[0][0]; - const parsed = JSON.parse(`{${text}}`); + const parsed = JSON.parse(`{${text}}`); - expect(parsed).toEqual({ - "new-repo-list": [scannedRepos[4].repository.fullName], + expect(parsed).toEqual({ + "new-repo-list": [ + scannedRepos[2].repository.fullName, + scannedRepos[0].repository.fullName, + scannedRepos[4].repository.fullName, + ], + }); + }); + + it("should use the search value", async () => { + await variantAnalysisManager.copyRepoListToClipboard( + variantAnalysis.id, + { + ...defaultFilterSortState, + searchValue: "ban", + }, + ); + + const text = writeTextStub.mock.calls[0][0]; + + const parsed = JSON.parse(`{${text}}`); + + expect(parsed).toEqual({ + "new-repo-list": [scannedRepos[4].repository.fullName], + }); }); }); }); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts new file mode 100644 index 000000000..13b0bd83b --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-monitor.test.ts @@ -0,0 +1,316 @@ +import { CancellationTokenSource, commands, extensions } from "vscode"; +import { CodeQLExtensionInterface } from "../../../../src/extension"; +import * as config from "../../../../src/config"; + +import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client"; +import { VariantAnalysisMonitor } from "../../../../src/remote-queries/variant-analysis-monitor"; +import { + VariantAnalysis as VariantAnalysisApiResponse, + VariantAnalysisFailureReason, + VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository, +} from "../../../../src/remote-queries/gh-api/variant-analysis"; +import { + createFailedMockApiResponse, + createMockApiResponse, +} from "../../../factories/remote-queries/gh-api/variant-analysis-api-response"; +import { + VariantAnalysis, + VariantAnalysisStatus, +} from "../../../../src/remote-queries/shared/variant-analysis"; +import { createMockScannedRepos } from "../../../factories/remote-queries/gh-api/scanned-repositories"; +import { + processFailureReason, + processScannedRepository, + processUpdatedVariantAnalysis, +} from "../../../../src/remote-queries/variant-analysis-processor"; +import { Credentials } from "../../../../src/authentication"; +import { createMockVariantAnalysis } from "../../../factories/remote-queries/shared/variant-analysis"; +import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager"; + +jest.setTimeout(60_000); + +describe("Variant Analysis Monitor", () => { + let extension: CodeQLExtensionInterface | Record; + let mockGetVariantAnalysis: jest.SpiedFunction< + typeof ghApiClient.getVariantAnalysis + >; + let cancellationTokenSource: CancellationTokenSource; + 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(); + + beforeEach(async () => { + jest + .spyOn(config, "isVariantAnalysisLiveResultsEnabled") + .mockReturnValue(false); + + cancellationTokenSource = new CancellationTokenSource(); + + variantAnalysis = createMockVariantAnalysis({}); + + shouldCancelMonitor = jest.fn(); + + extension = await extensions + .getExtension>( + "GitHub.vscode-codeql", + )! + .activate(); + variantAnalysisMonitor = new VariantAnalysisMonitor(shouldCancelMonitor); + variantAnalysisMonitor.onVariantAnalysisChange(onVariantAnalysisChangeSpy); + + variantAnalysisManager = extension.variantAnalysisManager; + mockGetDownloadResult = jest + .spyOn(variantAnalysisManager, "autoDownloadVariantAnalysisResult") + .mockResolvedValue(undefined); + + mockGetVariantAnalysis = jest + .spyOn(ghApiClient, "getVariantAnalysis") + .mockRejectedValue(new Error("Not mocked")); + + limitNumberOfAttemptsToMonitor(); + + const mockCredentials = { + getOctokit: () => + Promise.resolve({ + request: jest.fn(), + }), + } as unknown as Credentials; + jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); + }); + + it("should return early if variant analysis is cancelled", async () => { + cancellationTokenSource.cancel(); + + await variantAnalysisMonitor.monitorVariantAnalysis( + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled(); + }); + + it("should return early if variant analysis should be cancelled", async () => { + shouldCancelMonitor.mockResolvedValue(true); + + await variantAnalysisMonitor.monitorVariantAnalysis( + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled(); + }); + + describe("when the variant analysis fails", () => { + let mockFailedApiResponse: VariantAnalysisApiResponse; + + beforeEach(async () => { + mockFailedApiResponse = createFailedMockApiResponse(); + mockGetVariantAnalysis.mockResolvedValue(mockFailedApiResponse); + }); + + it("should mark as failed and stop monitoring", async () => { + await variantAnalysisMonitor.monitorVariantAnalysis( + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(mockGetVariantAnalysis).toHaveBeenCalledTimes(1); + + expect(onVariantAnalysisChangeSpy).toHaveBeenCalledWith( + expect.objectContaining({ + status: VariantAnalysisStatus.Failed, + failureReason: processFailureReason( + mockFailedApiResponse.failure_reason as VariantAnalysisFailureReason, + ), + }), + ); + }); + }); + + 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 () => { + scannedRepos = createMockScannedRepos([ + "pending", + "pending", + "in_progress", + "in_progress", + "succeeded", + "succeeded", + "succeeded", + ]); + 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, + cancellationTokenSource.token, + ); + + expect(commandSpy).toBeCalledTimes(succeededRepos.length); + + succeededRepos.forEach((succeededRepo, index) => { + expect(commandSpy).toHaveBeenNthCalledWith( + index + 1, + "codeQL.autoDownloadVariantAnalysisResult", + processScannedRepository(succeededRepo), + processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse), + ); + }); + }); + + it("should download all available results", async () => { + await variantAnalysisMonitor.monitorVariantAnalysis( + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(mockGetDownloadResult).toBeCalledTimes(succeededRepos.length); + + succeededRepos.forEach((succeededRepo, index) => { + expect(mockGetDownloadResult).toHaveBeenNthCalledWith( + index + 1, + processScannedRepository(succeededRepo), + processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse), + undefined, + ); + }); + }); + }); + + describe("when there are only in progress repos", () => { + let scannedRepos: ApiVariantAnalysisScannedRepository[]; + + beforeEach(async () => { + scannedRepos = createMockScannedRepos(["pending", "in_progress"]); + mockApiResponse = createMockApiResponse("in_progress", scannedRepos); + mockGetVariantAnalysis.mockResolvedValue(mockApiResponse); + }); + + it("should succeed and not download any repos via a command", async () => { + const commandSpy = jest + .spyOn(commands, "executeCommand") + .mockResolvedValue(undefined); + + await variantAnalysisMonitor.monitorVariantAnalysis( + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(commandSpy).not.toHaveBeenCalled(); + }); + + it("should not try to download any repos", async () => { + await variantAnalysisMonitor.monitorVariantAnalysis( + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(mockGetDownloadResult).not.toBeCalled(); + }); + }); + + describe("when the responses change", () => { + let scannedRepos: ApiVariantAnalysisScannedRepository[]; + + beforeEach(async () => { + scannedRepos = createMockScannedRepos([ + "pending", + "in_progress", + "in_progress", + "in_progress", + "pending", + "pending", + ]); + mockApiResponse = createMockApiResponse("in_progress", scannedRepos); + mockGetVariantAnalysis.mockResolvedValueOnce(mockApiResponse); + + let nextApiResponse = { + ...mockApiResponse, + scanned_repositories: [...scannedRepos.map((r) => ({ ...r }))], + }; + nextApiResponse.scanned_repositories[0].analysis_status = "succeeded"; + nextApiResponse.scanned_repositories[1].analysis_status = "succeeded"; + mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse); + + nextApiResponse = { + ...mockApiResponse, + scanned_repositories: [ + ...nextApiResponse.scanned_repositories.map((r) => ({ ...r })), + ], + }; + nextApiResponse.scanned_repositories[2].analysis_status = "succeeded"; + nextApiResponse.scanned_repositories[5].analysis_status = "succeeded"; + mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse); + + nextApiResponse = { + ...mockApiResponse, + scanned_repositories: [ + ...nextApiResponse.scanned_repositories.map((r) => ({ ...r })), + ], + }; + nextApiResponse.scanned_repositories[3].analysis_status = "succeeded"; + nextApiResponse.scanned_repositories[4].analysis_status = "failed"; + mockGetVariantAnalysis.mockResolvedValueOnce(nextApiResponse); + }); + + it("should trigger a download extension command for each repo", async () => { + const commandSpy = jest + .spyOn(commands, "executeCommand") + .mockResolvedValue(undefined); + + await variantAnalysisMonitor.monitorVariantAnalysis( + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(mockGetVariantAnalysis).toBeCalledTimes(4); + expect(commandSpy).toBeCalledTimes(5); + }); + }); + + describe("when there are no repos to scan", () => { + beforeEach(async () => { + scannedRepos = []; + mockApiResponse = createMockApiResponse("succeeded", scannedRepos); + mockGetVariantAnalysis.mockResolvedValue(mockApiResponse); + }); + + it("should not try to download any repos", async () => { + await variantAnalysisMonitor.monitorVariantAnalysis( + variantAnalysis, + cancellationTokenSource.token, + ); + + expect(mockGetDownloadResult).not.toBeCalled(); + }); + }); + }); + + function limitNumberOfAttemptsToMonitor() { + VariantAnalysisMonitor.maxAttemptCount = 3; + VariantAnalysisMonitor.sleepTime = 1; + } +}); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts similarity index 93% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts index 8c9b04a6b..7439cbf28 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-results-manager.test.ts @@ -1,20 +1,20 @@ import { extensions } from "vscode"; -import { CodeQLExtensionInterface } from "../../../extension"; -import { extLogger } from "../../../common"; -import { Credentials } from "../../../authentication"; +import { CodeQLExtensionInterface } from "../../../../src/extension"; +import { extLogger } from "../../../../src/common"; +import { Credentials } from "../../../../src/authentication"; import * as fs from "fs-extra"; import { join, resolve } from "path"; -import { VariantAnalysisResultsManager } from "../../../remote-queries/variant-analysis-results-manager"; -import { CodeQLCliServer } from "../../../cli"; +import { VariantAnalysisResultsManager } from "../../../../src/remote-queries/variant-analysis-results-manager"; +import { CodeQLCliServer } from "../../../../src/cli"; import { storagePath } from "../global.helper"; import { faker } from "@faker-js/faker"; -import * as ghApiClient from "../../../remote-queries/gh-api/gh-api-client"; -import { createMockVariantAnalysisRepositoryTask } from "../../factories/remote-queries/shared/variant-analysis-repo-tasks"; +import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client"; +import { createMockVariantAnalysisRepositoryTask } from "../../../factories/remote-queries/shared/variant-analysis-repo-tasks"; import { VariantAnalysisRepositoryTask, VariantAnalysisScannedRepositoryResult, -} from "../../../remote-queries/shared/variant-analysis"; +} from "../../../../src/remote-queries/shared/variant-analysis"; jest.setTimeout(10_000); @@ -47,8 +47,6 @@ describe(VariantAnalysisResultsManager.name, () => { beforeEach(async () => { jest.spyOn(extLogger, "log").mockResolvedValue(undefined); - jest.spyOn(fs, "mkdirSync").mockReturnValue(undefined); - jest.spyOn(fs, "writeFile").mockReturnValue(undefined); variantAnalysisResultsManager = new VariantAnalysisResultsManager( cli, @@ -111,7 +109,7 @@ describe(VariantAnalysisResultsManager.name, () => { beforeEach(async () => { const sourceFilePath = join( __dirname, - "../../../../src/vscode-tests/cli-integration/data/variant-analysis-results.zip", + "../data/variant-analysis-results.zip", ); arrayBuffer = fs.readFileSync(sourceFilePath).buffer; diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts similarity index 96% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts index 08a610957..dd101daf8 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/remote-queries/variant-analysis-submission-integration.test.ts @@ -12,11 +12,11 @@ import { import { Octokit } from "@octokit/rest"; import { retry } from "@octokit/plugin-retry"; -import { CodeQLExtensionInterface } from "../../../extension"; -import { Credentials } from "../../../authentication"; -import { MockGitHubApiServer } from "../../../mocks/mock-gh-api-server"; +import { CodeQLExtensionInterface } from "../../../../src/extension"; +import { Credentials } from "../../../../src/authentication"; +import { MockGitHubApiServer } from "../../../../src/mocks/mock-gh-api-server"; -jest.setTimeout(10_000); +jest.setTimeout(30_000); const mockServer = new MockGitHubApiServer(); beforeAll(() => mockServer.startServer()); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/run-cli.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts similarity index 91% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/run-cli.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts index 6063b01a0..df1230e06 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/run-cli.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts @@ -2,16 +2,16 @@ import { extensions, Uri } from "vscode"; import { join } from "path"; import { SemVer } from "semver"; -import { CodeQLCliServer, QueryInfoByLanguage } from "../../cli"; -import { CodeQLExtensionInterface } from "../../extension"; +import { CodeQLCliServer, QueryInfoByLanguage } from "../../../src/cli"; +import { CodeQLExtensionInterface } from "../../../src/extension"; import { itWithCodeQL } from "../cli"; import { getOnDiskWorkspaceFolders, getQlPackForDbscheme, languageToDbScheme, -} from "../../helpers"; -import { resolveQueries } from "../../contextual/queryResolver"; -import { KeyType } from "../../contextual/keyType"; +} from "../../../src/helpers"; +import { resolveQueries } from "../../../src/contextual/queryResolver"; +import { KeyType } from "../../../src/contextual/keyType"; jest.setTimeout(60_000); diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/sourcemap.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts similarity index 98% rename from extensions/ql-vscode/src/vscode-tests/cli-integration/sourcemap.test.ts rename to extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts index 199ea1108..e54d6b071 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/sourcemap.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts @@ -1,6 +1,6 @@ import { commands, Selection, window, workspace } from "vscode"; import { join, basename } from "path"; -import { tmpDir } from "../../helpers"; +import { tmpDir } from "../../../src/helpers"; import { readFile, writeFile, ensureDir, copy } from "fs-extra"; jest.setTimeout(20_000); diff --git a/extensions/ql-vscode/src/vscode-tests/cli.ts b/extensions/ql-vscode/test/vscode-tests/cli.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/cli.ts rename to extensions/ql-vscode/test/vscode-tests/cli.ts diff --git a/extensions/ql-vscode/src/vscode-tests/disposable-bucket.ts b/extensions/ql-vscode/test/vscode-tests/disposable-bucket.ts similarity index 86% rename from extensions/ql-vscode/src/vscode-tests/disposable-bucket.ts rename to extensions/ql-vscode/test/vscode-tests/disposable-bucket.ts index 9839debbf..54854fec1 100644 --- a/extensions/ql-vscode/src/vscode-tests/disposable-bucket.ts +++ b/extensions/ql-vscode/test/vscode-tests/disposable-bucket.ts @@ -1,5 +1,5 @@ import { Disposable } from "vscode"; -import { DisposableObject } from "../pure/disposable-object"; +import { DisposableObject } from "../../src/pure/disposable-object"; /** * A simple disposable object that does nothing other than contain a list of disposable objects. diff --git a/extensions/ql-vscode/src/vscode-tests/ensureCli.ts b/extensions/ql-vscode/test/vscode-tests/ensureCli.ts similarity index 99% rename from extensions/ql-vscode/src/vscode-tests/ensureCli.ts rename to extensions/ql-vscode/test/vscode-tests/ensureCli.ts index 53abc2940..bf5930690 100644 --- a/extensions/ql-vscode/src/vscode-tests/ensureCli.ts +++ b/extensions/ql-vscode/test/vscode-tests/ensureCli.ts @@ -4,7 +4,7 @@ import { getRequiredAssetName, extractZipArchive, codeQlLauncherName, -} from "../pure/distribution"; +} from "../../src/pure/distribution"; import fetch from "node-fetch"; /** diff --git a/extensions/ql-vscode/src/vscode-tests/jest-runner-vscode.config.base.js b/extensions/ql-vscode/test/vscode-tests/jest-runner-vscode.config.base.js similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/jest-runner-vscode.config.base.js rename to extensions/ql-vscode/test/vscode-tests/jest-runner-vscode.config.base.js diff --git a/extensions/ql-vscode/src/vscode-tests/jest.config.base.ts b/extensions/ql-vscode/test/vscode-tests/jest.config.base.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/jest.config.base.ts rename to extensions/ql-vscode/test/vscode-tests/jest.config.base.ts diff --git a/extensions/ql-vscode/src/vscode-tests/jest.setup.ts b/extensions/ql-vscode/test/vscode-tests/jest.setup.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/jest.setup.ts rename to extensions/ql-vscode/test/vscode-tests/jest.setup.ts diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/activation.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/activation.test.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/minimal-workspace/activation.test.ts rename to extensions/ql-vscode/test/vscode-tests/minimal-workspace/activation.test.ts diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/config.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/config.test.ts similarity index 99% rename from extensions/ql-vscode/src/vscode-tests/minimal-workspace/config.test.ts rename to extensions/ql-vscode/test/vscode-tests/minimal-workspace/config.test.ts index 39c562254..ec69b4c4b 100644 --- a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/config.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/config.test.ts @@ -5,7 +5,7 @@ import { ConfigListener, QueryHistoryConfigListener, QueryServerConfigListener, -} from "../../config"; +} from "../../../src/config"; describe("config listeners", () => { interface TestConfig { diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts similarity index 84% rename from extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases.test.ts rename to extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts index 9a50eabcc..d2a5e9e9b 100644 --- a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases.test.ts @@ -10,16 +10,16 @@ import { DatabaseContents, FullDatabaseOptions, findSourceArchive, -} from "../../databases"; -import { Logger } from "../../common"; -import { ProgressCallback } from "../../commandRunner"; -import { CodeQLCliServer, DbInfo } from "../../cli"; +} from "../../../src/databases"; +import { Logger } from "../../../src/common"; +import { ProgressCallback } from "../../../src/commandRunner"; +import { CodeQLCliServer, DbInfo } from "../../../src/cli"; import { encodeArchiveBasePath, encodeSourceArchiveUri, -} from "../../archive-filesystem-provider"; +} from "../../../src/archive-filesystem-provider"; import { testDisposeHandler } from "../test-dispose-handler"; -import { QueryRunner } from "../../queryRunner"; +import { QueryRunner } from "../../../src/queryRunner"; describe("databases", () => { const MOCK_DB_OPTIONS: FullDatabaseOptions = { @@ -200,9 +200,7 @@ describe("databases", () => { it("should remove a database item", async () => { const mockDbItem = createMockDB(); - const removeMock = jest - .spyOn(fs, "remove") - .mockImplementation(() => Promise.resolve()); + await fs.ensureDir(mockDbItem.databaseUri.fsPath); // pretend that this item is the first workspace folder in the list jest @@ -229,13 +227,14 @@ describe("databases", () => { expect(workspace.updateWorkspaceFolders).toBeCalledWith(0, 1); // should also delete the db contents - expect(removeMock).toBeCalledWith(mockDbItem.databaseUri.fsPath); + await expect(fs.pathExists(mockDbItem.databaseUri.fsPath)).resolves.toBe( + false, + ); }); it("should remove a database item outside of the extension controlled area", async () => { const mockDbItem = createMockDB(); - const removeMock = jest.spyOn(fs, "remove"); - removeMock.mockReset().mockImplementation(() => Promise.resolve()); + await fs.ensureDir(mockDbItem.databaseUri.fsPath); // pretend that this item is the first workspace folder in the list jest @@ -263,7 +262,9 @@ describe("databases", () => { expect(workspace.updateWorkspaceFolders).toBeCalledWith(0, 1); // should NOT delete the db contents - expect(removeMock).not.toBeCalled(); + await expect(fs.pathExists(mockDbItem.databaseUri.fsPath)).resolves.toBe( + true, + ); }); it("should register and deregister a database when adding and removing it", async () => { @@ -271,8 +272,6 @@ describe("databases", () => { // registration messages. const mockDbItem = createMockDB(); - jest.spyOn(fs, "remove").mockImplementation(() => Promise.resolve()); - await (databaseManager as any).addDatabaseItem( {} as ProgressCallback, {} as CancellationToken, @@ -411,98 +410,93 @@ describe("databases", () => { }); describe("isAffectedByTest", () => { - const directoryStats = new fs.Stats(); - const fileStats = new fs.Stats(); - beforeEach(() => { - jest.spyOn(directoryStats, "isDirectory").mockReturnValue(true); - jest.spyOn(fileStats, "isDirectory").mockReturnValue(false); + let directoryPath: string; + let projectPath: string; + let qlFilePath: string; + + beforeEach(async () => { + directoryPath = join(dir.name, "dir"); + await fs.ensureDir(directoryPath); + projectPath = join(directoryPath, "dir.testproj"); + await fs.writeFile(projectPath, ""); + qlFilePath = join(directoryPath, "test.ql"); + await fs.writeFile(qlFilePath, ""); }); it("should return true for testproj database in test directory", async () => { - jest.spyOn(fs, "stat").mockResolvedValue(directoryStats); - const db = createMockDB( - sourceLocationUri(), - Uri.file("/path/to/dir/dir.testproj"), - ); - expect(await db.isAffectedByTest("/path/to/dir")).toBe(true); + const db = createMockDB(sourceLocationUri(), Uri.file(projectPath)); + expect(await db.isAffectedByTest(directoryPath)).toBe(true); }); it("should return false for non-existent test directory", async () => { - jest.spyOn(fs, "stat").mockImplementation(() => { - throw new Error("Simulated Error: ENOENT"); - }); const db = createMockDB( sourceLocationUri(), - Uri.file("/path/to/dir/dir.testproj"), + Uri.file(join(dir.name, "non-existent/non-existent.testproj")), + ); + expect(await db.isAffectedByTest(join(dir.name, "non-existent"))).toBe( + false, ); - expect(await db.isAffectedByTest("/path/to/dir")).toBe(false); }); it("should return false for non-testproj database in test directory", async () => { - jest.spyOn(fs, "stat").mockResolvedValue(directoryStats); + const anotherProjectPath = join(directoryPath, "dir.proj"); + await fs.writeFile(anotherProjectPath, ""); + const db = createMockDB( sourceLocationUri(), - Uri.file("/path/to/dir/dir.proj"), + Uri.file(anotherProjectPath), ); - expect(await db.isAffectedByTest("/path/to/dir")).toBe(false); + expect(await db.isAffectedByTest(directoryPath)).toBe(false); }); it("should return false for testproj database outside test directory", async () => { - jest.spyOn(fs, "stat").mockResolvedValue(directoryStats); + const anotherProjectDir = join(dir.name, "other"); + await fs.ensureDir(anotherProjectDir); + const anotherProjectPath = join(anotherProjectDir, "other.testproj"); + await fs.writeFile(anotherProjectPath, ""); + const db = createMockDB( sourceLocationUri(), - Uri.file("/path/to/other/dir.testproj"), + Uri.file(anotherProjectPath), ); - expect(await db.isAffectedByTest("/path/to/dir")).toBe(false); + expect(await db.isAffectedByTest(directoryPath)).toBe(false); }); it("should return false for testproj database for prefix directory", async () => { - jest.spyOn(fs, "stat").mockResolvedValue(directoryStats); - const db = createMockDB( - sourceLocationUri(), - Uri.file("/path/to/dir/dir.testproj"), - ); - // /path/to/d is a prefix of /path/to/dir/dir.testproj, but - // /path/to/dir/dir.testproj is not under /path/to/d - expect(await db.isAffectedByTest("/path/to/d")).toBe(false); + const db = createMockDB(sourceLocationUri(), Uri.file(projectPath)); + // /d is a prefix of /dir/dir.testproj, but + // /dir/dir.testproj is not under /d + expect(await db.isAffectedByTest(join(directoryPath, "d"))).toBe(false); }); it("should return true for testproj database for test file", async () => { - jest.spyOn(fs, "stat").mockResolvedValue(fileStats); - const db = createMockDB( - sourceLocationUri(), - Uri.file("/path/to/dir/dir.testproj"), - ); - expect(await db.isAffectedByTest("/path/to/dir/test.ql")).toBe(true); + const db = createMockDB(sourceLocationUri(), Uri.file(projectPath)); + expect(await db.isAffectedByTest(qlFilePath)).toBe(true); }); it("should return false for non-existent test file", async () => { - jest.spyOn(fs, "stat").mockImplementation(() => { - throw new Error("Simulated Error: ENOENT"); - }); - const db = createMockDB( - sourceLocationUri(), - Uri.file("/path/to/dir/dir.testproj"), - ); - expect(await db.isAffectedByTest("/path/to/dir/test.ql")).toBe(false); + const otherTestFile = join(directoryPath, "other-test.ql"); + const db = createMockDB(sourceLocationUri(), Uri.file(projectPath)); + expect(await db.isAffectedByTest(otherTestFile)).toBe(false); }); it("should return false for non-testproj database for test file", async () => { - jest.spyOn(fs, "stat").mockResolvedValue(fileStats); + const anotherProjectPath = join(directoryPath, "dir.proj"); + await fs.writeFile(anotherProjectPath, ""); + const db = createMockDB( sourceLocationUri(), - Uri.file("/path/to/dir/dir.proj"), + Uri.file(anotherProjectPath), ); - expect(await db.isAffectedByTest("/path/to/dir/test.ql")).toBe(false); + expect(await db.isAffectedByTest(qlFilePath)).toBe(false); }); it("should return false for testproj database not matching test file", async () => { - jest.spyOn(fs, "stat").mockResolvedValue(fileStats); - const db = createMockDB( - sourceLocationUri(), - Uri.file("/path/to/dir/dir.testproj"), - ); - expect(await db.isAffectedByTest("/path/to/test.ql")).toBe(false); + const otherTestFile = join(dir.name, "test.ql"); + await fs.writeFile(otherTestFile, ""); + + const db = createMockDB(sourceLocationUri(), Uri.file(projectPath)); + expect(await db.isAffectedByTest(otherTestFile)).toBe(false); }); }); diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/README.md b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases/README.md similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/README.md rename to extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases/README.md diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/db-panel.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases/db-panel.test.ts similarity index 64% rename from extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/db-panel.test.ts rename to extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases/db-panel.test.ts index 291326a84..bed7db6f5 100644 --- a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/db-panel.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/databases/db-panel.test.ts @@ -4,15 +4,22 @@ import { ensureDir, readJSON, remove, writeJson } from "fs-extra"; import { DbConfig, SelectedDbItemKind, -} from "../../../databases/config/db-config"; -import { DbManager } from "../../../databases/db-manager"; -import { DbConfigStore } from "../../../databases/config/db-config-store"; -import { DbTreeDataProvider } from "../../../databases/ui/db-tree-data-provider"; -import { DbItemKind, LocalDatabaseDbItem } from "../../../databases/db-item"; -import { DbTreeViewItem } from "../../../databases/ui/db-tree-view-item"; -import { ExtensionApp } from "../../../common/vscode/vscode-app"; -import { createMockExtensionContext } from "../../factories/extension-context"; -import { createDbConfig } from "../../factories/db-config-factories"; +} from "../../../../src/databases/config/db-config"; +import { DbManager } from "../../../../src/databases/db-manager"; +import { DbConfigStore } from "../../../../src/databases/config/db-config-store"; +import { DbTreeDataProvider } from "../../../../src/databases/ui/db-tree-data-provider"; +import { + DbItemKind, + DbListKind, + LocalDatabaseDbItem, +} from "../../../../src/databases/db-item"; +import { + DbTreeViewItem, + SELECTED_DB_ITEM_RESOURCE_URI, +} from "../../../../src/databases/ui/db-tree-view-item"; +import { ExtensionApp } from "../../../../src/common/vscode/vscode-app"; +import { createMockExtensionContext } from "../../../factories/extension-context"; +import { createDbConfig } from "../../../factories/db-config-factories"; describe("db panel", () => { const workspaceStoragePath = join(__dirname, "test-workspace-storage"); @@ -36,7 +43,7 @@ describe("db panel", () => { const app = new ExtensionApp(extensionContext); - dbConfigStore = new DbConfigStore(app); + dbConfigStore = new DbConfigStore(app, false); dbManager = new DbManager(app, dbConfigStore); }); @@ -126,7 +133,7 @@ describe("db panel", () => { expect(systemDefinedListItems.length).toBe(3); const userDefinedListItems = remoteRootNode.children.filter( - (item) => item.dbItem?.kind === DbItemKind.RemoteUserDefinedList, + (item) => item.dbItem?.kind === DbItemKind.VariantAnalysisUserDefinedList, ); expect(userDefinedListItems.length).toBe(2); checkUserDefinedListItem(userDefinedListItems[0], "my-list-1", [ @@ -351,7 +358,7 @@ describe("db panel", () => { }, ], selected: { - kind: SelectedDbItemKind.RemoteUserDefinedList, + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, listName: "my-list-2", }, }); @@ -369,12 +376,12 @@ describe("db panel", () => { const list1 = remoteRootNode.children.find( (c) => - c.dbItem?.kind === DbItemKind.RemoteUserDefinedList && + c.dbItem?.kind === DbItemKind.VariantAnalysisUserDefinedList && c.dbItem?.listName === "my-list-1", ); const list2 = remoteRootNode.children.find( (c) => - c.dbItem?.kind === DbItemKind.RemoteUserDefinedList && + c.dbItem?.kind === DbItemKind.VariantAnalysisUserDefinedList && c.dbItem?.listName === "my-list-2", ); @@ -398,7 +405,7 @@ describe("db panel", () => { ], remoteRepos: ["owner1/repo1"], selected: { - kind: SelectedDbItemKind.RemoteRepository, + kind: SelectedDbItemKind.VariantAnalysisRepository, repositoryName: "owner1/repo1", listName: "my-list-2", }, @@ -417,7 +424,7 @@ describe("db panel", () => { const list2 = remoteRootNode.children.find( (c) => - c.dbItem?.kind === DbItemKind.RemoteUserDefinedList && + c.dbItem?.kind === DbItemKind.VariantAnalysisUserDefinedList && c.dbItem?.listName === "my-list-2", ); expect(list2).toBeTruthy(); @@ -443,52 +450,178 @@ describe("db panel", () => { } }); - it("should add a new list to the remote db list", async () => { - const dbConfig: DbConfig = createDbConfig({ - remoteLists: [ - { - name: "my-list-1", - repositories: ["owner1/repo1", "owner1/repo2"], - }, - ], - selected: { - kind: SelectedDbItemKind.RemoteUserDefinedList, - listName: "my-list-1", - }, + describe("addNewRemoteRepo", () => { + it("should add a new remote repo", async () => { + const dbConfig: DbConfig = createDbConfig({ + remoteRepos: ["owner1/repo1"], + }); + + await saveDbConfig(dbConfig); + + const dbTreeItems = await dbTreeDataProvider.getChildren(); + + expect(dbTreeItems).toBeTruthy(); + const items = dbTreeItems!; + + const remoteRootNode = items[0]; + const remoteRepos = remoteRootNode.children.filter( + (c) => c.dbItem?.kind === DbItemKind.RemoteRepo, + ); + const repo1 = remoteRootNode.children.find( + (c) => + c.dbItem?.kind === DbItemKind.RemoteRepo && + c.dbItem?.repoFullName === "owner1/repo1", + ); + + expect(remoteRepos.length).toBe(1); + expect(remoteRepos[0]).toBe(repo1); + + await dbManager.addNewRemoteRepo("owner2/repo2"); + + const dbConfigFileContents = await readDbConfigDirectly(); + expect( + dbConfigFileContents.databases.variantAnalysis.repositories.length, + ).toBe(2); + expect( + dbConfigFileContents.databases.variantAnalysis.repositories[1], + ).toEqual("owner2/repo2"); }); - await saveDbConfig(dbConfig); + it("should add a new remote repo to a user defined list", async () => { + const dbConfig: DbConfig = createDbConfig({ + remoteLists: [ + { + name: "my-list-1", + repositories: ["owner1/repo1"], + }, + ], + }); - const dbTreeItems = await dbTreeDataProvider.getChildren(); + await saveDbConfig(dbConfig); - expect(dbTreeItems).toBeTruthy(); - const items = dbTreeItems!; + const dbTreeItems = await dbTreeDataProvider.getChildren(); - const remoteRootNode = items[0]; - const remoteUserDefinedLists = remoteRootNode.children.filter( - (c) => c.dbItem?.kind === DbItemKind.RemoteUserDefinedList, - ); - const list1 = remoteRootNode.children.find( - (c) => - c.dbItem?.kind === DbItemKind.RemoteUserDefinedList && - c.dbItem?.listName === "my-list-1", - ); + expect(dbTreeItems).toBeTruthy(); + const items = dbTreeItems!; - expect(remoteUserDefinedLists.length).toBe(1); - expect(remoteUserDefinedLists[0]).toBe(list1); + const remoteRootNode = items[0]; + const remoteUserDefinedLists = remoteRootNode.children.filter( + (c) => c.dbItem?.kind === DbItemKind.VariantAnalysisUserDefinedList, + ); + const list1 = remoteRootNode.children.find( + (c) => + c.dbItem?.kind === DbItemKind.VariantAnalysisUserDefinedList && + c.dbItem?.listName === "my-list-1", + ); - await dbManager.addNewRemoteList("my-list-2"); + expect(remoteUserDefinedLists.length).toBe(1); + expect(remoteUserDefinedLists[0]).toBe(list1); - // Read the workspace databases JSON file directly to check that the new list has been added. - // We can't use the dbConfigStore's `read` function here because it depends on the file watcher - // picking up changes, and we don't control the timing of that. - const dbConfigFileContents = await readJSON(dbConfigFilePath); - expect(dbConfigFileContents.databases.remote.repositoryLists.length).toBe( - 2, - ); - expect(dbConfigFileContents.databases.remote.repositoryLists[1]).toEqual({ - name: "my-list-2", - repositories: [], + await dbManager.addNewRemoteRepo("owner2/repo2", "my-list-1"); + + // Read the workspace databases JSON file directly to check that the new repo has been added. + // We can't use the dbConfigStore's `read` function here because it depends on the file watcher + // picking up changes, and we don't control the timing of that. + const dbConfigFileContents = await readJSON(dbConfigFilePath); + expect( + dbConfigFileContents.databases.variantAnalysis.repositoryLists.length, + ).toBe(1); + + expect( + dbConfigFileContents.databases.variantAnalysis.repositoryLists[0], + ).toEqual({ + name: "my-list-1", + repositories: ["owner1/repo1", "owner2/repo2"], + }); + }); + }); + + describe("addNewList", () => { + it("should add a new remote list", async () => { + const dbConfig: DbConfig = createDbConfig({ + remoteLists: [ + { + name: "my-list-1", + repositories: ["owner1/repo1", "owner1/repo2"], + }, + ], + selected: { + kind: SelectedDbItemKind.VariantAnalysisUserDefinedList, + listName: "my-list-1", + }, + }); + + await saveDbConfig(dbConfig); + + const dbTreeItems = await dbTreeDataProvider.getChildren(); + + expect(dbTreeItems).toBeTruthy(); + const items = dbTreeItems!; + + const remoteRootNode = items[0]; + const remoteUserDefinedLists = remoteRootNode.children.filter( + (c) => c.dbItem?.kind === DbItemKind.VariantAnalysisUserDefinedList, + ); + const list1 = remoteRootNode.children.find( + (c) => + c.dbItem?.kind === DbItemKind.VariantAnalysisUserDefinedList && + c.dbItem?.listName === "my-list-1", + ); + + expect(remoteUserDefinedLists.length).toBe(1); + expect(remoteUserDefinedLists[0]).toBe(list1); + + await dbManager.addNewList(DbListKind.Remote, "my-list-2"); + + const dbConfigFileContents = await readDbConfigDirectly(); + expect( + dbConfigFileContents.databases.variantAnalysis.repositoryLists.length, + ).toBe(2); + expect( + dbConfigFileContents.databases.variantAnalysis.repositoryLists[1], + ).toEqual({ + name: "my-list-2", + repositories: [], + }); + }); + + it("should throw error when adding a new list to a local node", async () => { + const dbConfig: DbConfig = createDbConfig({ + localLists: [ + { + name: "my-list-1", + databases: [], + }, + ], + }); + await saveDbConfig(dbConfig); + + const dbTreeItems = await dbTreeDataProvider.getChildren(); + + expect(dbTreeItems).toBeTruthy(); + const items = dbTreeItems!; + + const localRootNode = items[1]; + const localUserDefinedLists = localRootNode.children.filter( + (c) => c.dbItem?.kind === DbItemKind.LocalList, + ); + const list1 = localRootNode.children.find( + (c) => + c.dbItem?.kind === DbItemKind.LocalList && + c.dbItem?.listName === "my-list-1", + ); + + expect(localUserDefinedLists.length).toBe(1); + expect(localUserDefinedLists[0]).toBe(list1); + + await dbManager.addNewList(DbListKind.Local, "my-list-2"); + + const dbConfigFileContents = await readDbConfigDirectly(); + expect(dbConfigFileContents.databases.local.lists.length).toBe(2); + expect(dbConfigFileContents.databases.local.lists[1]).toEqual({ + name: "my-list-2", + databases: [], + }); }); }); @@ -549,6 +682,83 @@ describe("db panel", () => { ); }); + describe("Name validation", () => { + it("should not allow adding a new list with empty name", async () => { + const dbConfig = createDbConfig(); + + await saveDbConfig(dbConfig); + + await expect(dbManager.addNewList(DbListKind.Remote, "")).rejects.toThrow( + new Error("List name cannot be empty"), + ); + }); + + it("should not allow adding a list with duplicate name", async () => { + const dbConfig = createDbConfig({ + remoteLists: [ + { + name: "my-list-1", + repositories: ["owner1/repo1", "owner1/repo2"], + }, + ], + }); + + await saveDbConfig(dbConfig); + + await expect( + dbManager.addNewList(DbListKind.Remote, "my-list-1"), + ).rejects.toThrow( + new Error("A remote list with the name 'my-list-1' already exists"), + ); + }); + + it("should not allow adding a new remote db with empty name", async () => { + const dbConfig = createDbConfig(); + + await saveDbConfig(dbConfig); + + await expect(dbManager.addNewRemoteRepo("")).rejects.toThrow( + new Error("Repository name cannot be empty"), + ); + }); + + it("should not allow adding a remote db with duplicate name", async () => { + const dbConfig = createDbConfig({ + remoteRepos: ["owner1/repo1"], + }); + + await saveDbConfig(dbConfig); + + await expect(dbManager.addNewRemoteRepo("owner1/repo1")).rejects.toThrow( + new Error( + "A remote repository with the name 'owner1/repo1' already exists", + ), + ); + }); + + it("should not allow adding a new remote owner with empty name", async () => { + const dbConfig = createDbConfig(); + + await saveDbConfig(dbConfig); + + await expect(dbManager.addNewRemoteOwner("")).rejects.toThrow( + new Error("Owner name cannot be empty"), + ); + }); + + it("should not allow adding a remote owner with duplicate name", async () => { + const dbConfig = createDbConfig({ + remoteOwners: ["owner1"], + }); + + await saveDbConfig(dbConfig); + + await expect(dbManager.addNewRemoteOwner("owner1")).rejects.toThrow( + new Error("A remote owner with the name 'owner1' already exists"), + ); + }); + }); + async function saveDbConfig(dbConfig: DbConfig): Promise { await writeJson(dbConfigFilePath, dbConfig); @@ -568,6 +778,7 @@ describe("db panel", () => { expect(item.tooltip).toBe(`Top ${n} repositories of a language`); expect(item.iconPath).toEqual(new ThemeIcon("github")); expect(item.collapsibleState).toBe(TreeItemCollapsibleState.None); + checkDbItemActions(item, ["canBeSelected"]); } function checkUserDefinedListItem( @@ -579,6 +790,7 @@ describe("db panel", () => { expect(item.tooltip).toBeUndefined(); expect(item.iconPath).toBeUndefined(); expect(item.collapsibleState).toBe(TreeItemCollapsibleState.Collapsed); + checkDbItemActions(item, ["canBeSelected", "canBeRenamed", "canBeRemoved"]); expect(item.children).toBeTruthy(); expect(item.children.length).toBe(repos.length); @@ -592,6 +804,11 @@ describe("db panel", () => { expect(item.tooltip).toBeUndefined(); expect(item.iconPath).toEqual(new ThemeIcon("organization")); expect(item.collapsibleState).toBe(TreeItemCollapsibleState.None); + checkDbItemActions(item, [ + "canBeSelected", + "canBeRemoved", + "canBeOpenedOnGitHub", + ]); expect(item.children).toBeTruthy(); expect(item.children.length).toBe(0); } @@ -601,6 +818,11 @@ describe("db panel", () => { expect(item.tooltip).toBeUndefined(); expect(item.iconPath).toEqual(new ThemeIcon("database")); expect(item.collapsibleState).toBe(TreeItemCollapsibleState.None); + checkDbItemActions(item, [ + "canBeSelected", + "canBeRemoved", + "canBeOpenedOnGitHub", + ]); } function checkLocalListItem( @@ -612,6 +834,7 @@ describe("db panel", () => { expect(item.tooltip).toBeUndefined(); expect(item.iconPath).toBeUndefined(); expect(item.collapsibleState).toBe(TreeItemCollapsibleState.Collapsed); + checkDbItemActions(item, ["canBeSelected", "canBeRemoved", "canBeRenamed"]); expect(item.children).toBeTruthy(); expect(item.children.length).toBe(databases.length); @@ -628,6 +851,16 @@ describe("db panel", () => { expect(item.tooltip).toBe(`Language: ${database.language}`); expect(item.iconPath).toEqual(new ThemeIcon("database")); expect(item.collapsibleState).toBe(TreeItemCollapsibleState.None); + checkDbItemActions(item, ["canBeSelected", "canBeRemoved", "canBeRenamed"]); + } + + function checkDbItemActions(item: DbTreeViewItem, actions: string[]): void { + const itemActions = item.contextValue?.split(","); + expect(itemActions).toBeDefined(); + expect(itemActions!.length).toBe(actions.length); + for (const action of actions) { + expect(itemActions).toContain(action); + } } function checkErrorItem( @@ -648,14 +881,23 @@ describe("db panel", () => { function isTreeViewItemSelectable(treeViewItem: DbTreeViewItem) { return ( treeViewItem.resourceUri === undefined && - treeViewItem.contextValue === "selectableDbItem" + treeViewItem.contextValue?.includes("canBeSelected") ); } function isTreeViewItemSelected(treeViewItem: DbTreeViewItem) { return ( - treeViewItem.resourceUri?.query === "selected=true" && - treeViewItem.contextValue === undefined + treeViewItem.resourceUri?.toString(true) === + SELECTED_DB_ITEM_RESOURCE_URI && + (treeViewItem.contextValue === undefined || + !treeViewItem.contextValue.includes("canBeSelected")) ); } + + async function readDbConfigDirectly(): Promise { + // Read the workspace databases JSON file directly to check that the new list has been added. + // We can't use the dbConfigStore's `read` function here because it depends on the file watcher + // picking up changes, and we don't control the timing of that. + return (await readJSON(dbConfigFilePath)) as DbConfig; + } }); diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/determining-selected-query-test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/determining-selected-query-test.ts similarity index 96% rename from extensions/ql-vscode/src/vscode-tests/minimal-workspace/determining-selected-query-test.ts rename to extensions/ql-vscode/test/vscode-tests/minimal-workspace/determining-selected-query-test.ts index f6f9dca4c..fbfdde11a 100644 --- a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/determining-selected-query-test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/determining-selected-query-test.ts @@ -1,7 +1,7 @@ import { resolve, join } from "path"; import * as vscode from "vscode"; import { Uri } from "vscode"; -import { determineSelectedQuery } from "../../run-queries-shared"; +import { determineSelectedQuery } from "../../../src/run-queries-shared"; async function showQlDocument(name: string): Promise { const folderPath = vscode.workspace.workspaceFolders![0].uri.fsPath; diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/jest-runner-vscode.config.js b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/jest-runner-vscode.config.js similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/minimal-workspace/jest-runner-vscode.config.js rename to extensions/ql-vscode/test/vscode-tests/minimal-workspace/jest-runner-vscode.config.js diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/jest.config.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/jest.config.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/minimal-workspace/jest.config.ts rename to extensions/ql-vscode/test/vscode-tests/minimal-workspace/jest.config.ts diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/qltest-discovery.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/qltest-discovery.test.ts new file mode 100644 index 000000000..0268f40f7 --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/qltest-discovery.test.ts @@ -0,0 +1,93 @@ +import { Uri, WorkspaceFolder } from "vscode"; +import * as fs from "fs-extra"; +import { join } from "path"; + +import { QLTestDiscovery } from "../../../src/qltest-discovery"; +import { DirectoryResult } from "tmp-promise"; +import * as tmp from "tmp-promise"; + +import "../../matchers/toEqualPath"; + +describe("qltest-discovery", () => { + describe("discoverTests", () => { + let directory: DirectoryResult; + + let baseDir: string; + let cDir: string; + let dFile: string; + let eFile: string; + let hDir: string; + let iFile: string; + let qlTestDiscover: QLTestDiscovery; + + beforeEach(async () => { + directory = await tmp.dir({ + unsafeCleanup: true, + }); + + const baseUri = Uri.file(directory.path); + baseDir = directory.path; + cDir = join(baseDir, "c"); + dFile = join(cDir, "d.ql"); + eFile = join(cDir, "e.ql"); + hDir = join(cDir, "f/g/h"); + iFile = join(hDir, "i.ql"); + + qlTestDiscover = new QLTestDiscovery( + { + uri: baseUri, + name: "My tests", + } as unknown as WorkspaceFolder, + { + resolveTests() { + return [dFile, eFile, iFile]; + }, + } as any, + ); + }); + + afterEach(async () => { + await directory.cleanup(); + }); + + it("should run discovery", async () => { + const result = await (qlTestDiscover as any).discover(); + expect(result.watchPath).toEqualPath(baseDir); + expect(result.testDirectory.path).toEqualPath(baseDir); + expect(result.testDirectory.name).toBe("My tests"); + + let children = result.testDirectory.children; + expect(children.length).toBe(1); + + expect(children[0].path).toEqualPath(cDir); + expect(children[0].name).toBe("c"); + + children = children[0].children; + expect(children.length).toBe(3); + + expect(children[0].path).toEqualPath(dFile); + expect(children[0].name).toBe("d.ql"); + expect(children[1].path).toEqualPath(eFile); + expect(children[1].name).toBe("e.ql"); + + // A merged foler + expect(children[2].path).toEqualPath(hDir); + expect(children[2].name).toBe("f / g / h"); + + children = children[2].children; + expect(children[0].path).toEqualPath(iFile); + expect(children[0].name).toBe("i.ql"); + }); + + it("should avoid discovery if a folder does not exist", async () => { + await fs.remove(baseDir); + + const result = await (qlTestDiscover as any).discover(); + expect(result.watchPath).toEqualPath(baseDir); + expect(result.testDirectory.path).toEqualPath(baseDir); + expect(result.testDirectory.name).toBe("My tests"); + + expect(result.testDirectory.children.length).toBe(0); + }); + }); +}); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/activation/activation.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/activation/activation.test.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/activation/activation.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/activation/activation.test.ts diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/archive-filesystem-provider.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/archive-filesystem-provider.test.ts similarity index 99% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/archive-filesystem-provider.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/archive-filesystem-provider.test.ts index 8f924ae32..7ca3c6ef0 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/archive-filesystem-provider.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/archive-filesystem-provider.test.ts @@ -7,7 +7,7 @@ import { decodeSourceArchiveUri, ZipFileReference, zipArchiveScheme, -} from "../../archive-filesystem-provider"; +} from "../../../src/archive-filesystem-provider"; import { FileType, FileSystemError, Uri } from "vscode"; describe("archive-filesystem-provider", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/astViewer.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/astViewer.test.ts similarity index 97% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/astViewer.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/astViewer.test.ts index dd152a7e8..12c4f8606 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/astViewer.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/astViewer.test.ts @@ -1,9 +1,9 @@ import { readFile } from "fs-extra"; import { load } from "js-yaml"; -import { AstViewer, AstItem } from "../../astViewer"; +import { AstViewer, AstItem } from "../../../src/astViewer"; import { commands, Range, Uri } from "vscode"; -import { DatabaseItem } from "../../databases"; +import { DatabaseItem } from "../../../src/databases"; import { testDisposeHandler } from "../test-dispose-handler"; describe("AstViewer", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/astBuilder.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/astBuilder.test.ts similarity index 95% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/astBuilder.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/astBuilder.test.ts index 143e11b19..669bf611f 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/astBuilder.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/astBuilder.test.ts @@ -1,10 +1,10 @@ import { readFileSync } from "fs-extra"; -import AstBuilder from "../../../contextual/astBuilder"; -import { CodeQLCliServer } from "../../../cli"; -import { DatabaseItem } from "../../../databases"; +import AstBuilder from "../../../../src/contextual/astBuilder"; +import { CodeQLCliServer } from "../../../../src/cli"; +import { DatabaseItem } from "../../../../src/databases"; import { Uri } from "vscode"; -import { QueryWithResults } from "../../../run-queries-shared"; +import { QueryWithResults } from "../../../../src/run-queries-shared"; /** * diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/fileRangeFromURI.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/fileRangeFromURI.test.ts similarity index 92% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/fileRangeFromURI.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/fileRangeFromURI.test.ts index 5a4607fda..d6a5d1148 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/fileRangeFromURI.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/fileRangeFromURI.test.ts @@ -1,11 +1,11 @@ import { Uri, Range } from "vscode"; -import fileRangeFromURI from "../../../contextual/fileRangeFromURI"; -import { DatabaseItem } from "../../../databases"; +import fileRangeFromURI from "../../../../src/contextual/fileRangeFromURI"; +import { DatabaseItem } from "../../../../src/databases"; import { WholeFileLocation, LineColumnLocation, -} from "../../../pure/bqrs-cli-types"; +} from "../../../../src/pure/bqrs-cli-types"; describe("fileRangeFromURI", () => { it("should return undefined when value is not a file URI", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/queryResolver.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/queryResolver.test.ts similarity index 81% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/queryResolver.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/queryResolver.test.ts index db07a865d..d06f9a51f 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/contextual/queryResolver.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/contextual/queryResolver.test.ts @@ -1,20 +1,18 @@ import { load } from "js-yaml"; import * as fs from "fs-extra"; -import { KeyType } from "../../../contextual/keyType"; -import { getErrorMessage } from "../../../pure/helpers-pure"; +import { KeyType } from "../../../../src/contextual/keyType"; +import { getErrorMessage } from "../../../../src/pure/helpers-pure"; -import * as helpers from "../../../helpers"; +import * as helpers from "../../../../src/helpers"; import { qlpackOfDatabase, resolveQueries, -} from "../../../contextual/queryResolver"; -import { CodeQLCliServer } from "../../../cli"; -import { DatabaseItem } from "../../../databases"; +} from "../../../../src/contextual/queryResolver"; +import { CodeQLCliServer } from "../../../../src/cli"; +import { DatabaseItem } from "../../../../src/databases"; describe("queryResolver", () => { - let writeFileSpy: jest.SpiedFunction; - let getQlPackForDbschemeSpy: jest.SpiedFunction< typeof helpers.getQlPackForDbscheme >; @@ -30,10 +28,6 @@ describe("queryResolver", () => { }; beforeEach(() => { - writeFileSpy = jest - .spyOn(fs, "writeFile") - .mockImplementation(() => Promise.resolve()); - getQlPackForDbschemeSpy = jest .spyOn(helpers, "getQlPackForDbscheme") .mockResolvedValue({ @@ -61,13 +55,15 @@ describe("queryResolver", () => { KeyType.DefinitionQuery, ); expect(result).toEqual(["a", "b"]); - expect(writeFileSpy).toHaveBeenNthCalledWith( - 1, - expect.stringMatching(/.qls$/), - expect.anything(), - expect.anything(), + + expect(mockCli.resolveQueriesInSuite).toHaveBeenCalledWith( + expect.stringMatching(/\.qls$/), + [], ); - expect(load(writeFileSpy.mock.calls[0][1])).toEqual([ + + const fileName = mockCli.resolveQueriesInSuite.mock.calls[0][0]; + + expect(load(await fs.readFile(fileName, "utf-8"))).toEqual([ { from: "my-qlpack", queries: ".", @@ -95,13 +91,15 @@ describe("queryResolver", () => { KeyType.DefinitionQuery, ); expect(result).toEqual(["a", "b"]); - expect(writeFileSpy).toHaveBeenNthCalledWith( - 1, - expect.stringMatching(/.qls$/), - expect.anything(), - expect.anything(), + + expect(mockCli.resolveQueriesInSuite).toHaveBeenCalledWith( + expect.stringMatching(/\.qls$/), + [], ); - expect(load(writeFileSpy.mock.calls[0][1])).toEqual([ + + const fileName = mockCli.resolveQueriesInSuite.mock.calls[0][0]; + + expect(load(await fs.readFile(fileName, "utf-8"))).toEqual([ { from: "my-qlpack2", queries: ".", diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/archive-filesystem-provider-test/single_file.zip b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/archive-filesystem-provider-test/single_file.zip similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/archive-filesystem-provider-test/single_file.zip rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/archive-filesystem-provider-test/single_file.zip diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/archive-filesystem-provider-test/zip_with_folder.zip b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/archive-filesystem-provider-test/zip_with_folder.zip similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/archive-filesystem-provider-test/zip_with_folder.zip rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/archive-filesystem-provider-test/zip_with_folder.zip diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/astBuilder.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/astBuilder.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/astBuilder.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/astBuilder.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/astViewer.yml b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/astViewer.yml similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/astViewer.yml rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/astViewer.yml diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/q0.ql b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/q0.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/q0.ql rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/q0.ql diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/q1.ql b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/q1.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/q1.ql rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/q1.ql diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/nwo.txt b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/nwo.txt similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/nwo.txt rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/nwo.txt diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/resultcount.txt b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/resultcount.txt similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/resultcount.txt rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/resultcount.txt diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.bqrs b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.bqrs similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.bqrs rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.bqrs diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.csv b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.csv similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.csv rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.csv diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.md b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.md similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.md rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.md diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.sarif b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.sarif similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.sarif rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/11111111/results.sarif diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/nwo.txt b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/nwo.txt similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/nwo.txt rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/nwo.txt diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/resultcount.txt b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/resultcount.txt similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/resultcount.txt rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/resultcount.txt diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.bqrs b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.bqrs similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.bqrs rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.bqrs diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.csv b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.csv similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.csv rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.csv diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.md b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.md similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.md rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.md diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.sarif b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.sarif similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.sarif rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/171543249/results.sarif diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/query-result.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/query-result.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/query-result.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/query-result.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/query.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/query.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/query.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/query.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/timestamp b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/timestamp similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/timestamp rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 1-6sBi6oaky_fxqXW2NA4bx/timestamp diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/nwo.txt b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/nwo.txt similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/nwo.txt rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/nwo.txt diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/resultcount.txt b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/resultcount.txt similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/resultcount.txt rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/resultcount.txt diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.bqrs b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.bqrs similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.bqrs rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.bqrs diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.csv b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.csv similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.csv rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.csv diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.md b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.md similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.md rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.md diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.sarif b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.sarif similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.sarif rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/171544171/results.sarif diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/query-result.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/query-result.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/query-result.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/query-result.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/query.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/query.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/query.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/query.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/timestamp b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/timestamp similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/timestamp rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/queries/MRVA Integration test 2-UL-vbKAjP8ffObxjsp7hN/timestamp diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/query-with-results/analyses-results.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/query-with-results/analyses-results.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/query-with-results/analyses-results.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/query-with-results/analyses-results.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/query-with-results/query.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/query-with-results/query.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/query-with-results/query.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/query-with-results/query.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/workspace-query-history.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/workspace-query-history.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/remote-queries/workspace-query-history.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/remote-queries/workspace-query-history.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/emptyResultsSarif.sarif b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/sarif/emptyResultsSarif.sarif similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/emptyResultsSarif.sarif rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/sarif/emptyResultsSarif.sarif diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/invalidSarif.sarif b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/sarif/invalidSarif.sarif similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/invalidSarif.sarif rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/sarif/invalidSarif.sarif diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/validSarif.sarif b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/sarif/validSarif.sarif similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/sarif/validSarif.sarif rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/sarif/validSarif.sarif diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/variant-analysis/q0.ql b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/q0.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/variant-analysis/q0.ql rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/q0.ql diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/variant-analysis/q1.ql b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/q1.ql similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/variant-analysis/q1.ql rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/q1.ql diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/data/variant-analysis/workspace-query-history.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/workspace-query-history.json similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/data/variant-analysis/workspace-query-history.json rename to extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/workspace-query-history.json diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/databaseFetcher.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/databaseFetcher.test.ts similarity index 86% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/databaseFetcher.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/databaseFetcher.test.ts index 15d7e4018..a032b10c1 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/databaseFetcher.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/databaseFetcher.test.ts @@ -6,9 +6,8 @@ import { QuickPickItem, window } from "vscode"; import { convertGithubNwoToDatabaseUrl, findDirWithFile, -} from "../../databaseFetcher"; +} from "../../../src/databaseFetcher"; import * as Octokit from "@octokit/rest"; -import { looksLikeGithubRepo } from "../../databases/github-nwo"; // These tests make API calls and may need extra time to complete. jest.setTimeout(10000); @@ -129,25 +128,6 @@ describe("databaseFetcher", () => { }); }); - describe("looksLikeGithubRepo", () => { - it("should handle invalid urls", () => { - expect(looksLikeGithubRepo("")).toBe(false); - expect(looksLikeGithubRepo("http://github.com/foo/bar")).toBe(false); - expect(looksLikeGithubRepo("https://ww.github.com/foo/bar")).toBe(false); - expect(looksLikeGithubRepo("https://ww.github.com/foo")).toBe(false); - expect(looksLikeGithubRepo("foo")).toBe(false); - }); - - it("should handle valid urls", () => { - expect(looksLikeGithubRepo("https://github.com/foo/bar")).toBe(true); - expect(looksLikeGithubRepo("https://www.github.com/foo/bar")).toBe(true); - expect(looksLikeGithubRepo("https://github.com/foo/bar/sub/pages")).toBe( - true, - ); - expect(looksLikeGithubRepo("foo/bar")).toBe(true); - }); - }); - describe("findDirWithFile", () => { let dir: tmp.DirResult; beforeEach(() => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/databases-ui.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/databases-ui.test.ts similarity index 97% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/databases-ui.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/databases-ui.test.ts index 0b916ace1..84970df47 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/databases-ui.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/databases-ui.test.ts @@ -9,9 +9,9 @@ import { } from "fs-extra"; import { Uri } from "vscode"; -import { DatabaseUI } from "../../databases-ui"; +import { DatabaseUI } from "../../../src/databases-ui"; import { testDisposeHandler } from "../test-dispose-handler"; -import { Credentials } from "../../authentication"; +import { Credentials } from "../../../src/authentication"; describe("databases-ui", () => { describe("fixDbUri", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/distribution.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/distribution.test.ts similarity index 84% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/distribution.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/distribution.test.ts index 6c29f7e31..131ef4699 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/distribution.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/distribution.test.ts @@ -1,18 +1,30 @@ -import { sep } from "path"; import * as fetch from "node-fetch"; import { Range } from "semver"; -import * as helpers from "../../helpers"; -import { extLogger } from "../../common"; +import * as helpers from "../../../src/helpers"; +import { extLogger } from "../../../src/common"; import * as fs from "fs-extra"; +import * as path from "path"; import * as os from "os"; +import * as tmp from "tmp-promise"; import { GithubRelease, GithubReleaseAsset, ReleasesApiConsumer, getExecutableFromDirectory, DistributionManager, -} from "../../distribution"; +} from "../../../src/distribution"; +import { DirectoryResult } from "tmp-promise"; + +jest.mock("os", () => { + const original = jest.requireActual("os"); + return { + ...original, + platform: jest.fn(), + }; +}); + +const mockedOS = jest.mocked(os); describe("Releases API consumer", () => { const owner = "someowner"; @@ -192,17 +204,16 @@ describe("Releases API consumer", () => { }); describe("Launcher path", () => { - const pathToCmd = `abc${sep}codeql.cmd`; - const pathToExe = `abc${sep}codeql.exe`; - let warnSpy: jest.SpiedFunction; let errorSpy: jest.SpiedFunction; let logSpy: jest.SpiedFunction; - let pathExistsSpy: jest.SpiedFunction; - let launcherThatExists = ""; + let directory: DirectoryResult; - beforeEach(() => { + let pathToCmd: string; + let pathToExe: string; + + beforeEach(async () => { warnSpy = jest .spyOn(helpers, "showAndLogWarningMessage") .mockResolvedValue(undefined); @@ -210,22 +221,25 @@ describe("Launcher path", () => { .spyOn(helpers, "showAndLogErrorMessage") .mockResolvedValue(undefined); logSpy = jest.spyOn(extLogger, "log").mockResolvedValue(undefined); - pathExistsSpy = jest - .spyOn(fs, "pathExists") - .mockImplementation(async (path: string) => { - return path.endsWith(launcherThatExists); - }); - jest.spyOn(os, "platform").mockReturnValue("win32"); + mockedOS.platform.mockReturnValue("win32"); + + directory = await tmp.dir({ + unsafeCleanup: true, + }); + + pathToCmd = path.join(directory.path, "codeql.cmd"); + pathToExe = path.join(directory.path, "codeql.exe"); + }); + + afterEach(async () => { + await directory.cleanup(); }); it("should not warn with proper launcher name", async () => { - launcherThatExists = "codeql.exe"; - const result = await getExecutableFromDirectory("abc"); - expect(pathExistsSpy).toBeCalledWith(pathToExe); + await fs.writeFile(pathToExe, ""); - // correct launcher has been found, so alternate one not looked for - expect(pathExistsSpy).not.toBeCalledWith(pathToCmd); + const result = await getExecutableFromDirectory(directory.path); // no warning message expect(warnSpy).not.toHaveBeenCalled(); @@ -235,10 +249,9 @@ describe("Launcher path", () => { }); it("should warn when using a hard-coded deprecated launcher name", async () => { - launcherThatExists = "codeql.cmd"; - const result = await getExecutableFromDirectory("abc"); - expect(pathExistsSpy).toBeCalledWith(pathToExe); - expect(pathExistsSpy).toBeCalledWith(pathToCmd); + await fs.writeFile(pathToCmd, ""); + + const result = await getExecutableFromDirectory(directory.path); // Should have opened a warning message expect(warnSpy).toHaveBeenCalled(); @@ -248,10 +261,7 @@ describe("Launcher path", () => { }); it("should avoid warn when no launcher is found", async () => { - launcherThatExists = "xxx"; - const result = await getExecutableFromDirectory("abc", false); - expect(pathExistsSpy).toBeCalledWith(pathToExe); - expect(pathExistsSpy).toBeCalledWith(pathToCmd); + const result = await getExecutableFromDirectory(directory.path, false); // no warning message expect(warnSpy).not.toHaveBeenCalled(); @@ -261,10 +271,7 @@ describe("Launcher path", () => { }); it("should warn when no launcher is found", async () => { - launcherThatExists = "xxx"; const result = await getExecutableFromDirectory("abc", true); - expect(pathExistsSpy).toBeCalledWith(pathToExe); - expect(pathExistsSpy).toBeCalledWith(pathToCmd); // no warning message expect(warnSpy).not.toHaveBeenCalled(); @@ -274,12 +281,13 @@ describe("Launcher path", () => { }); it("should not warn when deprecated launcher is used, but no new launcher is available", async function () { + await fs.writeFile(pathToCmd, ""); + const manager = new DistributionManager( { customCodeQlPath: pathToCmd } as any, {} as any, undefined as any, ); - launcherThatExists = "codeql.cmd"; const result = await manager.getCodeQlPathWithoutVersionCheck(); expect(result).toBe(pathToCmd); @@ -290,12 +298,14 @@ describe("Launcher path", () => { }); it("should warn when deprecated launcher is used, and new launcher is available", async () => { + await fs.writeFile(pathToCmd, ""); + await fs.writeFile(pathToExe, ""); + const manager = new DistributionManager( { customCodeQlPath: pathToCmd } as any, {} as any, undefined as any, ); - launcherThatExists = ""; // pretend both launchers exist const result = await manager.getCodeQlPathWithoutVersionCheck(); expect(result).toBe(pathToCmd); @@ -311,7 +321,6 @@ describe("Launcher path", () => { {} as any, undefined as any, ); - launcherThatExists = "xxx"; // pretend neither launcher exists const result = await manager.getCodeQlPathWithoutVersionCheck(); expect(result).toBeUndefined(); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/download-link.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/download-link.test.ts similarity index 94% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/download-link.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/download-link.test.ts index 2dd8f0e9d..7cf3de725 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/download-link.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/download-link.test.ts @@ -3,7 +3,7 @@ import { join } from "path"; import { DownloadLink, createDownloadPath, -} from "../../remote-queries/download-link"; +} from "../../../src/remote-queries/download-link"; describe("createDownloadPath", () => { it("should return the correct path", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/eval-log-tree-builder.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/eval-log-tree-builder.test.ts similarity index 95% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/eval-log-tree-builder.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/eval-log-tree-builder.test.ts index 9fe38eacd..67d2d0c5d 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/eval-log-tree-builder.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/eval-log-tree-builder.test.ts @@ -1,5 +1,5 @@ -import EvalLogTreeBuilder from "../../eval-log-tree-builder"; -import { EvalLogData } from "../../pure/log-summary-parser"; +import EvalLogTreeBuilder from "../../../src/eval-log-tree-builder"; +import { EvalLogData } from "../../../src/pure/log-summary-parser"; describe("EvalLogTreeBuilder", () => { it("should build the log tree roots", async () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/eval-log-viewer.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/eval-log-viewer.test.ts similarity index 98% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/eval-log-viewer.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/eval-log-viewer.test.ts index c1f0825d6..b4480ddc4 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/eval-log-viewer.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/eval-log-viewer.test.ts @@ -3,7 +3,7 @@ import { ChildEvalLogTreeItem, EvalLogTreeItem, EvalLogViewer, -} from "../../eval-log-viewer"; +} from "../../../src/eval-log-viewer"; import { testDisposeHandler } from "../test-dispose-handler"; describe("EvalLogViewer", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts similarity index 99% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts index c08a2ac25..fe464c1c0 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts @@ -25,8 +25,8 @@ import { showBinaryChoiceWithUrlDialog, showInformationMessageWithAction, walkDirectory, -} from "../../helpers"; -import { reportStreamProgress } from "../../commandRunner"; +} from "../../../src/helpers"; +import { reportStreamProgress } from "../../../src/commandRunner"; describe("helpers", () => { describe("Invocation rate limiter", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/history-item-label-provider.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/history-item-label-provider.test.ts similarity index 95% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/history-item-label-provider.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/history-item-label-provider.test.ts index 57947be09..5f2f4964e 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/history-item-label-provider.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/history-item-label-provider.test.ts @@ -1,9 +1,9 @@ import { env } from "vscode"; -import { QueryHistoryConfig } from "../../config"; -import { HistoryItemLabelProvider } from "../../history-item-label-provider"; -import { createMockLocalQueryInfo } from "../factories/local-queries/local-query-history-item"; -import { createMockRemoteQueryHistoryItem } from "../factories/remote-queries/remote-query-history-item"; -import { QueryStatus } from "../../query-status"; +import { QueryHistoryConfig } from "../../../src/config"; +import { HistoryItemLabelProvider } from "../../../src/history-item-label-provider"; +import { createMockLocalQueryInfo } from "../../factories/local-queries/local-query-history-item"; +import { createMockRemoteQueryHistoryItem } from "../../factories/remote-queries/remote-query-history-item"; +import { QueryStatus } from "../../../src/query-status"; describe("HistoryItemLabelProvider", () => { let labelProvider: HistoryItemLabelProvider; diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/index.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/index.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/index.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/index.ts diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/interface-utils.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/interface-utils.test.ts similarity index 96% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/interface-utils.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/interface-utils.test.ts index 5e781f47c..753e71845 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/interface-utils.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/interface-utils.test.ts @@ -9,9 +9,12 @@ import { } from "vscode"; import { basename } from "path"; import { fileSync, FileResult } from "tmp"; -import { fileUriToWebviewUri, tryResolveLocation } from "../../interface-utils"; -import { getDefaultResultSetName } from "../../pure/interface-types"; -import { DatabaseItem } from "../../databases"; +import { + fileUriToWebviewUri, + tryResolveLocation, +} from "../../../src/interface-utils"; +import { getDefaultResultSetName } from "../../../src/pure/interface-types"; +import { DatabaseItem } from "../../../src/databases"; describe("interface-utils", () => { describe("webview uri conversion", () => { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/jest-runner-vscode.config.js b/extensions/ql-vscode/test/vscode-tests/no-workspace/jest-runner-vscode.config.js similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/jest-runner-vscode.config.js rename to extensions/ql-vscode/test/vscode-tests/no-workspace/jest-runner-vscode.config.js diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/jest.config.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/jest.config.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/jest.config.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/jest.config.ts diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-history-info.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history-info.test.ts similarity index 90% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/query-history-info.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/query-history-info.test.ts index 212873501..c8fe45fe6 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-history-info.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history-info.test.ts @@ -1,20 +1,20 @@ -import { QueryStatus } from "../../query-status"; +import { QueryStatus } from "../../../src/query-status"; import { buildRepoLabel, getActionsWorkflowRunUrl, getQueryId, getQueryText, getRawQueryName, -} from "../../query-history-info"; -import { VariantAnalysisHistoryItem } from "../../remote-queries/variant-analysis-history-item"; -import { createMockVariantAnalysis } from "../factories/remote-queries/shared/variant-analysis"; -import { createMockScannedRepos } from "../factories/remote-queries/shared/scanned-repositories"; -import { createMockLocalQueryInfo } from "../factories/local-queries/local-query-history-item"; -import { createMockRemoteQueryHistoryItem } from "../factories/remote-queries/remote-query-history-item"; +} from "../../../src/query-history-info"; +import { VariantAnalysisHistoryItem } from "../../../src/remote-queries/variant-analysis-history-item"; +import { createMockVariantAnalysis } from "../../factories/remote-queries/shared/variant-analysis"; +import { createMockScannedRepos } from "../../factories/remote-queries/shared/scanned-repositories"; +import { createMockLocalQueryInfo } from "../../factories/local-queries/local-query-history-item"; +import { createMockRemoteQueryHistoryItem } from "../../factories/remote-queries/remote-query-history-item"; import { VariantAnalysisRepoStatus, VariantAnalysisStatus, -} from "../../remote-queries/shared/variant-analysis"; +} from "../../../src/remote-queries/shared/variant-analysis"; describe("Query history info", () => { const date = new Date("2022-01-01T00:00:00.000Z"); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history.test.ts similarity index 97% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/query-history.test.ts index 848a4d02a..747b311c4 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history.test.ts @@ -2,46 +2,49 @@ import { readdirSync, mkdirSync, writeFileSync } from "fs-extra"; import { join } from "path"; import * as vscode from "vscode"; -import { extLogger } from "../../common"; -import { registerQueryHistoryScrubber } from "../../query-history-scrubber"; +import { extLogger } from "../../../src/common"; +import { registerQueryHistoryScrubber } from "../../../src/query-history-scrubber"; import { HistoryTreeDataProvider, QueryHistoryManager, SortOrder, -} from "../../query-history"; -import { QueryHistoryConfig, QueryHistoryConfigListener } from "../../config"; -import { LocalQueryInfo } from "../../query-results"; -import { DatabaseManager } from "../../databases"; +} from "../../../src/query-history"; +import { + QueryHistoryConfig, + QueryHistoryConfigListener, +} from "../../../src/config"; +import { LocalQueryInfo } from "../../../src/query-results"; +import { DatabaseManager } from "../../../src/databases"; import { dirSync } from "tmp-promise"; import { ONE_DAY_IN_MS, ONE_HOUR_IN_MS, THREE_HOURS_IN_MS, TWO_HOURS_IN_MS, -} from "../../pure/time"; -import { tmpDir } from "../../helpers"; -import { HistoryItemLabelProvider } from "../../history-item-label-provider"; -import { RemoteQueriesManager } from "../../remote-queries/remote-queries-manager"; -import { ResultsView } from "../../interface"; -import { EvalLogViewer } from "../../eval-log-viewer"; -import { QueryRunner } from "../../queryRunner"; -import { VariantAnalysisManager } from "../../remote-queries/variant-analysis-manager"; -import { QueryHistoryInfo } from "../../query-history-info"; +} from "../../../src/pure/time"; +import { tmpDir } from "../../../src/helpers"; +import { HistoryItemLabelProvider } from "../../../src/history-item-label-provider"; +import { RemoteQueriesManager } from "../../../src/remote-queries/remote-queries-manager"; +import { ResultsView } from "../../../src/interface"; +import { EvalLogViewer } from "../../../src/eval-log-viewer"; +import { QueryRunner } from "../../../src/queryRunner"; +import { VariantAnalysisManager } from "../../../src/remote-queries/variant-analysis-manager"; +import { QueryHistoryInfo } from "../../../src/query-history-info"; import { createMockLocalQueryInfo, createMockQueryWithResults, -} from "../factories/local-queries/local-query-history-item"; -import { createMockRemoteQueryHistoryItem } from "../factories/remote-queries/remote-query-history-item"; -import { RemoteQueryHistoryItem } from "../../remote-queries/remote-query-history-item"; +} from "../../factories/local-queries/local-query-history-item"; +import { createMockRemoteQueryHistoryItem } from "../../factories/remote-queries/remote-query-history-item"; +import { RemoteQueryHistoryItem } from "../../../src/remote-queries/remote-query-history-item"; import { shuffleHistoryItems } from "../utils/query-history-helpers"; -import { createMockVariantAnalysisHistoryItem } from "../factories/remote-queries/variant-analysis-history-item"; -import { VariantAnalysisHistoryItem } from "../../remote-queries/variant-analysis-history-item"; -import { QueryStatus } from "../../query-status"; -import { VariantAnalysisStatus } from "../../remote-queries/shared/variant-analysis"; -import * as ghActionsApiClient from "../../remote-queries/gh-api/gh-actions-api-client"; -import { Credentials } from "../../authentication"; +import { createMockVariantAnalysisHistoryItem } from "../../factories/remote-queries/variant-analysis-history-item"; +import { VariantAnalysisHistoryItem } from "../../../src/remote-queries/variant-analysis-history-item"; +import { QueryStatus } from "../../../src/query-status"; +import { VariantAnalysisStatus } from "../../../src/remote-queries/shared/variant-analysis"; +import * as ghActionsApiClient from "../../../src/remote-queries/gh-api/gh-actions-api-client"; +import { Credentials } from "../../../src/authentication"; import { QuickPickItem, TextEditor } from "vscode"; -import { WebviewReveal } from "../../interface-utils"; +import { WebviewReveal } from "../../../src/interface-utils"; describe("query-history", () => { const mockExtensionLocation = join(tmpDir.name, "mock-extension-location"); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-results.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-results.test.ts similarity index 97% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/query-results.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/query-results.test.ts index 56f7b2f92..c5000a68b 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/query-results.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-results.test.ts @@ -11,26 +11,29 @@ import { LocalQueryInfo, InitialQueryInfo, interpretResultsSarif, -} from "../../query-results"; -import { QueryWithResults } from "../../run-queries-shared"; +} from "../../../src/query-results"; +import { QueryWithResults } from "../../../src/run-queries-shared"; import { DatabaseInfo, SortDirection, SortedResultSetInfo, -} from "../../pure/interface-types"; -import { CodeQLCliServer, SourceInfo } from "../../cli"; +} from "../../../src/pure/interface-types"; +import { CodeQLCliServer, SourceInfo } from "../../../src/cli"; import { CancellationTokenSource, Uri } from "vscode"; -import { tmpDir } from "../../helpers"; +import { tmpDir } from "../../../src/helpers"; import { slurpQueryHistory, splatQueryHistory, -} from "../../query-serialization"; +} from "../../../src/query-serialization"; import { formatLegacyMessage, QueryInProgress, -} from "../../legacy-query-server/run-queries"; -import { EvaluationResult, QueryResultType } from "../../pure/legacy-messages"; -import { sleep } from "../../pure/time"; +} from "../../../src/legacy-query-server/run-queries"; +import { + EvaluationResult, + QueryResultType, +} from "../../../src/pure/legacy-messages"; +import { sleep } from "../../../src/pure/time"; describe("query-results", () => { let queryPath: string; diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/export-results.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/export-results.test.ts similarity index 77% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/export-results.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/export-results.test.ts index 8e9a0100a..28aad971e 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/export-results.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/export-results.test.ts @@ -1,10 +1,9 @@ import { join } from "path"; import { readFile } from "fs-extra"; -import { createMockExtensionContext } from "../index"; -import { Credentials } from "../../../authentication"; -import * as markdownGenerator from "../../../remote-queries/remote-queries-markdown-generation"; -import * as ghApiClient from "../../../remote-queries/gh-api/gh-api-client"; -import { exportRemoteQueryAnalysisResults } from "../../../remote-queries/export-results"; +import { Credentials } from "../../../../src/authentication"; +import * as markdownGenerator from "../../../../src/remote-queries/remote-queries-markdown-generation"; +import * as ghApiClient from "../../../../src/remote-queries/gh-api/gh-api-client"; +import { exportRemoteQueryAnalysisResults } from "../../../../src/remote-queries/export-results"; describe("export results", () => { describe("exportRemoteQueryAnalysisResults", () => { @@ -20,7 +19,6 @@ describe("export results", () => { .spyOn(ghApiClient, "createGist") .mockResolvedValue(undefined); - const ctx = createMockExtensionContext(); const query = JSON.parse( await readFile( join( @@ -41,7 +39,6 @@ describe("export results", () => { ); await exportRemoteQueryAnalysisResults( - ctx, "", query, analysesResults, diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts similarity index 90% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts index 22b8f78ae..d2de53e68 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/gh-api/gh-actions-api-client.test.ts @@ -1,12 +1,12 @@ -import { Credentials } from "../../../../authentication"; +import { Credentials } from "../../../../../src/authentication"; import { cancelRemoteQuery, cancelVariantAnalysis, getRepositoriesMetadata, -} from "../../../../remote-queries/gh-api/gh-actions-api-client"; -import { RemoteQuery } from "../../../../remote-queries/remote-query"; -import { createMockVariantAnalysis } from "../../../factories/remote-queries/shared/variant-analysis"; -import { VariantAnalysis } from "../../../../remote-queries/shared/variant-analysis"; +} from "../../../../../src/remote-queries/gh-api/gh-actions-api-client"; +import { RemoteQuery } from "../../../../../src/remote-queries/remote-query"; +import { createMockVariantAnalysis } from "../../../../factories/remote-queries/shared/variant-analysis"; +import { VariantAnalysis } from "../../../../../src/remote-queries/shared/variant-analysis"; jest.setTimeout(10000); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/remote-queries-api.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-queries-api.test.ts similarity index 98% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/remote-queries-api.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-queries-api.test.ts index c9c9e517f..fe96be07c 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/remote-queries-api.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-queries-api.test.ts @@ -1,6 +1,6 @@ import { EOL } from "os"; -import { parseResponse } from "../../../remote-queries/remote-queries-api"; -import { Repository } from "../../../remote-queries/shared/repository"; +import { parseResponse } from "../../../../src/remote-queries/remote-queries-api"; +import { Repository } from "../../../../src/remote-queries/shared/repository"; describe("parseResponse", () => { const controllerRepository: Repository = { diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts similarity index 94% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts index 20a785575..38921ccf9 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts @@ -17,21 +17,21 @@ import { window, workspace, } from "vscode"; -import { QueryHistoryConfig } from "../../../config"; -import { DatabaseManager } from "../../../databases"; -import { tmpDir, walkDirectory } from "../../../helpers"; -import { QueryHistoryManager } from "../../../query-history"; -import { Credentials } from "../../../authentication"; -import { AnalysesResultsManager } from "../../../remote-queries/analyses-results-manager"; -import { RemoteQueryResult } from "../../../remote-queries/shared/remote-query-result"; +import { QueryHistoryConfig } from "../../../../src/config"; +import { DatabaseManager } from "../../../../src/databases"; +import { tmpDir, walkDirectory } from "../../../../src/helpers"; +import { QueryHistoryManager } from "../../../../src/query-history"; +import { Credentials } from "../../../../src/authentication"; +import { AnalysesResultsManager } from "../../../../src/remote-queries/analyses-results-manager"; +import { RemoteQueryResult } from "../../../../src/remote-queries/shared/remote-query-result"; import { DisposableBucket } from "../../disposable-bucket"; import { testDisposeHandler } from "../../test-dispose-handler"; -import { HistoryItemLabelProvider } from "../../../history-item-label-provider"; -import { RemoteQueriesManager } from "../../../remote-queries/remote-queries-manager"; -import { ResultsView } from "../../../interface"; -import { EvalLogViewer } from "../../../eval-log-viewer"; -import { QueryRunner } from "../../../queryRunner"; -import { VariantAnalysisManager } from "../../../remote-queries/variant-analysis-manager"; +import { HistoryItemLabelProvider } from "../../../../src/history-item-label-provider"; +import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager"; +import { ResultsView } from "../../../../src/interface"; +import { EvalLogViewer } from "../../../../src/eval-log-viewer"; +import { QueryRunner } from "../../../../src/queryRunner"; +import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager"; // set a higher timeout since recursive delete may take a while, expecially on Windows. jest.setTimeout(120000); @@ -279,7 +279,6 @@ describe("Remote queries and query history manager", () => { jest.spyOn(Credentials, "initialize").mockResolvedValue(mockCredentials); arm = new AnalysesResultsManager( - {} as ExtensionContext, mockCliServer, join(STORAGE_DIR, "queries"), mockLogger, diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts similarity index 82% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts index d2e29a7ad..f43b8ee30 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts @@ -1,21 +1,24 @@ import { QuickPickItem, window } from "vscode"; -import * as fs from "fs-extra"; -import { UserCancellationException } from "../../../commandRunner"; +import { join } from "path"; +import { DirectoryResult } from "tmp-promise"; +import * as tmp from "tmp-promise"; +import { ensureDir, writeFile, writeJson } from "fs-extra"; +import { UserCancellationException } from "../../../../src/commandRunner"; -import * as config from "../../../config"; -import { getRepositorySelection } from "../../../remote-queries/repository-selection"; -import { DbManager } from "../../../databases/db-manager"; +import * as config from "../../../../src/config"; +import { getRepositorySelection } from "../../../../src/remote-queries/repository-selection"; +import { DbManager } from "../../../../src/databases/db-manager"; import { DbItem, DbItemKind, RemoteRepoDbItem, -} from "../../../databases/db-item"; +} from "../../../../src/databases/db-item"; describe("repository selection", () => { - describe("newQueryRunExperience true", () => { + describe("variantAnalysisReposPanel true", () => { beforeEach(() => { jest - .spyOn(config, "isNewQueryRunExperienceEnabled") + .spyOn(config, "isVariantAnalysisReposPanelEnabled") .mockReturnValue(true); }); @@ -39,7 +42,7 @@ describe("repository selection", () => { it("should log an error when an empty remote user defined list is selected", async () => { const dbManager = setUpDbManager({ - kind: DbItemKind.RemoteUserDefinedList, + kind: DbItemKind.VariantAnalysisUserDefinedList, repos: [] as RemoteRepoDbItem[], } as DbItem); @@ -63,7 +66,7 @@ describe("repository selection", () => { it("should return correct selection when remote user defined list is selected", async () => { const dbManager = setUpDbManager({ - kind: DbItemKind.RemoteUserDefinedList, + kind: DbItemKind.VariantAnalysisUserDefinedList, repos: [ { repoFullName: "owner1/repo1" }, { repoFullName: "owner1/repo2" }, @@ -115,7 +118,7 @@ describe("repository selection", () => { } }); - describe("newQueryRunExperience false", () => { + describe("variantAnalysisReposPanel false", () => { let quickPickSpy: jest.SpiedFunction; let showInputBoxSpy: jest.SpiedFunction; @@ -126,10 +129,6 @@ describe("repository selection", () => { typeof config.getRemoteRepositoryListsPath >; - let pathExistsStub: jest.SpiedFunction; - let fsStatStub: jest.SpiedFunction; - let fsReadFileStub: jest.SpiedFunction; - beforeEach(() => { quickPickSpy = jest .spyOn(window, "showQuickPick") @@ -144,16 +143,6 @@ describe("repository selection", () => { getRemoteRepositoryListsPathSpy = jest .spyOn(config, "getRemoteRepositoryListsPath") .mockReturnValue(undefined); - - pathExistsStub = jest - .spyOn(fs, "pathExists") - .mockImplementation(() => false); - fsStatStub = jest - .spyOn(fs, "stat") - .mockRejectedValue(new Error("not found")); - fsReadFileStub = jest - .spyOn(fs, "readFile") - .mockRejectedValue(new Error("not found")); }); describe("repo lists from settings", () => { it("should allow selection from repo lists from your pre-defined config", async () => { @@ -362,21 +351,31 @@ describe("repository selection", () => { }); describe("external repository lists file", () => { + let directory: DirectoryResult; + + beforeEach(async () => { + directory = await tmp.dir({ + unsafeCleanup: true, + }); + }); + + afterEach(async () => { + await directory.cleanup(); + }); + it("should fail if path does not exist", async () => { - const fakeFilePath = "/path/that/does/not/exist.json"; - getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath); - pathExistsStub.mockImplementation(() => false); + const nonExistingFile = join(directory.path, "non-existing-file.json"); + getRemoteRepositoryListsPathSpy.mockReturnValue(nonExistingFile); await expect(getRepositorySelection()).rejects.toThrow( - `External repository lists file does not exist at ${fakeFilePath}`, + `External repository lists file does not exist at ${nonExistingFile}`, ); }); it("should fail if path points to directory", async () => { - const fakeFilePath = "/path/to/dir"; - getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath); - pathExistsStub.mockImplementation(() => true); - fsStatStub.mockResolvedValue({ isDirectory: () => true } as any); + const existingDirectory = join(directory.path, "directory"); + await ensureDir(existingDirectory); + getRemoteRepositoryListsPathSpy.mockReturnValue(existingDirectory); await expect(getRepositorySelection()).rejects.toThrow( "External repository lists path should not point to a directory", @@ -384,11 +383,9 @@ describe("repository selection", () => { }); it("should fail if file does not have valid JSON", async () => { - const fakeFilePath = "/path/to/file.json"; - getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath); - pathExistsStub.mockImplementation(() => true); - fsStatStub.mockResolvedValue({ isDirectory: () => false } as any); - fsReadFileStub.mockResolvedValue("not-json" as any as Buffer); + const existingFile = join(directory.path, "repository-lists.json"); + await writeFile(existingFile, "not-json"); + getRemoteRepositoryListsPathSpy.mockReturnValue(existingFile); await expect(getRepositorySelection()).rejects.toThrow( "Invalid repository lists file. It should contain valid JSON.", @@ -396,11 +393,9 @@ describe("repository selection", () => { }); it("should fail if file contains array", async () => { - const fakeFilePath = "/path/to/file.json"; - getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath); - pathExistsStub.mockImplementation(() => true); - fsStatStub.mockResolvedValue({ isDirectory: () => false } as any); - fsReadFileStub.mockResolvedValue("[]" as any as Buffer); + const existingFile = join(directory.path, "repository-lists.json"); + await writeJson(existingFile, []); + getRemoteRepositoryListsPathSpy.mockReturnValue(existingFile); await expect(getRepositorySelection()).rejects.toThrow( "Invalid repository lists file. It should be an object mapping names to a list of repositories.", @@ -408,16 +403,12 @@ describe("repository selection", () => { }); it("should fail if file does not contain repo lists in the right format", async () => { - const fakeFilePath = "/path/to/file.json"; - getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath); - pathExistsStub.mockImplementation(() => true); - fsStatStub.mockResolvedValue({ isDirectory: () => false } as any); + const existingFile = join(directory.path, "repository-lists.json"); const repoLists = { list1: "owner1/repo1", }; - fsReadFileStub.mockResolvedValue( - JSON.stringify(repoLists) as any as Buffer, - ); + await writeJson(existingFile, repoLists); + getRemoteRepositoryListsPathSpy.mockReturnValue(existingFile); await expect(getRepositorySelection()).rejects.toThrow( "Invalid repository lists file. It should contain an array of repositories for each list.", @@ -425,17 +416,13 @@ describe("repository selection", () => { }); it("should get repo lists from file", async () => { - const fakeFilePath = "/path/to/file.json"; - getRemoteRepositoryListsPathSpy.mockReturnValue(fakeFilePath); - pathExistsStub.mockImplementation(() => true); - fsStatStub.mockResolvedValue({ isDirectory: () => false } as any); + const existingFile = join(directory.path, "repository-lists.json"); const repoLists = { list1: ["owner1/repo1", "owner2/repo2"], list2: ["owner3/repo3"], }; - fsReadFileStub.mockResolvedValue( - JSON.stringify(repoLists) as any as Buffer, - ); + await writeJson(existingFile, repoLists); + getRemoteRepositoryListsPathSpy.mockReturnValue(existingFile); getRemoteRepositoryListsSpy.mockReturnValue({ list3: ["onwer4/repo4"], list4: [], diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts similarity index 90% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts index 1a369e061..9d8f1bfcd 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/remote-queries/variant-analysis-history.test.ts @@ -9,18 +9,18 @@ import { import { join } from "path"; import { commands, ExtensionContext, Uri } from "vscode"; -import { QueryHistoryConfig } from "../../../config"; -import { DatabaseManager } from "../../../databases"; -import { tmpDir, walkDirectory } from "../../../helpers"; -import { QueryHistoryManager } from "../../../query-history"; +import { QueryHistoryConfig } from "../../../../src/config"; +import { DatabaseManager } from "../../../../src/databases"; +import { tmpDir, walkDirectory } from "../../../../src/helpers"; +import { QueryHistoryManager } from "../../../../src/query-history"; import { DisposableBucket } from "../../disposable-bucket"; import { testDisposeHandler } from "../../test-dispose-handler"; -import { HistoryItemLabelProvider } from "../../../history-item-label-provider"; -import { RemoteQueriesManager } from "../../../remote-queries/remote-queries-manager"; -import { ResultsView } from "../../../interface"; -import { EvalLogViewer } from "../../../eval-log-viewer"; -import { QueryRunner } from "../../../queryRunner"; -import { VariantAnalysisManager } from "../../../remote-queries/variant-analysis-manager"; +import { HistoryItemLabelProvider } from "../../../../src/history-item-label-provider"; +import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager"; +import { ResultsView } from "../../../../src/interface"; +import { EvalLogViewer } from "../../../../src/eval-log-viewer"; +import { QueryRunner } from "../../../../src/queryRunner"; +import { VariantAnalysisManager } from "../../../../src/remote-queries/variant-analysis-manager"; // set a higher timeout since recursive delete may take a while, expecially on Windows. jest.setTimeout(120000); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/run-queries.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts similarity index 95% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/run-queries.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts index fb5708d53..0d0b705c4 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/run-queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts @@ -7,15 +7,15 @@ import { compileQuery, registerDatabases, deregisterDatabases, -} from "../../pure/legacy-messages"; -import * as config from "../../config"; -import { tmpDir } from "../../helpers"; -import { QueryServerClient } from "../../legacy-query-server/queryserver-client"; -import { CodeQLCliServer } from "../../cli"; -import { SELECT_QUERY_NAME } from "../../contextual/locationFinder"; -import { QueryInProgress } from "../../legacy-query-server/run-queries"; -import { LegacyQueryRunner } from "../../legacy-query-server/legacyRunner"; -import { DatabaseItem } from "../../databases"; +} from "../../../src/pure/legacy-messages"; +import * as config from "../../../src/config"; +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 { LegacyQueryRunner } from "../../../src/legacy-query-server/legacyRunner"; +import { DatabaseItem } from "../../../src/databases"; describe("run-queries", () => { let isCanarySpy: jest.SpiedFunction; diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/sarifParser.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/sarifParser.test.ts similarity index 92% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/sarifParser.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/sarifParser.test.ts index 35910b8a8..1c4fa6765 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/sarifParser.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/sarifParser.test.ts @@ -1,6 +1,6 @@ import { join } from "path"; -import { sarifParser } from "../../sarif-parser"; +import { sarifParser } from "../../../src/sarif-parser"; describe("sarif parser", () => { const sarifDir = join(__dirname, "data/sarif"); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/telemetry.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/telemetry.test.ts similarity index 98% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/telemetry.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/telemetry.test.ts index a8b887fa9..7be66b2ee 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/telemetry.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/telemetry.test.ts @@ -8,9 +8,9 @@ import { import { TelemetryListener, telemetryListener as globalTelemetryListener, -} from "../../telemetry"; -import { UserCancellationException } from "../../commandRunner"; -import { ENABLE_TELEMETRY } from "../../config"; +} from "../../../src/telemetry"; +import { UserCancellationException } from "../../../src/commandRunner"; +import { ENABLE_TELEMETRY } from "../../../src/config"; import { createMockExtensionContext } from "./index"; // setting preferences can trigger lots of background activity diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/test-adapter.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/test-adapter.test.ts similarity index 94% rename from extensions/ql-vscode/src/vscode-tests/no-workspace/test-adapter.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/test-adapter.test.ts index 4af6e9892..202f26a45 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/test-adapter.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/test-adapter.test.ts @@ -1,14 +1,24 @@ import * as fs from "fs-extra"; import { Uri, WorkspaceFolder } from "vscode"; -import { QLTestAdapter } from "../../test-adapter"; -import { CodeQLCliServer } from "../../cli"; +import { QLTestAdapter } from "../../../src/test-adapter"; +import { CodeQLCliServer } from "../../../src/cli"; import { DatabaseItem, DatabaseItemImpl, DatabaseManager, FullDatabaseOptions, -} from "../../databases"; +} from "../../../src/databases"; + +jest.mock("fs-extra", () => { + const original = jest.requireActual("fs-extra"); + return { + ...original, + access: jest.fn(), + }; +}); + +const mockedFsExtra = jest.mocked(fs); describe("test-adapter", () => { let adapter: QLTestAdapter; @@ -118,7 +128,7 @@ describe("test-adapter", () => { }); it("should reregister testproj databases around test run", async () => { - jest.spyOn(fs, "access").mockResolvedValue(undefined); + mockedFsExtra.access.mockResolvedValue(undefined); currentDatabaseItem = preTestDatabaseItem; databaseItems = [preTestDatabaseItem]; diff --git a/extensions/ql-vscode/src/vscode-tests/test-config.ts b/extensions/ql-vscode/test/vscode-tests/test-config.ts similarity index 98% rename from extensions/ql-vscode/src/vscode-tests/test-config.ts rename to extensions/ql-vscode/test/vscode-tests/test-config.ts index e91b5799f..29386e7d5 100644 --- a/extensions/ql-vscode/src/vscode-tests/test-config.ts +++ b/extensions/ql-vscode/test/vscode-tests/test-config.ts @@ -1,7 +1,7 @@ import { readFileSync } from "fs-extra"; import { join } from "path"; import { ConfigurationTarget } from "vscode"; -import { ALL_SETTINGS, InspectionResult, Setting } from "../config"; +import { ALL_SETTINGS, InspectionResult, Setting } from "../../src/config"; class TestSetting { private initialSettingState: InspectionResult | undefined; diff --git a/extensions/ql-vscode/src/vscode-tests/test-dispose-handler.ts b/extensions/ql-vscode/test/vscode-tests/test-dispose-handler.ts similarity index 85% rename from extensions/ql-vscode/src/vscode-tests/test-dispose-handler.ts rename to extensions/ql-vscode/test/vscode-tests/test-dispose-handler.ts index b395f3ea0..ff10fa2d4 100644 --- a/extensions/ql-vscode/src/vscode-tests/test-dispose-handler.ts +++ b/extensions/ql-vscode/test/vscode-tests/test-dispose-handler.ts @@ -1,5 +1,5 @@ import { Disposable } from "vscode"; -import { DisposableObject } from "../pure/disposable-object"; +import { DisposableObject } from "../../src/pure/disposable-object"; export function testDisposeHandler(disposable: any & Disposable) { if ( diff --git a/extensions/ql-vscode/src/vscode-tests/utils/bundled-pack-helpers.ts b/extensions/ql-vscode/test/vscode-tests/utils/bundled-pack-helpers.ts similarity index 100% rename from extensions/ql-vscode/src/vscode-tests/utils/bundled-pack-helpers.ts rename to extensions/ql-vscode/test/vscode-tests/utils/bundled-pack-helpers.ts diff --git a/extensions/ql-vscode/src/vscode-tests/utils/query-history-helpers.ts b/extensions/ql-vscode/test/vscode-tests/utils/query-history-helpers.ts similarity index 63% rename from extensions/ql-vscode/src/vscode-tests/utils/query-history-helpers.ts rename to extensions/ql-vscode/test/vscode-tests/utils/query-history-helpers.ts index b55c8d1ba..0f4c8931a 100644 --- a/extensions/ql-vscode/src/vscode-tests/utils/query-history-helpers.ts +++ b/extensions/ql-vscode/test/vscode-tests/utils/query-history-helpers.ts @@ -1,4 +1,4 @@ -import { QueryHistoryInfo } from "../../query-history-info"; +import { QueryHistoryInfo } from "../../../src/query-history-info"; export function shuffleHistoryItems(history: QueryHistoryInfo[]) { return history.sort(() => Math.random() - 0.5); diff --git a/extensions/ql-vscode/tsconfig.json b/extensions/ql-vscode/tsconfig.json index ba94c7757..19759b3dc 100644 --- a/extensions/ql-vscode/tsconfig.json +++ b/extensions/ql-vscode/tsconfig.json @@ -18,7 +18,10 @@ "noImplicitReturns": true, "experimentalDecorators": true, "noUnusedLocals": true, - "noUnusedParameters": true + "noUnusedParameters": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "noEmit": true }, "include": ["src/**/*.ts"], "exclude": ["node_modules", "test", "**/view"] diff --git a/extensions/ql-vscode/workspace-databases-schema.json b/extensions/ql-vscode/workspace-databases-schema.json index 8c95814de..7dc7c5787 100644 --- a/extensions/ql-vscode/workspace-databases-schema.json +++ b/extensions/ql-vscode/workspace-databases-schema.json @@ -7,7 +7,7 @@ "databases": { "type": "object", "properties": { - "remote": { + "variantAnalysis": { "type": "object", "properties": { "repositoryLists": { @@ -125,65 +125,9 @@ "additionalProperties": false } }, - "required": ["remote", "local"], + "required": ["variantAnalysis", "local"], "additionalProperties": false }, - "expanded": { - "type": "array", - "items": { - "type": "object", - "oneOf": [ - { - "properties": { - "kind": { - "type": "string", - "enum": ["rootLocal"] - } - }, - "required": ["kind"], - "additionalProperties": false - }, - { - "properties": { - "kind": { - "type": "string", - "enum": ["localUserDefinedList"] - }, - "listName": { - "type": "string", - "minLength": 1 - } - }, - "required": ["kind", "listName"], - "additionalProperties": false - }, - { - "properties": { - "kind": { - "type": "string", - "enum": ["rootRemote"] - } - }, - "required": ["kind"], - "additionalProperties": false - }, - { - "properties": { - "kind": { - "type": "string", - "enum": ["remoteUserDefinedList"] - }, - "listName": { - "type": "string", - "minLength": 1 - } - }, - "required": ["kind", "listName"], - "additionalProperties": false - } - ] - } - }, "selected": { "type": "object", "oneOf": [ @@ -222,7 +166,7 @@ "properties": { "kind": { "type": "string", - "enum": ["remoteSystemDefinedList"] + "enum": ["variantAnalysisSystemDefinedList"] }, "listName": { "type": "string", @@ -236,7 +180,7 @@ "properties": { "kind": { "type": "string", - "enum": ["remoteUserDefinedList"] + "enum": ["variantAnalysisUserDefinedList"] }, "listName": { "type": "string", @@ -250,7 +194,7 @@ "properties": { "kind": { "type": "string", - "enum": ["remoteOwner"] + "enum": ["variantAnalysisOwner"] }, "ownerName": { "type": "string", @@ -264,7 +208,7 @@ "properties": { "kind": { "type": "string", - "enum": ["remoteRepository"] + "enum": ["variantAnalysisRepository"] }, "repositoryName": { "type": "string", @@ -281,6 +225,6 @@ ] } }, - "required": ["databases", "expanded"], + "required": ["databases"], "additionalProperties": false }