diff --git a/.github/workflows/cli-test.yml b/.github/workflows/cli-test.yml index afb9b01e0..5e2463d00 100644 --- a/.github/workflows/cli-test.yml +++ b/.github/workflows/cli-test.yml @@ -11,6 +11,7 @@ on: - extensions/ql-vscode/src/language-support/** - extensions/ql-vscode/src/query-server/** - extensions/ql-vscode/supported_cli_versions.json + - extensions/ql-vscode/src/variant-analysis/run-remote-query.ts jobs: find-nightly: diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 000000000..26c8837e0 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,46 @@ +name: Run E2E Playwright tests +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + e2e-test: + name: E2E Test + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: extensions/ql-vscode/.nvmrc + cache: 'npm' + cache-dependency-path: extensions/ql-vscode/package-lock.json + + - name: Install dependencies + working-directory: extensions/ql-vscode + run: npm ci + + - name: Start containers + working-directory: extensions/ql-vscode/test/e2e + run: docker-compose -f "docker-compose.yml" up -d --build + + - name: Install Playwright Browsers + working-directory: extensions/ql-vscode + run: npx playwright install --with-deps + - name: Run Playwright tests + working-directory: extensions/ql-vscode/test/e2e + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: extensions/ql-vscode/playwright-report/ + retention-days: 30 + - name: Stop containers + working-directory: extensions/ql-vscode/test/e2e + if: always() + run: docker-compose -f "docker-compose.yml" down -v diff --git a/.gitignore b/.gitignore index 94107c69e..4784b9ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ artifacts/ # CodeQL metadata .cache/ .codeql/ + +# E2E Reports +**/playwright-report/** +**/test-results/** \ No newline at end of file diff --git a/extensions/ql-vscode/.eslintrc.js b/extensions/ql-vscode/.eslintrc.js index 5621731a1..6b28409c4 100644 --- a/extensions/ql-vscode/.eslintrc.js +++ b/extensions/ql-vscode/.eslintrc.js @@ -28,6 +28,7 @@ const baseConfig = { "plugin:@typescript-eslint/recommended", "plugin:import/recommended", "plugin:import/typescript", + "plugin:deprecation/recommended", ], rules: { "@typescript-eslint/await-thenable": "error", diff --git a/extensions/ql-vscode/.storybook/vscode-theme-addon/manager.tsx b/extensions/ql-vscode/.storybook/vscode-theme-addon/manager.tsx index 9ab2d0b6c..8d7881ffb 100644 --- a/extensions/ql-vscode/.storybook/vscode-theme-addon/manager.tsx +++ b/extensions/ql-vscode/.storybook/vscode-theme-addon/manager.tsx @@ -1,5 +1,6 @@ import * as React from "react"; -import { addons, types } from "@storybook/manager-api"; +import { addons } from "@storybook/manager-api"; +import { Addon_TypesEnum } from "@storybook/types"; import { ThemeSelector } from "./ThemeSelector"; const ADDON_ID = "vscode-theme-addon"; @@ -7,7 +8,7 @@ const ADDON_ID = "vscode-theme-addon"; addons.register(ADDON_ID, () => { addons.add(ADDON_ID, { title: "VSCode Themes", - type: types.TOOL, + type: Addon_TypesEnum.TOOL, match: ({ viewMode }) => !!(viewMode && viewMode.match(/^(story|docs)$/)), render: () => , }); diff --git a/extensions/ql-vscode/package-lock.json b/extensions/ql-vscode/package-lock.json index e72683471..170e7469d 100644 --- a/extensions/ql-vscode/package-lock.json +++ b/extensions/ql-vscode/package-lock.json @@ -34,7 +34,7 @@ "source-map": "^0.7.4", "source-map-support": "^0.5.21", "stream-json": "^1.7.3", - "styled-components": "^6.0.2", + "styled-components": "^6.1.8", "tmp": "^0.2.1", "tmp-promise": "^3.0.2", "tree-kill": "^1.2.2", @@ -42,7 +42,7 @@ "vscode-jsonrpc": "^8.0.2", "vscode-languageclient": "^8.0.2", "yauzl": "^2.10.0", - "zip-a-folder": "^3.1.3" + "zip-a-folder": "^3.1.6" }, "devDependencies": { "@babel/core": "^7.18.13", @@ -53,6 +53,7 @@ "@faker-js/faker": "^8.0.2", "@github/markdownlint-github": "^0.6.0", "@octokit/plugin-throttling": "^8.0.0", + "@playwright/test": "^1.40.1", "@storybook/addon-a11y": "^7.6.9", "@storybook/addon-actions": "^7.1.0", "@storybook/addon-essentials": "^7.1.0", @@ -81,7 +82,7 @@ "@types/node": "18.15.*", "@types/node-fetch": "^2.5.2", "@types/react": "^18.0.28", - "@types/react-dom": "^18.0.11", + "@types/react-dom": "^18.2.18", "@types/sarif": "^2.1.2", "@types/semver": "^7.2.0", "@types/stream-json": "^1.7.1", @@ -105,6 +106,7 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-etc": "^2.0.2", "eslint-plugin-github": "^4.4.1", "eslint-plugin-import": "^2.29.1", @@ -124,13 +126,13 @@ "jest-environment-jsdom": "^29.0.3", "jest-runner-vscode": "^3.0.1", "lint-staged": "^15.0.2", - "markdownlint-cli2": "^0.11.0", + "markdownlint-cli2": "^0.12.1", "markdownlint-cli2-formatter-pretty": "^0.0.5", "mini-css-extract-plugin": "^2.6.1", "npm-run-all": "^4.1.5", "patch-package": "^8.0.0", - "prettier": "^3.0.0", - "storybook": "^7.6.7", + "prettier": "^3.2.4", + "storybook": "^7.6.10", "tar-stream": "^3.0.0", "through2": "^4.0.2", "ts-jest": "^29.0.1", @@ -2473,9 +2475,9 @@ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" }, "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", @@ -4442,6 +4444,21 @@ "node": ">= 8" } }, + "node_modules/@playwright/test": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", + "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "dev": true, + "dependencies": { + "playwright": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.11", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", @@ -5671,15 +5688,15 @@ } }, "node_modules/@storybook/builder-manager": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.7.tgz", - "integrity": "sha512-6HYpj6+g/qbDMvImVz/G/aANbkhppyBa1ozfHxLK7tRD79YvozCWmj2Z9umRekPv9VIeMxnI5EEzJXOsoMX5DQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/builder-manager/-/builder-manager-7.6.10.tgz", + "integrity": "sha512-f+YrjZwohGzvfDtH8BHzqM3xW0p4vjjg9u7uzRorqUiNIAAKHpfNrZ/WvwPlPYmrpAHt4xX/nXRJae4rFSygPw==", "dev": true, "dependencies": { "@fal-works/esbuild-plugin-global-externals": "^2.1.2", - "@storybook/core-common": "7.6.7", - "@storybook/manager": "7.6.7", - "@storybook/node-logger": "7.6.7", + "@storybook/core-common": "7.6.10", + "@storybook/manager": "7.6.10", + "@storybook/node-logger": "7.6.10", "@types/ejs": "^3.1.1", "@types/find-cache-dir": "^3.2.1", "@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10", @@ -5699,13 +5716,13 @@ } }, "node_modules/@storybook/builder-manager/node_modules/@storybook/channels": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.7.tgz", - "integrity": "sha512-u1hURhfQHHtZyRIDUENRCp+CRRm7IQfcjQaoWI06XCevQPuhVEtFUfXHjG+J74aA/JuuTLFUtqwNm1zGqbXTAQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.10.tgz", + "integrity": "sha512-ITCLhFuDBKgxetuKnWwYqMUWlU7zsfH3gEKZltTb+9/2OAWR7ez0iqU7H6bXP1ridm0DCKkt2UMWj2mmr9iQqg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.7", - "@storybook/core-events": "7.6.7", + "@storybook/client-logger": "7.6.10", + "@storybook/core-events": "7.6.10", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -5717,9 +5734,9 @@ } }, "node_modules/@storybook/builder-manager/node_modules/@storybook/client-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.7.tgz", - "integrity": "sha512-A16zpWgsa0gSdXMR9P3bWVdC9u/1B1oG4H7Z1+JhNzgnL3CdyOYO0qFSiAtNBso4nOjIAJVb6/AoBzdRhmSVQg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.10.tgz", + "integrity": "sha512-U7bbpu21ntgePMz/mKM18qvCSWCUGCUlYru8mgVlXLCKqFqfTeP887+CsPEQf29aoE3cLgDrxqbRJ1wxX9kL9A==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -5730,14 +5747,14 @@ } }, "node_modules/@storybook/builder-manager/node_modules/@storybook/core-common": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.7.tgz", - "integrity": "sha512-F1fJnauVSPQtAlpicbN/O4XW38Ai8kf/IoU0Hgm9gEwurIk6MF5hiVLsaTI/5GUbrepMl9d9J+iIL4lHAT8IyA==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.10.tgz", + "integrity": "sha512-K3YWqjCKMnpvYsWNjOciwTH6zWbuuZzmOiipziZaVJ+sB1XYmH52Y3WGEm07TZI8AYK9DRgwA13dR/7W0nw72Q==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.7", - "@storybook/node-logger": "7.6.7", - "@storybook/types": "7.6.7", + "@storybook/core-events": "7.6.10", + "@storybook/node-logger": "7.6.10", + "@storybook/types": "7.6.10", "@types/find-cache-dir": "^3.2.1", "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", @@ -5765,9 +5782,9 @@ } }, "node_modules/@storybook/builder-manager/node_modules/@storybook/core-events": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.7.tgz", - "integrity": "sha512-KZ5d03c47pnr5/kY26pJtWq7WpmCPXLbgyjJZDSc+TTY153BdZksvlBXRHtqM1yj2UM6QsSyIuiJaADJNAbP2w==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.10.tgz", + "integrity": "sha512-yccDH67KoROrdZbRKwxgTswFMAco5nlCyxszCDASCLygGSV2Q2e+YuywrhchQl3U6joiWi3Ps1qWu56NeNafag==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -5778,9 +5795,9 @@ } }, "node_modules/@storybook/builder-manager/node_modules/@storybook/node-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.7.tgz", - "integrity": "sha512-XLih8MxylkpZG9+8tgp8sPGc2tldlWF+DpuAkUv6J3Mc81mPyc3cQKQWZ7Hb+m1LpRGqKV4wyOQj1rC+leVMoQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.10.tgz", + "integrity": "sha512-ZBuqrv4bjJzKXyfRGFkVIi+z6ekn6rOPoQao4KmsfLNQAUUsEdR8Baw/zMnnU417zw5dSEaZdpuwx75SCQAeOA==", "dev": true, "funding": { "type": "opencollective", @@ -5788,12 +5805,12 @@ } }, "node_modules/@storybook/builder-manager/node_modules/@storybook/types": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.7.tgz", - "integrity": "sha512-VcGwrI4AkBENxkoAUJ+Z7SyMK73hpoY0TTtw2J7tc05/xdiXhkQTX15Qa12IBWIkoXCyNrtaU+q7KR8Tjzi+uw==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.10.tgz", + "integrity": "sha512-hcS2HloJblaMpCAj2axgGV+53kgSRYPT0a1PG1IHsZaYQILfHSMmBqM8XzXXYTsgf9250kz3dqFX1l0n3EqMlQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.7", + "@storybook/channels": "7.6.10", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -6111,23 +6128,23 @@ } }, "node_modules/@storybook/cli": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.7.tgz", - "integrity": "sha512-DwDWzkifBH17ry+n+d+u52Sv69dZQ+04ETJdDDzghcyAcKnFzrRNukj4tJ21cm+ZAU/r0fKR9d4Qpbogca9fAg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/cli/-/cli-7.6.10.tgz", + "integrity": "sha512-pK1MEseMm73OMO2OVoSz79QWX8ymxgIGM8IeZTCo9gImiVRChMNDFYcv8yPWkjuyesY8c15CoO48aR7pdA1OjQ==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@ndelangen/get-tarball": "^3.0.7", - "@storybook/codemod": "7.6.7", - "@storybook/core-common": "7.6.7", - "@storybook/core-events": "7.6.7", - "@storybook/core-server": "7.6.7", - "@storybook/csf-tools": "7.6.7", - "@storybook/node-logger": "7.6.7", - "@storybook/telemetry": "7.6.7", - "@storybook/types": "7.6.7", + "@storybook/codemod": "7.6.10", + "@storybook/core-common": "7.6.10", + "@storybook/core-events": "7.6.10", + "@storybook/core-server": "7.6.10", + "@storybook/csf-tools": "7.6.10", + "@storybook/node-logger": "7.6.10", + "@storybook/telemetry": "7.6.10", + "@storybook/types": "7.6.10", "@types/semver": "^7.3.4", "@yarnpkg/fslib": "2.10.3", "@yarnpkg/libzip": "2.3.0", @@ -6152,7 +6169,6 @@ "puppeteer-core": "^2.1.1", "read-pkg-up": "^7.0.1", "semver": "^7.3.7", - "simple-update-notifier": "^2.0.0", "strip-json-comments": "^3.0.1", "tempy": "^1.0.1", "ts-dedent": "^2.0.0", @@ -6168,13 +6184,13 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/channels": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.7.tgz", - "integrity": "sha512-u1hURhfQHHtZyRIDUENRCp+CRRm7IQfcjQaoWI06XCevQPuhVEtFUfXHjG+J74aA/JuuTLFUtqwNm1zGqbXTAQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.10.tgz", + "integrity": "sha512-ITCLhFuDBKgxetuKnWwYqMUWlU7zsfH3gEKZltTb+9/2OAWR7ez0iqU7H6bXP1ridm0DCKkt2UMWj2mmr9iQqg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.7", - "@storybook/core-events": "7.6.7", + "@storybook/client-logger": "7.6.10", + "@storybook/core-events": "7.6.10", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -6186,9 +6202,9 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/client-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.7.tgz", - "integrity": "sha512-A16zpWgsa0gSdXMR9P3bWVdC9u/1B1oG4H7Z1+JhNzgnL3CdyOYO0qFSiAtNBso4nOjIAJVb6/AoBzdRhmSVQg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.10.tgz", + "integrity": "sha512-U7bbpu21ntgePMz/mKM18qvCSWCUGCUlYru8mgVlXLCKqFqfTeP887+CsPEQf29aoE3cLgDrxqbRJ1wxX9kL9A==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -6199,14 +6215,14 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/core-common": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.7.tgz", - "integrity": "sha512-F1fJnauVSPQtAlpicbN/O4XW38Ai8kf/IoU0Hgm9gEwurIk6MF5hiVLsaTI/5GUbrepMl9d9J+iIL4lHAT8IyA==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.10.tgz", + "integrity": "sha512-K3YWqjCKMnpvYsWNjOciwTH6zWbuuZzmOiipziZaVJ+sB1XYmH52Y3WGEm07TZI8AYK9DRgwA13dR/7W0nw72Q==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.7", - "@storybook/node-logger": "7.6.7", - "@storybook/types": "7.6.7", + "@storybook/core-events": "7.6.10", + "@storybook/node-logger": "7.6.10", + "@storybook/types": "7.6.10", "@types/find-cache-dir": "^3.2.1", "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", @@ -6234,9 +6250,9 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/core-events": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.7.tgz", - "integrity": "sha512-KZ5d03c47pnr5/kY26pJtWq7WpmCPXLbgyjJZDSc+TTY153BdZksvlBXRHtqM1yj2UM6QsSyIuiJaADJNAbP2w==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.10.tgz", + "integrity": "sha512-yccDH67KoROrdZbRKwxgTswFMAco5nlCyxszCDASCLygGSV2Q2e+YuywrhchQl3U6joiWi3Ps1qWu56NeNafag==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -6247,9 +6263,9 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/csf-tools": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.7.tgz", - "integrity": "sha512-hyRbUGa2Uxvz3U09BjcOfMNf/5IYgRum1L6XszqK2O8tK9DGte1r6hArCIAcqiEmFMC40d0kalPzqu6WMNn7sg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.10.tgz", + "integrity": "sha512-TnDNAwIALcN6SA4l00Cb67G02XMOrYU38bIpFJk5VMDX2dvgPjUtJNBuLmEbybGcOt7nPyyFIHzKcY5FCVGoWA==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -6257,7 +6273,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.7", + "@storybook/types": "7.6.10", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -6268,9 +6284,9 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/node-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.7.tgz", - "integrity": "sha512-XLih8MxylkpZG9+8tgp8sPGc2tldlWF+DpuAkUv6J3Mc81mPyc3cQKQWZ7Hb+m1LpRGqKV4wyOQj1rC+leVMoQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.10.tgz", + "integrity": "sha512-ZBuqrv4bjJzKXyfRGFkVIi+z6ekn6rOPoQao4KmsfLNQAUUsEdR8Baw/zMnnU417zw5dSEaZdpuwx75SCQAeOA==", "dev": true, "funding": { "type": "opencollective", @@ -6278,12 +6294,12 @@ } }, "node_modules/@storybook/cli/node_modules/@storybook/types": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.7.tgz", - "integrity": "sha512-VcGwrI4AkBENxkoAUJ+Z7SyMK73hpoY0TTtw2J7tc05/xdiXhkQTX15Qa12IBWIkoXCyNrtaU+q7KR8Tjzi+uw==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.10.tgz", + "integrity": "sha512-hcS2HloJblaMpCAj2axgGV+53kgSRYPT0a1PG1IHsZaYQILfHSMmBqM8XzXXYTsgf9250kz3dqFX1l0n3EqMlQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.7", + "@storybook/channels": "7.6.10", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -6403,18 +6419,18 @@ } }, "node_modules/@storybook/codemod": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.7.tgz", - "integrity": "sha512-an2pD5OHqO7CE8Wb7JxjrDnpQgeoxB22MyOs8PPJ9Rvclhpjg+Ku9RogoObYm//zR4g406l7Ec8mTltUkVCEOA==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/codemod/-/codemod-7.6.10.tgz", + "integrity": "sha512-pzFR0nocBb94vN9QCJLC3C3dP734ZigqyPmd0ZCDj9Xce2ytfHK3v1lKB6TZWzKAZT8zztauECYxrbo4LVuagw==", "dev": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.7", - "@storybook/node-logger": "7.6.7", - "@storybook/types": "7.6.7", + "@storybook/csf-tools": "7.6.10", + "@storybook/node-logger": "7.6.10", + "@storybook/types": "7.6.10", "@types/cross-spawn": "^6.0.2", "cross-spawn": "^7.0.3", "globby": "^11.0.2", @@ -6429,13 +6445,13 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/channels": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.7.tgz", - "integrity": "sha512-u1hURhfQHHtZyRIDUENRCp+CRRm7IQfcjQaoWI06XCevQPuhVEtFUfXHjG+J74aA/JuuTLFUtqwNm1zGqbXTAQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.10.tgz", + "integrity": "sha512-ITCLhFuDBKgxetuKnWwYqMUWlU7zsfH3gEKZltTb+9/2OAWR7ez0iqU7H6bXP1ridm0DCKkt2UMWj2mmr9iQqg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.7", - "@storybook/core-events": "7.6.7", + "@storybook/client-logger": "7.6.10", + "@storybook/core-events": "7.6.10", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -6447,9 +6463,9 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/client-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.7.tgz", - "integrity": "sha512-A16zpWgsa0gSdXMR9P3bWVdC9u/1B1oG4H7Z1+JhNzgnL3CdyOYO0qFSiAtNBso4nOjIAJVb6/AoBzdRhmSVQg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.10.tgz", + "integrity": "sha512-U7bbpu21ntgePMz/mKM18qvCSWCUGCUlYru8mgVlXLCKqFqfTeP887+CsPEQf29aoE3cLgDrxqbRJ1wxX9kL9A==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -6460,9 +6476,9 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/core-events": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.7.tgz", - "integrity": "sha512-KZ5d03c47pnr5/kY26pJtWq7WpmCPXLbgyjJZDSc+TTY153BdZksvlBXRHtqM1yj2UM6QsSyIuiJaADJNAbP2w==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.10.tgz", + "integrity": "sha512-yccDH67KoROrdZbRKwxgTswFMAco5nlCyxszCDASCLygGSV2Q2e+YuywrhchQl3U6joiWi3Ps1qWu56NeNafag==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -6473,9 +6489,9 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/csf-tools": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.7.tgz", - "integrity": "sha512-hyRbUGa2Uxvz3U09BjcOfMNf/5IYgRum1L6XszqK2O8tK9DGte1r6hArCIAcqiEmFMC40d0kalPzqu6WMNn7sg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.10.tgz", + "integrity": "sha512-TnDNAwIALcN6SA4l00Cb67G02XMOrYU38bIpFJk5VMDX2dvgPjUtJNBuLmEbybGcOt7nPyyFIHzKcY5FCVGoWA==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -6483,7 +6499,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.7", + "@storybook/types": "7.6.10", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -6494,9 +6510,9 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/node-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.7.tgz", - "integrity": "sha512-XLih8MxylkpZG9+8tgp8sPGc2tldlWF+DpuAkUv6J3Mc81mPyc3cQKQWZ7Hb+m1LpRGqKV4wyOQj1rC+leVMoQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.10.tgz", + "integrity": "sha512-ZBuqrv4bjJzKXyfRGFkVIi+z6ekn6rOPoQao4KmsfLNQAUUsEdR8Baw/zMnnU417zw5dSEaZdpuwx75SCQAeOA==", "dev": true, "funding": { "type": "opencollective", @@ -6504,12 +6520,12 @@ } }, "node_modules/@storybook/codemod/node_modules/@storybook/types": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.7.tgz", - "integrity": "sha512-VcGwrI4AkBENxkoAUJ+Z7SyMK73hpoY0TTtw2J7tc05/xdiXhkQTX15Qa12IBWIkoXCyNrtaU+q7KR8Tjzi+uw==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.10.tgz", + "integrity": "sha512-hcS2HloJblaMpCAj2axgGV+53kgSRYPT0a1PG1IHsZaYQILfHSMmBqM8XzXXYTsgf9250kz3dqFX1l0n3EqMlQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.7", + "@storybook/channels": "7.6.10", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -6870,26 +6886,26 @@ } }, "node_modules/@storybook/core-server": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.7.tgz", - "integrity": "sha512-elKRv/DNahNNkGcQY/FdOBrLPmZF0T0fwmAmbc4qqeAisjl+to9TO77zdo2ieaEHKyRwE3B3dOB4EXomdF4N/g==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-server/-/core-server-7.6.10.tgz", + "integrity": "sha512-2icnqJkn3vwq0eJPP0rNaHd7IOvxYf5q4lSVl2AWTxo/Ae19KhokI6j/2vvS2XQJMGQszwshlIwrZUNsj5p0yw==", "dev": true, "dependencies": { "@aw-web-design/x-default-browser": "1.4.126", "@discoveryjs/json-ext": "^0.5.3", - "@storybook/builder-manager": "7.6.7", - "@storybook/channels": "7.6.7", - "@storybook/core-common": "7.6.7", - "@storybook/core-events": "7.6.7", + "@storybook/builder-manager": "7.6.10", + "@storybook/channels": "7.6.10", + "@storybook/core-common": "7.6.10", + "@storybook/core-events": "7.6.10", "@storybook/csf": "^0.1.2", - "@storybook/csf-tools": "7.6.7", + "@storybook/csf-tools": "7.6.10", "@storybook/docs-mdx": "^0.1.0", "@storybook/global": "^5.0.0", - "@storybook/manager": "7.6.7", - "@storybook/node-logger": "7.6.7", - "@storybook/preview-api": "7.6.7", - "@storybook/telemetry": "7.6.7", - "@storybook/types": "7.6.7", + "@storybook/manager": "7.6.10", + "@storybook/node-logger": "7.6.10", + "@storybook/preview-api": "7.6.10", + "@storybook/telemetry": "7.6.10", + "@storybook/types": "7.6.10", "@types/detect-port": "^1.3.0", "@types/node": "^18.0.0", "@types/pretty-hrtime": "^1.0.0", @@ -6923,13 +6939,13 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/channels": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.7.tgz", - "integrity": "sha512-u1hURhfQHHtZyRIDUENRCp+CRRm7IQfcjQaoWI06XCevQPuhVEtFUfXHjG+J74aA/JuuTLFUtqwNm1zGqbXTAQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.10.tgz", + "integrity": "sha512-ITCLhFuDBKgxetuKnWwYqMUWlU7zsfH3gEKZltTb+9/2OAWR7ez0iqU7H6bXP1ridm0DCKkt2UMWj2mmr9iQqg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.7", - "@storybook/core-events": "7.6.7", + "@storybook/client-logger": "7.6.10", + "@storybook/core-events": "7.6.10", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -6941,9 +6957,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/client-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.7.tgz", - "integrity": "sha512-A16zpWgsa0gSdXMR9P3bWVdC9u/1B1oG4H7Z1+JhNzgnL3CdyOYO0qFSiAtNBso4nOjIAJVb6/AoBzdRhmSVQg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.10.tgz", + "integrity": "sha512-U7bbpu21ntgePMz/mKM18qvCSWCUGCUlYru8mgVlXLCKqFqfTeP887+CsPEQf29aoE3cLgDrxqbRJ1wxX9kL9A==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -6954,14 +6970,14 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/core-common": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.7.tgz", - "integrity": "sha512-F1fJnauVSPQtAlpicbN/O4XW38Ai8kf/IoU0Hgm9gEwurIk6MF5hiVLsaTI/5GUbrepMl9d9J+iIL4lHAT8IyA==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.10.tgz", + "integrity": "sha512-K3YWqjCKMnpvYsWNjOciwTH6zWbuuZzmOiipziZaVJ+sB1XYmH52Y3WGEm07TZI8AYK9DRgwA13dR/7W0nw72Q==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.7", - "@storybook/node-logger": "7.6.7", - "@storybook/types": "7.6.7", + "@storybook/core-events": "7.6.10", + "@storybook/node-logger": "7.6.10", + "@storybook/types": "7.6.10", "@types/find-cache-dir": "^3.2.1", "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", @@ -6989,9 +7005,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/core-events": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.7.tgz", - "integrity": "sha512-KZ5d03c47pnr5/kY26pJtWq7WpmCPXLbgyjJZDSc+TTY153BdZksvlBXRHtqM1yj2UM6QsSyIuiJaADJNAbP2w==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.10.tgz", + "integrity": "sha512-yccDH67KoROrdZbRKwxgTswFMAco5nlCyxszCDASCLygGSV2Q2e+YuywrhchQl3U6joiWi3Ps1qWu56NeNafag==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -7002,9 +7018,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/csf-tools": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.7.tgz", - "integrity": "sha512-hyRbUGa2Uxvz3U09BjcOfMNf/5IYgRum1L6XszqK2O8tK9DGte1r6hArCIAcqiEmFMC40d0kalPzqu6WMNn7sg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.10.tgz", + "integrity": "sha512-TnDNAwIALcN6SA4l00Cb67G02XMOrYU38bIpFJk5VMDX2dvgPjUtJNBuLmEbybGcOt7nPyyFIHzKcY5FCVGoWA==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -7012,7 +7028,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.7", + "@storybook/types": "7.6.10", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -7023,9 +7039,9 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/node-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.7.tgz", - "integrity": "sha512-XLih8MxylkpZG9+8tgp8sPGc2tldlWF+DpuAkUv6J3Mc81mPyc3cQKQWZ7Hb+m1LpRGqKV4wyOQj1rC+leVMoQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.10.tgz", + "integrity": "sha512-ZBuqrv4bjJzKXyfRGFkVIi+z6ekn6rOPoQao4KmsfLNQAUUsEdR8Baw/zMnnU417zw5dSEaZdpuwx75SCQAeOA==", "dev": true, "funding": { "type": "opencollective", @@ -7033,17 +7049,17 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/preview-api": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.7.tgz", - "integrity": "sha512-ja85ItrT6q2TeBQ6n0CNoRi1R6L8yF2kkis9hVeTQHpwLdZyHUTRqqR5WmhtLqqQXcofyasBPOeJV06wuOhgRQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-7.6.10.tgz", + "integrity": "sha512-5A3etoIwZCx05yuv3KSTv1wynN4SR4rrzaIs/CTBp3BC4q1RBL+Or/tClk0IJPXQMlx/4Y134GtNIBbkiDofpw==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.7", - "@storybook/client-logger": "7.6.7", - "@storybook/core-events": "7.6.7", + "@storybook/channels": "7.6.10", + "@storybook/client-logger": "7.6.10", + "@storybook/core-events": "7.6.10", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/types": "7.6.7", + "@storybook/types": "7.6.10", "@types/qs": "^6.9.5", "dequal": "^2.0.2", "lodash": "^4.17.21", @@ -7059,12 +7075,12 @@ } }, "node_modules/@storybook/core-server/node_modules/@storybook/types": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.7.tgz", - "integrity": "sha512-VcGwrI4AkBENxkoAUJ+Z7SyMK73hpoY0TTtw2J7tc05/xdiXhkQTX15Qa12IBWIkoXCyNrtaU+q7KR8Tjzi+uw==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.10.tgz", + "integrity": "sha512-hcS2HloJblaMpCAj2axgGV+53kgSRYPT0a1PG1IHsZaYQILfHSMmBqM8XzXXYTsgf9250kz3dqFX1l0n3EqMlQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.7", + "@storybook/channels": "7.6.10", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -7402,9 +7418,9 @@ "dev": true }, "node_modules/@storybook/manager": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.7.tgz", - "integrity": "sha512-ZCrkB2zEXogzdOcVzD242ZVm4tlHqrayotnI6iOn9uiun0Pgny0m2d7s9Zge6K2dTOO1vZiOHuA/Mr6nnIDjsA==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/manager/-/manager-7.6.10.tgz", + "integrity": "sha512-Co3sLCbNYY6O4iH2ggmRDLCPWLj03JE5s/DOG8OVoXc6vBwTc/Qgiyrsxxp6BHQnPpM0mxL6aKAxE3UjsW/Nog==", "dev": true, "funding": { "type": "opencollective", @@ -8183,14 +8199,14 @@ } }, "node_modules/@storybook/telemetry": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.7.tgz", - "integrity": "sha512-NHGzC/LGLXpK4AFbVj8ln5ab86ZiiNFvORQMn3+LNGwUt3ZdsHBzExN+WPZdw7OPtfk4ubUY89FXH2GedhTALw==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.10.tgz", + "integrity": "sha512-p3mOSUtIyy2tF1z6pQXxNh1JzYFcAm97nUgkwLzF07GfEdVAPM+ftRSLFbD93zVvLEkmLTlsTiiKaDvOY/lQWg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.7", - "@storybook/core-common": "7.6.7", - "@storybook/csf-tools": "7.6.7", + "@storybook/client-logger": "7.6.10", + "@storybook/core-common": "7.6.10", + "@storybook/csf-tools": "7.6.10", "chalk": "^4.1.0", "detect-package-manager": "^2.0.1", "fetch-retry": "^5.0.2", @@ -8203,13 +8219,13 @@ } }, "node_modules/@storybook/telemetry/node_modules/@storybook/channels": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.7.tgz", - "integrity": "sha512-u1hURhfQHHtZyRIDUENRCp+CRRm7IQfcjQaoWI06XCevQPuhVEtFUfXHjG+J74aA/JuuTLFUtqwNm1zGqbXTAQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.10.tgz", + "integrity": "sha512-ITCLhFuDBKgxetuKnWwYqMUWlU7zsfH3gEKZltTb+9/2OAWR7ez0iqU7H6bXP1ridm0DCKkt2UMWj2mmr9iQqg==", "dev": true, "dependencies": { - "@storybook/client-logger": "7.6.7", - "@storybook/core-events": "7.6.7", + "@storybook/client-logger": "7.6.10", + "@storybook/core-events": "7.6.10", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -8221,9 +8237,9 @@ } }, "node_modules/@storybook/telemetry/node_modules/@storybook/client-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.7.tgz", - "integrity": "sha512-A16zpWgsa0gSdXMR9P3bWVdC9u/1B1oG4H7Z1+JhNzgnL3CdyOYO0qFSiAtNBso4nOjIAJVb6/AoBzdRhmSVQg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.10.tgz", + "integrity": "sha512-U7bbpu21ntgePMz/mKM18qvCSWCUGCUlYru8mgVlXLCKqFqfTeP887+CsPEQf29aoE3cLgDrxqbRJ1wxX9kL9A==", "dev": true, "dependencies": { "@storybook/global": "^5.0.0" @@ -8234,14 +8250,14 @@ } }, "node_modules/@storybook/telemetry/node_modules/@storybook/core-common": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.7.tgz", - "integrity": "sha512-F1fJnauVSPQtAlpicbN/O4XW38Ai8kf/IoU0Hgm9gEwurIk6MF5hiVLsaTI/5GUbrepMl9d9J+iIL4lHAT8IyA==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-common/-/core-common-7.6.10.tgz", + "integrity": "sha512-K3YWqjCKMnpvYsWNjOciwTH6zWbuuZzmOiipziZaVJ+sB1XYmH52Y3WGEm07TZI8AYK9DRgwA13dR/7W0nw72Q==", "dev": true, "dependencies": { - "@storybook/core-events": "7.6.7", - "@storybook/node-logger": "7.6.7", - "@storybook/types": "7.6.7", + "@storybook/core-events": "7.6.10", + "@storybook/node-logger": "7.6.10", + "@storybook/types": "7.6.10", "@types/find-cache-dir": "^3.2.1", "@types/node": "^18.0.0", "@types/node-fetch": "^2.6.4", @@ -8269,9 +8285,9 @@ } }, "node_modules/@storybook/telemetry/node_modules/@storybook/core-events": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.7.tgz", - "integrity": "sha512-KZ5d03c47pnr5/kY26pJtWq7WpmCPXLbgyjJZDSc+TTY153BdZksvlBXRHtqM1yj2UM6QsSyIuiJaADJNAbP2w==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.10.tgz", + "integrity": "sha512-yccDH67KoROrdZbRKwxgTswFMAco5nlCyxszCDASCLygGSV2Q2e+YuywrhchQl3U6joiWi3Ps1qWu56NeNafag==", "dev": true, "dependencies": { "ts-dedent": "^2.0.0" @@ -8282,9 +8298,9 @@ } }, "node_modules/@storybook/telemetry/node_modules/@storybook/csf-tools": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.7.tgz", - "integrity": "sha512-hyRbUGa2Uxvz3U09BjcOfMNf/5IYgRum1L6XszqK2O8tK9DGte1r6hArCIAcqiEmFMC40d0kalPzqu6WMNn7sg==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/csf-tools/-/csf-tools-7.6.10.tgz", + "integrity": "sha512-TnDNAwIALcN6SA4l00Cb67G02XMOrYU38bIpFJk5VMDX2dvgPjUtJNBuLmEbybGcOt7nPyyFIHzKcY5FCVGoWA==", "dev": true, "dependencies": { "@babel/generator": "^7.23.0", @@ -8292,7 +8308,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", "@storybook/csf": "^0.1.2", - "@storybook/types": "7.6.7", + "@storybook/types": "7.6.10", "fs-extra": "^11.1.0", "recast": "^0.23.1", "ts-dedent": "^2.0.0" @@ -8303,9 +8319,9 @@ } }, "node_modules/@storybook/telemetry/node_modules/@storybook/node-logger": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.7.tgz", - "integrity": "sha512-XLih8MxylkpZG9+8tgp8sPGc2tldlWF+DpuAkUv6J3Mc81mPyc3cQKQWZ7Hb+m1LpRGqKV4wyOQj1rC+leVMoQ==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-7.6.10.tgz", + "integrity": "sha512-ZBuqrv4bjJzKXyfRGFkVIi+z6ekn6rOPoQao4KmsfLNQAUUsEdR8Baw/zMnnU417zw5dSEaZdpuwx75SCQAeOA==", "dev": true, "funding": { "type": "opencollective", @@ -8313,12 +8329,12 @@ } }, "node_modules/@storybook/telemetry/node_modules/@storybook/types": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.7.tgz", - "integrity": "sha512-VcGwrI4AkBENxkoAUJ+Z7SyMK73hpoY0TTtw2J7tc05/xdiXhkQTX15Qa12IBWIkoXCyNrtaU+q7KR8Tjzi+uw==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.10.tgz", + "integrity": "sha512-hcS2HloJblaMpCAj2axgGV+53kgSRYPT0a1PG1IHsZaYQILfHSMmBqM8XzXXYTsgf9250kz3dqFX1l0n3EqMlQ==", "dev": true, "dependencies": { - "@storybook/channels": "7.6.7", + "@storybook/channels": "7.6.10", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -9658,9 +9674,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.17", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", - "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", "dev": true, "dependencies": { "@types/react": "*" @@ -9768,9 +9784,9 @@ } }, "node_modules/@types/stylis": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.4.tgz", - "integrity": "sha512-36ZrGJ8fgtBr6nwNnuJ9jXIj+bn/pF6UoqmrQT7+Y99+tFFeHHsoR54+194dHdyhPjgbeoNz3Qru0oRt0l6ASQ==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" }, "node_modules/@types/tar-stream": { "version": "3.1.3", @@ -13849,7 +13865,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true }, "node_modules/d": { "version": "1.0.1", @@ -14676,9 +14693,9 @@ } }, "node_modules/defu": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.3.tgz", - "integrity": "sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "dev": true }, "node_modules/del": { @@ -15648,6 +15665,21 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-plugin-deprecation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-deprecation/-/eslint-plugin-deprecation-2.0.0.tgz", + "integrity": "sha512-OAm9Ohzbj11/ZFyICyR5N6LbOIvQMp7ZU2zI7Ej0jIc8kiGUERXPNMfw2QqqHD1ZHtjMub3yPZILovYEYucgoQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^6.0.0", + "tslib": "^2.3.1", + "tsutils": "^3.21.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "^4.2.4 || ^5.0.0" + } + }, "node_modules/eslint-plugin-escompat": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/eslint-plugin-escompat/-/eslint-plugin-escompat-3.4.0.tgz", @@ -17447,9 +17479,9 @@ "dev": true }, "node_modules/flow-parser": { - "version": "0.225.1", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.225.1.tgz", - "integrity": "sha512-50fjR6zbLQcpq5IFNkheUSY/AFPxVeeLiBM5B3NQBSKId2G0cUuExOlDDOguxc49dl9lnh8hI1xcYlPJWNp4KQ==", + "version": "0.227.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.227.0.tgz", + "integrity": "sha512-nOygtGKcX/siZK/lFzpfdHEfOkfGcTW7rNroR1Zsz6T/JxSahPALXVt5qVHq/fgvMJuv096BTKbgxN3PzVBaDA==", "dev": true, "engines": { "node": ">=0.4.0" @@ -24377,13 +24409,13 @@ } }, "node_modules/markdownlint": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.32.1.tgz", - "integrity": "sha512-3sx9xpi4xlHlokGyHO9k0g3gJbNY4DI6oNEeEYq5gQ4W7UkiJ90VDAnuDl2U+yyXOUa6BX+0gf69ZlTUGIBp6A==", + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.33.0.tgz", + "integrity": "sha512-4lbtT14A3m0LPX1WS/3d1m7Blg+ZwiLq36WvjQqFGsX3Gik99NV+VXp/PW3n+Q62xyPdbvGOCfjPqjW+/SKMig==", "dev": true, "dependencies": { - "markdown-it": "13.0.2", - "markdownlint-micromark": "0.1.7" + "markdown-it": "14.0.0", + "markdownlint-micromark": "0.1.8" }, "engines": { "node": ">=18" @@ -24393,22 +24425,20 @@ } }, "node_modules/markdownlint-cli2": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.11.0.tgz", - "integrity": "sha512-RmFpr+My5in8KT+H/A6ozKIVYVzZtL5t9c8DYdv0YJdljl385z44CcCVBrclpHxCGMY2tr0hZ/ca+meGGvgdnQ==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.12.1.tgz", + "integrity": "sha512-RcK+l5FjJEyrU3REhrThiEUXNK89dLYNJCYbvOUKypxqIGfkcgpz8g08EKqhrmUbYfYoLC5nEYQy53NhJSEtfQ==", "dev": true, "dependencies": { "globby": "14.0.0", - "markdownlint": "0.32.1", + "jsonc-parser": "3.2.0", + "markdownlint": "0.33.0", "markdownlint-cli2-formatter-default": "0.0.4", "micromatch": "4.0.5", - "strip-json-comments": "5.0.1", "yaml": "2.3.4" }, "bin": { - "markdownlint-cli2": "markdownlint-cli2.js", - "markdownlint-cli2-config": "markdownlint-cli2-config.js", - "markdownlint-cli2-fix": "markdownlint-cli2-fix.js" + "markdownlint-cli2": "markdownlint-cli2.js" }, "engines": { "node": ">=18" @@ -24498,64 +24528,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/markdownlint-cli2/node_modules/strip-json-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", - "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/markdownlint-micromark": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.7.tgz", - "integrity": "sha512-BbRPTC72fl5vlSKv37v/xIENSRDYL/7X/XoFzZ740FGEbs9vZerLrIkFRY0rv7slQKxDczToYuMmqQFN61fi4Q==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.8.tgz", + "integrity": "sha512-1ouYkMRo9/6gou9gObuMDnvZM8jC/ly3QCFQyoSPCS2XV1ZClU0xpKbL1Ar3bWWRT1RnBZkWUEiNKrI2CwiBQA==", "dev": true, "engines": { "node": ">=16" - } - }, - "node_modules/markdownlint/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "dev": true, - "engines": { - "node": ">=0.12" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/DavidAnson" } }, "node_modules/markdownlint/node_modules/linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/markdownlint/node_modules/markdown-it": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", - "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.0.0.tgz", + "integrity": "sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==", "dev": true, "dependencies": { "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.0.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/markdownlint/node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, + "node_modules/markdownlint/node_modules/uc.micro": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.0.0.tgz", + "integrity": "sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==", + "dev": true + }, "node_modules/matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", @@ -25563,14 +25585,14 @@ "dev": true }, "node_modules/nypm": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.4.tgz", - "integrity": "sha512-1JLkp/zHBrkS3pZ692IqOaIKSYHmQXgqfELk6YTOfVBnwealAmPA1q2kKK7PHJAHSMBozerThEFZXP3G6o7Ukg==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.6.tgz", + "integrity": "sha512-2CATJh3pd6CyNfU5VZM7qSwFu0ieyabkEdnogE30Obn1czrmOYiZ8DOZLe1yBdLKWoyD3Mcy2maUs+0MR3yVjQ==", "dev": true, "dependencies": { "citty": "^0.1.5", "execa": "^8.0.1", - "pathe": "^1.1.1", + "pathe": "^1.1.2", "ufo": "^1.3.2" }, "bin": { @@ -26712,9 +26734,9 @@ } }, "node_modules/pathe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", - "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "dev": true }, "node_modules/peek-stream": { @@ -26822,6 +26844,50 @@ "node": ">=10" } }, + "node_modules/playwright": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", + "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "dev": true, + "dependencies": { + "playwright-core": "1.40.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plugin-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", @@ -26871,6 +26937,7 @@ "version": "8.4.32", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -26975,6 +27042,7 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, "funding": [ { "type": "github", @@ -27025,9 +27093,9 @@ } }, "node_modules/prettier": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", - "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -27223,6 +27291,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/puppeteer-core": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-2.1.1.tgz", @@ -29019,18 +29096,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -29442,12 +29507,12 @@ "dev": true }, "node_modules/storybook": { - "version": "7.6.7", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.7.tgz", - "integrity": "sha512-1Cd895dqYIT5MOUOCDlD73OTWoJubLq/sWC7AMzkMrLu76yD4Cu6f+wv1HDrRAheRaCaeT3yhYEhsMB6qHIcaA==", + "version": "7.6.10", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-7.6.10.tgz", + "integrity": "sha512-ypFeGhQTUBBfqSUVZYh7wS5ghn3O2wILCiQc4459SeUpvUn+skcqw/TlrwGSoF5EWjDA7gtRrWDxO3mnlPt5Cw==", "dev": true, "dependencies": { - "@storybook/cli": "7.6.7" + "@storybook/cli": "7.6.10" }, "bin": { "sb": "index.js", @@ -29767,19 +29832,19 @@ } }, "node_modules/styled-components": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.1.tgz", - "integrity": "sha512-cpZZP5RrKRIClBW5Eby4JM1wElLVP4NQrJbJ0h10TidTyJf4SIIwa3zLXOoPb4gJi8MsJ8mjq5mu2IrEhZIAcQ==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", "dependencies": { - "@emotion/is-prop-valid": "^1.2.1", - "@emotion/unitless": "^0.8.0", - "@types/stylis": "^4.0.2", - "css-to-react-native": "^3.2.0", - "csstype": "^3.1.2", - "postcss": "^8.4.31", - "shallowequal": "^1.1.0", - "stylis": "^4.3.0", - "tslib": "^2.5.0" + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" }, "engines": { "node": ">= 16" @@ -29793,10 +29858,64 @@ "react-dom": ">= 16.8.0" } }, + "node_modules/styled-components/node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/styled-components/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/stylis": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", - "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" }, "node_modules/supports-color": { "version": "5.5.0", @@ -32701,9 +32820,9 @@ } }, "node_modules/zip-a-folder": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/zip-a-folder/-/zip-a-folder-3.1.5.tgz", - "integrity": "sha512-w7ZOjJS17MYqdjVEFiqa537H/hxVGcwxnmCcmOaUXDoezttVrWkbSob7nit4lqUqha+Q0pOFTCVsBttBx6hs5A==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/zip-a-folder/-/zip-a-folder-3.1.6.tgz", + "integrity": "sha512-u+qRL0sSsq2T2mDFcDzdgCSVO8sChsJ4swd2aZlzyoIoNwyVL7HBHPKDQVq1t0utd8nPXzCq44gAKzXAhbvpXA==", "dependencies": { "archiver": "^6.0.1", "glob": "^10.3.10", diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index 3df7cb0e2..73fa13147 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -1952,7 +1952,7 @@ "source-map": "^0.7.4", "source-map-support": "^0.5.21", "stream-json": "^1.7.3", - "styled-components": "^6.0.2", + "styled-components": "^6.1.8", "tmp": "^0.2.1", "tmp-promise": "^3.0.2", "tree-kill": "^1.2.2", @@ -1960,7 +1960,7 @@ "vscode-jsonrpc": "^8.0.2", "vscode-languageclient": "^8.0.2", "yauzl": "^2.10.0", - "zip-a-folder": "^3.1.3" + "zip-a-folder": "^3.1.6" }, "devDependencies": { "@babel/core": "^7.18.13", @@ -1971,6 +1971,7 @@ "@faker-js/faker": "^8.0.2", "@github/markdownlint-github": "^0.6.0", "@octokit/plugin-throttling": "^8.0.0", + "@playwright/test": "^1.40.1", "@storybook/addon-a11y": "^7.6.9", "@storybook/addon-actions": "^7.1.0", "@storybook/addon-essentials": "^7.1.0", @@ -1999,7 +2000,7 @@ "@types/node": "18.15.*", "@types/node-fetch": "^2.5.2", "@types/react": "^18.0.28", - "@types/react-dom": "^18.0.11", + "@types/react-dom": "^18.2.18", "@types/sarif": "^2.1.2", "@types/semver": "^7.2.0", "@types/stream-json": "^1.7.1", @@ -2023,6 +2024,7 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-etc": "^2.0.2", "eslint-plugin-github": "^4.4.1", "eslint-plugin-import": "^2.29.1", @@ -2042,13 +2044,13 @@ "jest-environment-jsdom": "^29.0.3", "jest-runner-vscode": "^3.0.1", "lint-staged": "^15.0.2", - "markdownlint-cli2": "^0.11.0", + "markdownlint-cli2": "^0.12.1", "markdownlint-cli2-formatter-pretty": "^0.0.5", "mini-css-extract-plugin": "^2.6.1", "npm-run-all": "^4.1.5", "patch-package": "^8.0.0", - "prettier": "^3.0.0", - "storybook": "^7.6.7", + "prettier": "^3.2.4", + "storybook": "^7.6.10", "tar-stream": "^3.0.0", "through2": "^4.0.2", "ts-jest": "^29.0.1", diff --git a/extensions/ql-vscode/scripts/find-deadcode.ts b/extensions/ql-vscode/scripts/find-deadcode.ts index 12e3fb03e..9e21286b4 100644 --- a/extensions/ql-vscode/scripts/find-deadcode.ts +++ b/extensions/ql-vscode/scripts/find-deadcode.ts @@ -14,7 +14,8 @@ function ignoreFile(file: string): boolean { ) || basename(file) === "jest.config.ts" || basename(file) === "index.tsx" || - basename(file) === "index.ts" + basename(file) === "index.ts" || + basename(file) === "playwright.config.ts" ); } diff --git a/extensions/ql-vscode/scripts/util/vscode-versions.ts b/extensions/ql-vscode/scripts/util/vscode-versions.ts index d18c247f6..ce7853620 100644 --- a/extensions/ql-vscode/scripts/util/vscode-versions.ts +++ b/extensions/ql-vscode/scripts/util/vscode-versions.ts @@ -45,8 +45,9 @@ export async function getVersionInformation( vscodeVersion: string, ): Promise { const vsCodePackageJson = await getVsCodePackageJson(vscodeVersion); - const electronVersion = minVersion(vsCodePackageJson.devDependencies.electron) - ?.version; + const electronVersion = minVersion( + vsCodePackageJson.devDependencies.electron, + )?.version; if (!electronVersion) { throw new Error("Could not find Electron version"); } diff --git a/extensions/ql-vscode/src/codeql-cli/cli.ts b/extensions/ql-vscode/src/codeql-cli/cli.ts index 064a013fe..9d963d820 100644 --- a/extensions/ql-vscode/src/codeql-cli/cli.ts +++ b/extensions/ql-vscode/src/codeql-cli/cli.ts @@ -1739,6 +1739,15 @@ export class CliVersionConstraint { */ public static CLI_VERSION_WITH_TRIM_CACHE = new SemVer("2.15.1"); + public static CLI_VERSION_WITHOUT_MRVA_EXTENSIBLE_PREDICATE_HACK = new SemVer( + "2.16.1", + ); + + /** + * CLI version where there is support for multiple queries on the pack create command. + */ + public static CLI_VERSION_WITH_MULTI_QUERY_PACK_CREATE = new SemVer("2.16.1"); + constructor(private readonly cli: CodeQLCliServer) { /**/ } @@ -1781,6 +1790,19 @@ export class CliVersionConstraint { ); } + async preservesExtensiblePredicatesInMrvaPack() { + // Negated, because we _stopped_ preserving these in 2.16.1. + return !(await this.isVersionAtLeast( + CliVersionConstraint.CLI_VERSION_WITHOUT_MRVA_EXTENSIBLE_PREDICATE_HACK, + )); + } + + async supportsPackCreateWithMultipleQueries() { + return this.isVersionAtLeast( + CliVersionConstraint.CLI_VERSION_WITH_MULTI_QUERY_PACK_CREATE, + ); + } + async supportsMrvaPackCreate(): Promise { return (await this.cli.getFeatures()).mrvaPackCreate === true; } diff --git a/extensions/ql-vscode/src/common/ql.ts b/extensions/ql-vscode/src/common/ql.ts index 6bb08d674..691a08dbc 100644 --- a/extensions/ql-vscode/src/common/ql.ts +++ b/extensions/ql-vscode/src/common/ql.ts @@ -1,4 +1,4 @@ -import { join } from "path"; +import { dirname, join, parse } from "path"; import { pathExists } from "fs-extra"; export const QLPACK_FILENAMES = ["qlpack.yml", "codeql-pack.yml"]; @@ -8,7 +8,13 @@ export const QLPACK_LOCK_FILENAMES = [ ]; export const FALLBACK_QLPACK_FILENAME = QLPACK_FILENAMES[0]; -export async function getQlPackPath( +/** + * Gets the path to the QL pack file (a qlpack.yml or + * codeql-pack.yml). + * @param packRoot The root of the pack. + * @returns The path to the qlpack file, or undefined if it doesn't exist. + */ +export async function getQlPackFilePath( packRoot: string, ): Promise { for (const filename of QLPACK_FILENAMES) { @@ -21,3 +27,28 @@ export async function getQlPackPath( return undefined; } + +/** + * Recursively find the directory containing qlpack.yml or codeql-pack.yml. If + * no such directory is found, the directory containing the query file is returned. + * @param queryFile The query file to start from. + * @returns The path to the pack root. + */ +export async function findPackRoot(queryFile: string): Promise { + let dir = dirname(queryFile); + while (!(await getQlPackFilePath(dir))) { + dir = dirname(dir); + if (isFileSystemRoot(dir)) { + // there is no qlpack.yml or codeql-pack.yml in this directory or any parent directory. + // just use the query file's directory as the pack root. + return dirname(queryFile); + } + } + + return dir; +} + +function isFileSystemRoot(dir: string): boolean { + const pathObj = parse(dir); + return pathObj.root === dir && pathObj.base === ""; +} diff --git a/extensions/ql-vscode/src/common/readonly.ts b/extensions/ql-vscode/src/common/readonly.ts index d6bf75098..19a4ad963 100644 --- a/extensions/ql-vscode/src/common/readonly.ts +++ b/extensions/ql-vscode/src/common/readonly.ts @@ -1,11 +1,12 @@ -export type DeepReadonly = T extends Array - ? DeepReadonlyArray - : // eslint-disable-next-line @typescript-eslint/ban-types - T extends Function - ? T - : T extends object - ? DeepReadonlyObject - : T; +export type DeepReadonly = + T extends Array + ? DeepReadonlyArray + : // eslint-disable-next-line @typescript-eslint/ban-types + T extends Function + ? T + : T extends object + ? DeepReadonlyObject + : T; interface DeepReadonlyArray extends ReadonlyArray> {} diff --git a/extensions/ql-vscode/src/config.ts b/extensions/ql-vscode/src/config.ts index 095b53d96..faa71f864 100644 --- a/extensions/ql-vscode/src/config.ts +++ b/extensions/ql-vscode/src/config.ts @@ -716,12 +716,17 @@ const LLM_GENERATION_DEV_ENDPOINT = new Setting( ); const EXTENSIONS_DIRECTORY = new Setting("extensionsDirectory", MODEL_SETTING); const ENABLE_RUBY = new Setting("enableRuby", MODEL_SETTING); +const ENABLE_ACCESS_PATH_SUGGESTIONS = new Setting( + "enableAccessPathSuggestions", + MODEL_SETTING, +); export interface ModelConfig { flowGeneration: boolean; llmGeneration: boolean; getExtensionsDirectory(languageId: string): string | undefined; enableRuby: boolean; + enableAccessPathSuggestions: boolean; } export class ModelConfigListener extends ConfigListener implements ModelConfig { @@ -762,6 +767,10 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig { public get enableRuby(): boolean { return !!ENABLE_RUBY.getValue(); } + + public get enableAccessPathSuggestions(): boolean { + return !!ENABLE_ACCESS_PATH_SUGGESTIONS.getValue(); + } } const GITHUB_DATABASE_SETTING = new Setting("githubDatabase", ROOT_SETTING); diff --git a/extensions/ql-vscode/src/databases/local-databases-ui.ts b/extensions/ql-vscode/src/databases/local-databases-ui.ts index 674c1ecd3..f1b802ed8 100644 --- a/extensions/ql-vscode/src/databases/local-databases-ui.ts +++ b/extensions/ql-vscode/src/databases/local-databases-ui.ts @@ -235,7 +235,7 @@ async function chooseDatabaseDir(byFolder: boolean): Promise { return getFirst(chosen); } -interface DatabaseSelectionQuickPickItem extends QuickPickItem { +export interface DatabaseSelectionQuickPickItem extends QuickPickItem { databaseKind: "new" | "existing"; } @@ -243,7 +243,7 @@ export interface DatabaseQuickPickItem extends QuickPickItem { databaseItem: DatabaseItem; } -interface DatabaseImportQuickPickItems extends QuickPickItem { +export interface DatabaseImportQuickPickItems extends QuickPickItem { importType: "URL" | "github" | "archive" | "folder"; } diff --git a/extensions/ql-vscode/src/databases/qlpack.ts b/extensions/ql-vscode/src/databases/qlpack.ts index db2b77e6e..fc8260656 100644 --- a/extensions/ql-vscode/src/databases/qlpack.ts +++ b/extensions/ql-vscode/src/databases/qlpack.ts @@ -3,7 +3,7 @@ import { glob } from "glob"; import { basename } from "path"; import { load } from "js-yaml"; import { readFile } from "fs-extra"; -import { getQlPackPath } from "../common/ql"; +import { getQlPackFilePath } from "../common/ql"; import type { CodeQLCliServer, QlpacksInfo } from "../codeql-cli/cli"; import { extLogger } from "../common/logging/vscode"; import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders"; @@ -31,7 +31,7 @@ async function findDbschemePack( ): Promise<{ name: string; isLibraryPack: boolean }> { for (const { packDir, packName } of packs) { if (packDir !== undefined) { - const qlpackPath = await getQlPackPath(packDir); + const qlpackPath = await getQlPackFilePath(packDir); if (qlpackPath !== undefined) { const qlpack = load(await readFile(qlpackPath, "utf8")) as { diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 5e409398d..8e4680139 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -215,8 +215,9 @@ function getCommands( "codeQL.restartLegacyQueryServerOnConfigChange": restartQueryServer, "codeQL.restartQueryServerOnExternalConfigChange": restartQueryServer, "codeQL.copyVersion": async () => { - const text = `CodeQL extension version: ${extension?.packageJSON - .version} \nCodeQL CLI version: ${await getCliVersion()} \nPlatform: ${platform()} ${arch()}`; + const text = `CodeQL extension version: ${ + extension?.packageJSON.version + } \nCodeQL CLI version: ${await getCliVersion()} \nPlatform: ${platform()} ${arch()}`; await env.clipboard.writeText(text); void showAndLogInformationMessage(extLogger, text); }, diff --git a/extensions/ql-vscode/src/local-queries/quick-query.ts b/extensions/ql-vscode/src/local-queries/quick-query.ts index 60d61765b..53ca5677e 100644 --- a/extensions/ql-vscode/src/local-queries/quick-query.ts +++ b/extensions/ql-vscode/src/local-queries/quick-query.ts @@ -11,7 +11,7 @@ import { getPrimaryDbscheme, getQlPackForDbscheme } from "../databases/qlpack"; import type { ProgressCallback } from "../common/vscode/progress"; import { UserCancellationException } from "../common/vscode/progress"; import { getErrorMessage } from "../common/helpers-pure"; -import { FALLBACK_QLPACK_FILENAME, getQlPackPath } from "../common/ql"; +import { FALLBACK_QLPACK_FILENAME, getQlPackFilePath } from "../common/ql"; import type { App } from "../common/app"; import type { ExtensionApp } from "../common/vscode/vscode-app"; @@ -119,7 +119,7 @@ export async function displayQuickQuery( const dbscheme = await getPrimaryDbscheme(datasetFolder); const qlpack = (await getQlPackForDbscheme(cliServer, dbscheme)) .dbschemePack; - const qlPackFile = await getQlPackPath(queriesDir); + const qlPackFile = await getQlPackFilePath(queriesDir); const qlFile = join(queriesDir, QUICK_QUERY_QUERY_NAME); const shouldRewrite = await checkShouldRewrite(qlPackFile, qlpack); diff --git a/extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts b/extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts index e306f18ef..61a7de920 100644 --- a/extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts +++ b/extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts @@ -36,7 +36,7 @@ import { redactableError } from "../common/errors"; import type { App } from "../common/app"; import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item"; import { containsPath, pathsEqual } from "../common/files"; -import { getQlPackPath } from "../common/ql"; +import { getQlPackFilePath } from "../common/ql"; import { getQlPackLanguage } from "../common/qlpack-language"; type QueryLanguagesToDatabaseMap = Record; @@ -111,7 +111,7 @@ export class SkeletonQueryWizard { // Try to detect if there is already a qlpack in this location. We will assume that // the user hasn't changed the language of the qlpack. - const qlPackPath = await getQlPackPath(this.qlPackStoragePath); + const qlPackPath = await getQlPackFilePath(this.qlPackStoragePath); // If we are creating or using a qlpack in the user's selected folder, we will also // create the query in that folder @@ -248,7 +248,7 @@ export class SkeletonQueryWizard { const matchingQueryPackPath = matchingQueryPacks[0]; - const qlPackPath = await getQlPackPath(matchingQueryPackPath); + const qlPackPath = await getQlPackFilePath(matchingQueryPackPath); if (!qlPackPath) { return undefined; } diff --git a/extensions/ql-vscode/src/model-editor/extension-pack-picker.ts b/extensions/ql-vscode/src/model-editor/extension-pack-picker.ts index b1f391951..9962164ef 100644 --- a/extensions/ql-vscode/src/model-editor/extension-pack-picker.ts +++ b/extensions/ql-vscode/src/model-editor/extension-pack-picker.ts @@ -9,7 +9,7 @@ import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders"; import type { ProgressCallback } from "../common/vscode/progress"; import { UserCancellationException } from "../common/vscode/progress"; import type { DatabaseItem } from "../databases/local-databases"; -import { getQlPackPath, QLPACK_FILENAMES } from "../common/ql"; +import { getQlPackFilePath, QLPACK_FILENAMES } from "../common/ql"; import { getErrorMessage } from "../common/helpers-pure"; import type { ExtensionPack } from "./shared/extension-pack"; import type { NotificationLogger } from "../common/logging"; @@ -208,7 +208,7 @@ async function readExtensionPack( path: string, language: string, ): Promise { - const qlpackPath = await getQlPackPath(path); + const qlpackPath = await getQlPackFilePath(path); if (!qlpackPath) { throw new Error( `Could not find any of ${QLPACK_FILENAMES.join(", ")} in ${path}`, 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 2139f95bc..7b8f08878 100644 --- a/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisAnalyzedRepos.stories.tsx +++ b/extensions/ql-vscode/src/stories/variant-analysis/VariantAnalysisAnalyzedRepos.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryFn } from "@storybook/react"; import { faker } from "@faker-js/faker"; +import { customAlphabet } from "nanoid"; import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer"; import { VariantAnalysisAnalyzedRepos } from "../../view/variant-analysis/VariantAnalysisAnalyzedRepos"; @@ -125,11 +126,10 @@ Example.args = { }; faker.seed(42); -const uniqueStore = {}; const manyScannedRepos = Array.from({ length: 1000 }, (_, i) => { const mockedScannedRepo = createMockScannedRepo(); - + const nanoid = customAlphabet("123456789"); return { ...mockedScannedRepo, analysisStatus: VariantAnalysisRepoStatus.Succeeded, @@ -137,12 +137,8 @@ const manyScannedRepos = Array.from({ length: 1000 }, (_, i) => { repository: { ...mockedScannedRepo.repository, // We need to ensure the ID is unique for React keys - id: faker.helpers.unique(faker.number.int, [], { - store: uniqueStore, - }), - fullName: `octodemo/${faker.helpers.unique(faker.word.sample, [], { - store: uniqueStore, - })}`, + id: parseInt(nanoid()), + fullName: `octodemo/${nanoid()}`, }, }; }); diff --git a/extensions/ql-vscode/src/variant-analysis/ql-pack-details.ts b/extensions/ql-vscode/src/variant-analysis/ql-pack-details.ts new file mode 100644 index 000000000..d8d0d31c3 --- /dev/null +++ b/extensions/ql-vscode/src/variant-analysis/ql-pack-details.ts @@ -0,0 +1,20 @@ +import type { QueryLanguage } from "../common/query-language"; + +/** + * Details about the original QL pack that is used for triggering + * a variant analysis. + */ +export interface QlPackDetails { + // The absolute paths of the query files. + queryFiles: string[]; + + // The absolute path to the QL pack that is used for triggering a variant analysis. + // If there is no query pack, this is the same as the directory of the query files. + qlPackRootPath: string; + + // The absolute path to the QL pack file (a qlpack.yml or codeql-pack.yml) or undefined if + // it doesn't exist. + qlPackFilePath: string | undefined; + + language: QueryLanguage; +} diff --git a/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts b/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts index 62ced7fe4..42139504b 100644 --- a/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts +++ b/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts @@ -1,6 +1,6 @@ import type { CancellationToken } from "vscode"; import { Uri, window } from "vscode"; -import { relative, join, sep, dirname, parse, basename } from "path"; +import { join, sep, basename, relative } from "path"; import { dump, load } from "js-yaml"; import { copy, writeFile, readFile, mkdirp } from "fs-extra"; import type { DirectoryResult } from "tmp-promise"; @@ -29,27 +29,20 @@ import { import type { Repository } from "./shared/repository"; import type { DbManager } from "../databases/db-manager"; import { - getQlPackPath, + getQlPackFilePath, FALLBACK_QLPACK_FILENAME, QLPACK_FILENAMES, QLPACK_LOCK_FILENAMES, } from "../common/ql"; -import type { QueryLanguage } from "../common/query-language"; -import { tryGetQueryMetadata } from "../codeql-cli/query-metadata"; -import { askForLanguage, findLanguage } from "../codeql-cli/query-language"; import type { QlPackFile } from "../packaging/qlpack-file"; import { expandShortPaths } from "../common/short-paths"; +import type { QlPackDetails } from "./ql-pack-details"; /** * Well-known names for the query pack used by the server. */ const QUERY_PACK_NAME = "codeql-remote/query"; -interface GeneratedQueryPack { - base64Pack: string; - language: string; -} - /** * Two possibilities: * 1. There is no qlpack.yml (or codeql-pack.yml) in this directory. Assume this is a lone query and generate a synthetic qlpack for it. @@ -59,45 +52,30 @@ interface GeneratedQueryPack { */ async function generateQueryPack( cliServer: CodeQLCliServer, - queryFile: string, + qlPackDetails: QlPackDetails, tmpDir: RemoteQueryTempDir, -): Promise { - const originalPackRoot = await findPackRoot(queryFile); - const packRelativePath = relative(originalPackRoot, queryFile); +): Promise { const workspaceFolders = getOnDiskWorkspaceFolders(); const extensionPacks = await getExtensionPacksToInject( cliServer, workspaceFolders, ); - const mustSynthesizePack = - (await getQlPackPath(originalPackRoot)) === undefined; + const mustSynthesizePack = qlPackDetails.qlPackFilePath === undefined; const cliSupportsMrvaPackCreate = await cliServer.cliConstraints.supportsMrvaPackCreate(); - const language: QueryLanguage | undefined = mustSynthesizePack - ? await askForLanguage(cliServer) // open popup to ask for language if not already hardcoded - : await findLanguage(cliServer, Uri.file(queryFile)); - if (!language) { - throw new UserCancellationException("Could not determine language"); - } - - let queryPackDir: string; + let targetPackPath: string; let needsInstall: boolean; if (mustSynthesizePack) { // This section applies whether or not the CLI supports MRVA pack creation directly. - queryPackDir = tmpDir.queryPackDir; + targetPackPath = tmpDir.queryPackDir; // Synthesize a query pack for the query. // copy only the query file to the query pack directory // and generate a synthetic query pack - await createNewQueryPack( - queryFile, - queryPackDir, - language, - packRelativePath, - ); + await createNewQueryPack(qlPackDetails, targetPackPath); // Clear the cliServer cache so that the previous qlpack text is purged from the CLI. await cliServer.clearCache(); @@ -105,14 +83,8 @@ async function generateQueryPack( needsInstall = true; } else if (!cliSupportsMrvaPackCreate) { // We need to copy the query pack to a temporary directory and then fix it up to work with MRVA. - queryPackDir = tmpDir.queryPackDir; - await copyExistingQueryPack( - cliServer, - originalPackRoot, - queryFile, - queryPackDir, - packRelativePath, - ); + targetPackPath = tmpDir.queryPackDir; + await copyExistingQueryPack(cliServer, qlPackDetails, targetPackPath); // We should already have all the dependencies available, but these older versions of the CLI // have a bug where they will not search `--additional-packs` during validation in `codeql pack bundle`. @@ -120,14 +92,14 @@ async function generateQueryPack( needsInstall = true; } else { // The CLI supports creating a MRVA query pack directly from the source pack. - queryPackDir = originalPackRoot; + targetPackPath = qlPackDetails.qlPackRootPath; // We expect any dependencies to be available already. needsInstall = false; } if (needsInstall) { // Install the dependencies of the synthesized query pack. - await cliServer.packInstall(queryPackDir, { + await cliServer.packInstall(targetPackPath, { workspaceFolders, }); @@ -137,10 +109,23 @@ async function generateQueryPack( let precompilationOpts: string[]; if (cliSupportsMrvaPackCreate) { + if ( + qlPackDetails.queryFiles.length > 1 && + !(await cliServer.cliConstraints.supportsPackCreateWithMultipleQueries()) + ) { + throw new Error( + `Installed CLI version does not allow creating a MRVA pack with multiple queries`, + ); + } + + const queryOpts = qlPackDetails.queryFiles.flatMap((q) => [ + "--query", + join(targetPackPath, relative(qlPackDetails.qlPackRootPath, q)), + ]); + precompilationOpts = [ "--mrva", - "--query", - join(queryPackDir, packRelativePath), + ...queryOpts, // We need to specify the extension packs as dependencies so that they are included in the MRVA pack. // The version range doesn't matter, since they'll always be found by source lookup. ...extensionPacks.map((p) => `--extension-pack=${p}@*`), @@ -149,7 +134,7 @@ async function generateQueryPack( if (await cliServer.cliConstraints.usesGlobalCompilationCache()) { precompilationOpts = ["--qlx"]; } else { - const cache = join(originalPackRoot, ".cache"); + const cache = join(qlPackDetails.qlPackRootPath, ".cache"); precompilationOpts = [ "--qlx", "--no-default-compilation-cache", @@ -158,60 +143,61 @@ async function generateQueryPack( } if (extensionPacks.length > 0) { - await addExtensionPacksAsDependencies(queryPackDir, extensionPacks); + await addExtensionPacksAsDependencies(targetPackPath, extensionPacks); } } const bundlePath = tmpDir.bundleFile; void extLogger.log( - `Compiling and bundling query pack from ${queryPackDir} to ${bundlePath}. (This may take a while.)`, + `Compiling and bundling query pack from ${targetPackPath} to ${bundlePath}. (This may take a while.)`, ); await cliServer.packBundle( - queryPackDir, + targetPackPath, workspaceFolders, bundlePath, tmpDir.compiledPackDir, precompilationOpts, ); const base64Pack = (await readFile(bundlePath)).toString("base64"); - return { - base64Pack, - language, - }; + return base64Pack; } async function createNewQueryPack( - queryFile: string, - queryPackDir: string, - language: string | undefined, - packRelativePath: string, + qlPackDetails: QlPackDetails, + targetPackPath: string, ) { - void extLogger.log(`Copying ${queryFile} to ${queryPackDir}`); - const targetQueryFileName = join(queryPackDir, packRelativePath); - await copy(queryFile, targetQueryFileName); + for (const queryFile of qlPackDetails.queryFiles) { + void extLogger.log(`Copying ${queryFile} to ${targetPackPath}`); + const relativeQueryPath = relative(qlPackDetails.qlPackRootPath, queryFile); + const targetQueryFileName = join(targetPackPath, relativeQueryPath); + await copy(queryFile, targetQueryFileName); + } + void extLogger.log("Generating synthetic query pack"); const syntheticQueryPack = { name: QUERY_PACK_NAME, version: "0.0.0", dependencies: { - [`codeql/${language}-all`]: "*", + [`codeql/${qlPackDetails.language}-all`]: "*", }, - defaultSuite: generateDefaultSuite(packRelativePath), + defaultSuite: generateDefaultSuite(qlPackDetails), }; + await writeFile( - join(queryPackDir, FALLBACK_QLPACK_FILENAME), + join(targetPackPath, FALLBACK_QLPACK_FILENAME), dump(syntheticQueryPack), ); } async function copyExistingQueryPack( cliServer: CodeQLCliServer, - originalPackRoot: string, - queryFile: string, - queryPackDir: string, - packRelativePath: string, + qlPackDetails: QlPackDetails, + targetPackPath: string, ) { - const toCopy = await cliServer.packPacklist(originalPackRoot, false); + const toCopy = await cliServer.packPacklist( + qlPackDetails.qlPackRootPath, + false, + ); // Also include query files that contain extensible predicates. These query files are not // needed for the query to run, but they are needed for the query pack to pass deep validation @@ -219,19 +205,20 @@ async function copyExistingQueryPack( if ( await cliServer.cliConstraints.supportsGenerateExtensiblePredicateMetadata() ) { - const metadata = - await cliServer.generateExtensiblePredicateMetadata(originalPackRoot); + const metadata = await cliServer.generateExtensiblePredicateMetadata( + qlPackDetails.qlPackRootPath, + ); metadata.extensible_predicates.forEach((predicate) => { if (predicate.path.endsWith(".ql")) { - toCopy.push(join(originalPackRoot, predicate.path)); + toCopy.push(join(qlPackDetails.qlPackRootPath, predicate.path)); } }); } [ // also copy the lock file (either new name or old name) and the query file itself. These are not included in the packlist. - ...QLPACK_LOCK_FILENAMES.map((f) => join(originalPackRoot, f)), - queryFile, + ...QLPACK_LOCK_FILENAMES.map((f) => join(qlPackDetails.qlPackRootPath, f)), + ...qlPackDetails.queryFiles, ].forEach((absolutePath) => { if (absolutePath) { toCopy.push(absolutePath); @@ -239,7 +226,7 @@ async function copyExistingQueryPack( }); let copiedCount = 0; - await copy(originalPackRoot, queryPackDir, { + await copy(qlPackDetails.qlPackRootPath, targetPackPath, { filter: (file: string) => // copy file if it is in the packlist, or it is a parent directory of a file in the packlist !!toCopy.find((f) => { @@ -254,29 +241,9 @@ async function copyExistingQueryPack( }), }); - void extLogger.log(`Copied ${copiedCount} files to ${queryPackDir}`); + void extLogger.log(`Copied ${copiedCount} files to ${targetPackPath}`); - await fixPackFile(queryPackDir, packRelativePath); -} - -async function findPackRoot(queryFile: string): Promise { - // recursively find the directory containing qlpack.yml or codeql-pack.yml - let dir = dirname(queryFile); - while (!(await getQlPackPath(dir))) { - dir = dirname(dir); - if (isFileSystemRoot(dir)) { - // there is no qlpack.yml or codeql-pack.yml in this directory or any parent directory. - // just use the query file's directory as the pack root. - return dirname(queryFile); - } - } - - return dir; -} - -function isFileSystemRoot(dir: string): boolean { - const pathObj = parse(dir); - return pathObj.root === dir && pathObj.base === ""; + await fixPackFile(targetPackPath, qlPackDetails); } interface RemoteQueryTempDir { @@ -319,35 +286,26 @@ interface PreparedRemoteQuery { actionBranch: string; base64Pack: string; repoSelection: RepositorySelection; - queryFile: string; - queryMetadata: QueryMetadata | undefined; controllerRepo: Repository; queryStartTime: number; - language: string; } export async function prepareRemoteQueryRun( cliServer: CodeQLCliServer, credentials: Credentials, - uris: Uri[], + qlPackDetails: QlPackDetails, progress: ProgressCallback, token: CancellationToken, dbManager: DbManager, ): Promise { - if (uris.length !== 1) { - // For now we only support a single file, but we're aiming - // to support multiple files in the near future. - throw Error("Exactly one query file must be selected."); + for (const queryFile of qlPackDetails.queryFiles) { + if (!queryFile.endsWith(".ql")) { + throw new UserCancellationException( + `Not a CodeQL query file: ${queryFile}`, + ); + } } - const uri = uris[0]; - - if (!uri.fsPath.endsWith(".ql")) { - throw new UserCancellationException("Not a CodeQL query file."); - } - - const queryFile = uri.fsPath; - progress({ maxStep: 4, step: 1, @@ -379,16 +337,14 @@ export async function prepareRemoteQueryRun( const tempDir = await createRemoteQueriesTempDirectory(); - let pack: GeneratedQueryPack; + let base64Pack: string; try { - pack = await generateQueryPack(cliServer, queryFile, tempDir); + base64Pack = await generateQueryPack(cliServer, qlPackDetails, tempDir); } finally { await tempDir.remoteQueryDir.cleanup(); } - const { base64Pack, language } = pack; - if (token.isCancellationRequested) { throw new UserCancellationException("Cancelled"); } @@ -401,17 +357,13 @@ export async function prepareRemoteQueryRun( const actionBranch = getActionBranch(); const queryStartTime = Date.now(); - const queryMetadata = await tryGetQueryMetadata(cliServer, queryFile); return { actionBranch, base64Pack, repoSelection, - queryFile, - queryMetadata, controllerRepo, queryStartTime, - language, }; } @@ -426,26 +378,26 @@ export async function prepareRemoteQueryRun( * - Removes any `${workspace}` version references from the qlpack.yml or codeql-pack.yml file. Converts them * to `*` versions. * - * @param queryPackDir The directory containing the query pack - * @param packRelativePath The relative path to the query pack from the root of the query pack + * @param targetPackPath The path to the directory containing the target pack + * @param qlPackDetails The details of the original QL pack */ async function fixPackFile( - queryPackDir: string, - packRelativePath: string, + targetPackPath: string, + qlPackDetails: QlPackDetails, ): Promise { - const packPath = await getQlPackPath(queryPackDir); + const packPath = await getQlPackFilePath(targetPackPath); // This should not happen since we create the pack ourselves. if (!packPath) { throw new Error( `Could not find ${QLPACK_FILENAMES.join( " or ", - )} file in '${queryPackDir}'`, + )} file in '${targetPackPath}'`, ); } const qlpack = load(await readFile(packPath, "utf8")) as QlPackFile; - updateDefaultSuite(qlpack, packRelativePath); + updateDefaultSuite(qlpack, qlPackDetails); removeWorkspaceRefs(qlpack); await writeFile(packPath, dump(qlpack)); @@ -483,7 +435,7 @@ async function addExtensionPacksAsDependencies( queryPackDir: string, extensionPacks: string[], ): Promise { - const qlpackFile = await getQlPackPath(queryPackDir); + const qlpackFile = await getQlPackFilePath(queryPackDir); if (!qlpackFile) { throw new Error( `Could not find ${QLPACK_FILENAMES.join( @@ -509,19 +461,23 @@ async function addExtensionPacksAsDependencies( await writeFile(qlpackFile, dump(syntheticQueryPack)); } -function updateDefaultSuite(qlpack: QlPackFile, packRelativePath: string) { +function updateDefaultSuite(qlpack: QlPackFile, qlPackDetails: QlPackDetails) { delete qlpack.defaultSuiteFile; - qlpack.defaultSuite = generateDefaultSuite(packRelativePath); + qlpack.defaultSuite = generateDefaultSuite(qlPackDetails); } -function generateDefaultSuite(packRelativePath: string) { +function generateDefaultSuite(qlPackDetails: QlPackDetails) { + const queries = qlPackDetails.queryFiles.map((query) => { + const relativePath = relative(qlPackDetails.qlPackRootPath, query); + return { + query: relativePath.replace(/\\/g, "/"), + }; + }); return [ { description: "Query suite for variant analysis", }, - { - query: packRelativePath.replace(/\\/g, "/"), - }, + ...queries, ]; } diff --git a/extensions/ql-vscode/src/variant-analysis/shared/variant-analysis-filter-sort.ts b/extensions/ql-vscode/src/variant-analysis/shared/variant-analysis-filter-sort.ts index 9d2d153bc..794439e13 100644 --- a/extensions/ql-vscode/src/variant-analysis/shared/variant-analysis-filter-sort.ts +++ b/extensions/ql-vscode/src/variant-analysis/shared/variant-analysis-filter-sort.ts @@ -146,8 +146,8 @@ export function filterAndSortRepositoriesWithResults< filterSortState.repositoryIds.length > 0 ) { return repositories - .filter( - (repo) => filterSortState.repositoryIds?.includes(repo.repository.id), + .filter((repo) => + filterSortState.repositoryIds?.includes(repo.repository.id), ) .sort(compareWithResults(filterSortState)); } diff --git a/extensions/ql-vscode/src/variant-analysis/shared/variant-analysis.ts b/extensions/ql-vscode/src/variant-analysis/shared/variant-analysis.ts index 1c862b793..73347426f 100644 --- a/extensions/ql-vscode/src/variant-analysis/shared/variant-analysis.ts +++ b/extensions/ql-vscode/src/variant-analysis/shared/variant-analysis.ts @@ -159,6 +159,7 @@ export interface VariantAnalysisSubmission { // unclear what it will look like in the future. export interface VariantAnalysisQueries { language: QueryLanguage; + count: number; } export async function isVariantAnalysisComplete( diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts index b5452c59d..4cd52f26f 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts @@ -22,6 +22,7 @@ import { DisposableObject } from "../common/disposable-object"; import { VariantAnalysisMonitor } from "./variant-analysis-monitor"; import type { VariantAnalysis, + VariantAnalysisQueries, VariantAnalysisRepositoryTask, VariantAnalysisScannedRepository, VariantAnalysisScannedRepositoryResult, @@ -87,7 +88,10 @@ import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item"; import { RequestError } from "@octokit/request-error"; import { handleRequestError } from "./custom-errors"; import { createMultiSelectionCommand } from "../common/vscode/selection-commands"; -import { askForLanguage } from "../codeql-cli/query-language"; +import { askForLanguage, findLanguage } from "../codeql-cli/query-language"; +import type { QlPackDetails } from "./ql-pack-details"; +import { findPackRoot, getQlPackFilePath } from "../common/ql"; +import { tryGetQueryMetadata } from "../codeql-cli/query-metadata"; const maxRetryCount = 3; @@ -191,26 +195,22 @@ export class VariantAnalysisManager throw new Error("Please select a .ql file to run as a variant analysis"); } - await this.runVariantAnalysisCommand(fileUri); + await this.runVariantAnalysisCommand([fileUri]); } private async runVariantAnalysisFromContextEditor(uri: Uri) { - await this.runVariantAnalysisCommand(uri); + await this.runVariantAnalysisCommand([uri]); } private async runVariantAnalysisFromExplorer(fileURIs: Uri[]): Promise { - if (fileURIs.length !== 1) { - throw new Error("Can only run a single query at a time"); - } - - return this.runVariantAnalysisCommand(fileURIs[0]); + return this.runVariantAnalysisCommand(fileURIs); } private async runVariantAnalysisFromQueriesPanel( queryTreeViewItem: QueryTreeViewItem, ): Promise { if (queryTreeViewItem.path !== undefined) { - await this.runVariantAnalysisCommand(Uri.file(queryTreeViewItem.path)); + await this.runVariantAnalysisCommand([Uri.file(queryTreeViewItem.path)]); } } @@ -223,6 +223,9 @@ export class VariantAnalysisManager }); const language = await askForLanguage(this.cliServer); + if (!language) { + return; + } progress({ maxStep: 8, @@ -263,8 +266,18 @@ export class VariantAnalysisManager return; } + const qlPackFilePath = await getQlPackFilePath(packDir); + + // Build up details to pass to the functions that run the variant analysis. + const qlPackDetails: QlPackDetails = { + queryFiles: problemQueries, + qlPackRootPath: packDir, + qlPackFilePath, + language, + }; + await this.runVariantAnalysis( - problemQueries.map((q) => Uri.file(q)), + qlPackDetails, (p) => progress({ ...p, @@ -294,10 +307,43 @@ export class VariantAnalysisManager return problemQueries; } - private async runVariantAnalysisCommand(uri: Uri): Promise { + private async runVariantAnalysisCommand(queryFiles: Uri[]): Promise { + if (queryFiles.length === 0) { + throw new Error("Please select a .ql file to run as a variant analysis"); + } + + const qlPackRootPath = await findPackRoot(queryFiles[0].fsPath); + const qlPackFilePath = await getQlPackFilePath(qlPackRootPath); + + // Make sure that all remaining queries have the same pack root + for (let i = 1; i < queryFiles.length; i++) { + const packRoot = await findPackRoot(queryFiles[i].fsPath); + if (packRoot !== qlPackRootPath) { + throw new Error( + "Please select queries that all belong to the same query pack", + ); + } + } + + // Open popup to ask for language if not already hardcoded + const language = qlPackFilePath + ? await findLanguage(this.cliServer, queryFiles[0]) + : await askForLanguage(this.cliServer); + + if (!language) { + throw new UserCancellationException("Could not determine query language"); + } + + const qlPackDetails: QlPackDetails = { + queryFiles: queryFiles.map((uri) => uri.fsPath), + qlPackRootPath, + qlPackFilePath, + language, + }; + return withProgress( async (progress, token) => { - await this.runVariantAnalysis([uri], progress, token); + await this.runVariantAnalysis(qlPackDetails, progress, token); }, { title: "Run Variant Analysis", @@ -307,7 +353,7 @@ export class VariantAnalysisManager } public async runVariantAnalysis( - uris: Uri[], + qlPackDetails: QlPackDetails, progress: ProgressCallback, token: CancellationToken, ): Promise { @@ -323,35 +369,43 @@ export class VariantAnalysisManager actionBranch, base64Pack, repoSelection, - queryFile, - queryMetadata, controllerRepo, queryStartTime, - language, } = await prepareRemoteQueryRun( this.cliServer, this.app.credentials, - uris, + qlPackDetails, progress, token, this.dbManager, ); - const queryName = getQueryName(queryMetadata, queryFile); - const variantAnalysisLanguage = parseVariantAnalysisQueryLanguage(language); + // For now we get the metadata for the first query in the pack. + // and use that in the submission and query history. In the future + // we'll need to consider how to handle having multiple queries. + const firstQueryFile = qlPackDetails.queryFiles[0]; + const queryMetadata = await tryGetQueryMetadata( + this.cliServer, + firstQueryFile, + ); + const queryName = getQueryName(queryMetadata, firstQueryFile); + const variantAnalysisLanguage = parseVariantAnalysisQueryLanguage( + qlPackDetails.language, + ); if (variantAnalysisLanguage === undefined) { throw new UserCancellationException( - `Found unsupported language: ${language}`, + `Found unsupported language: ${qlPackDetails.language}`, ); } - const queryText = await readFile(queryFile, "utf8"); + const queryText = await readFile(firstQueryFile, "utf8"); - const queries = - uris.length === 1 + const queries: VariantAnalysisQueries | undefined = + qlPackDetails.queryFiles.length === 1 ? undefined : { - language: variantAnalysisLanguage, + language: qlPackDetails.language, + count: qlPackDetails.queryFiles.length, }; const variantAnalysisSubmission: VariantAnalysisSubmission = { @@ -360,7 +414,7 @@ export class VariantAnalysisManager controllerRepoId: controllerRepo.id, query: { name: queryName, - filePath: queryFile, + filePath: firstQueryFile, pack: base64Pack, language: variantAnalysisLanguage, text: queryText, diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts index 2aebd1fb9..65f5151a6 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts @@ -218,9 +218,15 @@ export class VariantAnalysisView } private getTitle(variantAnalysis: VariantAnalysis | undefined): string { - return variantAnalysis - ? `${variantAnalysis.query.name} - Variant Analysis Results` - : `Variant Analysis ${this.variantAnalysisId} - Results`; + if (!variantAnalysis) { + return `Variant Analysis ${this.variantAnalysisId} - Results`; + } + + if (variantAnalysis.queries) { + return `Variant Analysis using multiple queries - Results`; + } else { + return `${variantAnalysis.query.name} - Variant Analysis Results`; + } } private async showDataFlows(dataFlows: DataFlowPaths): Promise { diff --git a/extensions/ql-vscode/src/view/compare/Compare.tsx b/extensions/ql-vscode/src/view/compare/Compare.tsx index 65dc7d0db..5b2b5a51d 100644 --- a/extensions/ql-vscode/src/view/compare/Compare.tsx +++ b/extensions/ql-vscode/src/view/compare/Compare.tsx @@ -25,7 +25,7 @@ const Message = styled.div` padding: 1.5rem; `; -export function Compare(_: Record): JSX.Element { +export function Compare(_: Record): React.JSX.Element { const [queryInfo, setQueryInfo] = useState(null); const [comparison, setComparison] = useState( diff --git a/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPaths.tsx b/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPaths.tsx index 4b43b1573..d7846edcf 100644 --- a/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPaths.tsx +++ b/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPaths.tsx @@ -32,7 +32,7 @@ export const DataFlowPaths = ({ dataFlowPaths, }: { dataFlowPaths: DataFlowPathsDomainModel; -}): JSX.Element => { +}): React.JSX.Element => { const [selectedCodeFlow, setSelectedCodeFlow] = useState( dataFlowPaths.codeFlows[0], ); diff --git a/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPathsView.tsx b/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPathsView.tsx index 798577a3b..9267b81f0 100644 --- a/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPathsView.tsx +++ b/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPathsView.tsx @@ -9,7 +9,7 @@ export type DataFlowPathsViewProps = { export function DataFlowPathsView({ dataFlowPaths: initialDataFlowPaths, -}: DataFlowPathsViewProps): JSX.Element { +}: DataFlowPathsViewProps): React.JSX.Element { const [dataFlowPaths, setDataFlowPaths] = useState< DataFlowPathsDomainModel | undefined >(initialDataFlowPaths); diff --git a/extensions/ql-vscode/src/view/method-modeling/MethodModeling.tsx b/extensions/ql-vscode/src/view/method-modeling/MethodModeling.tsx index cf0048e68..47872ee59 100644 --- a/extensions/ql-vscode/src/view/method-modeling/MethodModeling.tsx +++ b/extensions/ql-vscode/src/view/method-modeling/MethodModeling.tsx @@ -64,7 +64,7 @@ export const MethodModeling = ({ method, isModelingInProgress, onChange, -}: MethodModelingProps): JSX.Element => { +}: MethodModelingProps): React.JSX.Element => { return ( diff --git a/extensions/ql-vscode/src/view/method-modeling/MethodModelingInputs.tsx b/extensions/ql-vscode/src/view/method-modeling/MethodModelingInputs.tsx index bb9d1c920..78d96f391 100644 --- a/extensions/ql-vscode/src/view/method-modeling/MethodModelingInputs.tsx +++ b/extensions/ql-vscode/src/view/method-modeling/MethodModelingInputs.tsx @@ -39,7 +39,7 @@ export const MethodModelingInputs = ({ modelingStatus, isModelingInProgress, onChange, -}: MethodModelingInputsProps): JSX.Element => { +}: MethodModelingInputsProps): React.JSX.Element => { const inputProps = { language, method, diff --git a/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx b/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx index a12d257a7..fd9742c7b 100644 --- a/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx +++ b/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx @@ -16,7 +16,9 @@ type Props = { initialViewState?: MethodModelingPanelViewState; }; -export function MethodModelingView({ initialViewState }: Props): JSX.Element { +export function MethodModelingView({ + initialViewState, +}: Props): React.JSX.Element { const [viewState, setViewState] = useState< MethodModelingPanelViewState | undefined >(initialViewState); diff --git a/extensions/ql-vscode/src/view/model-editor/MethodName.tsx b/extensions/ql-vscode/src/view/model-editor/MethodName.tsx index 7e239d9c2..bf1892394 100644 --- a/extensions/ql-vscode/src/view/model-editor/MethodName.tsx +++ b/extensions/ql-vscode/src/view/model-editor/MethodName.tsx @@ -22,7 +22,7 @@ const TypeMethodName = (method: Method) => { ); }; -export const MethodName = (method: Method): JSX.Element => { +export const MethodName = (method: Method): React.JSX.Element => { return ( <Name> {method.packageName && <>{method.packageName}.</>} diff --git a/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx b/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx index b59f5e9cc..c0d868f54 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelEditor.tsx @@ -85,7 +85,7 @@ export function ModelEditor({ initialMethods = [], initialModeledMethods = {}, initialHideModeledMethods = INITIAL_HIDE_MODELED_METHODS_VALUE, -}: Props): JSX.Element { +}: Props): React.JSX.Element { const [viewState, setViewState] = useState<ModelEditorViewState | undefined>( initialViewState, ); diff --git a/extensions/ql-vscode/src/view/model-editor/ModelInputDropdown.tsx b/extensions/ql-vscode/src/view/model-editor/ModelInputDropdown.tsx index e50b5f566..999e651af 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelInputDropdown.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelInputDropdown.tsx @@ -27,7 +27,7 @@ export const ModelInputDropdown = ({ modeledMethod, modelingStatus, onChange, -}: Props): JSX.Element => { +}: Props): React.JSX.Element => { const options = useMemo(() => { const modelsAsDataLanguage = getModelsAsDataLanguage(language); diff --git a/extensions/ql-vscode/src/view/model-editor/ModelOutputDropdown.tsx b/extensions/ql-vscode/src/view/model-editor/ModelOutputDropdown.tsx index 5c5053903..a03d72042 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelOutputDropdown.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelOutputDropdown.tsx @@ -27,7 +27,7 @@ export const ModelOutputDropdown = ({ modeledMethod, modelingStatus, onChange, -}: Props): JSX.Element => { +}: Props): React.JSX.Element => { const options = useMemo(() => { const modelsAsDataLanguage = getModelsAsDataLanguage(language); diff --git a/extensions/ql-vscode/src/view/model-editor/ModelTypeDropdown.tsx b/extensions/ql-vscode/src/view/model-editor/ModelTypeDropdown.tsx index 98b9bf7c1..7842a9c23 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelTypeDropdown.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelTypeDropdown.tsx @@ -31,7 +31,7 @@ export const ModelTypeDropdown = ({ modeledMethod, modelingStatus, onChange, -}: Props): JSX.Element => { +}: Props): React.JSX.Element => { const options = useMemo(() => { const baseOptions: Array<{ value: ModeledMethodType; label: string }> = [ { value: "none", label: "Unmodeled" }, diff --git a/extensions/ql-vscode/src/view/model-editor/ModelTypeTextbox.tsx b/extensions/ql-vscode/src/view/model-editor/ModelTypeTextbox.tsx index 48a2de7df..d52ab1306 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModelTypeTextbox.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModelTypeTextbox.tsx @@ -15,12 +15,16 @@ type Props = { "aria-label"?: string; }; +const stopClickPropagation = (e: React.MouseEvent) => { + e.stopPropagation(); +}; + export const ModelTypeTextbox = ({ modeledMethod, typeInfo, onChange, ...props -}: Props): JSX.Element => { +}: Props): React.JSX.Element => { const [value, setValue] = useState<string | undefined>( modeledMethod[typeInfo], ); @@ -48,5 +52,12 @@ export const ModelTypeTextbox = ({ 500, ); - return <VSCodeTextField value={value} onInput={handleChange} {...props} />; + return ( + <VSCodeTextField + value={value} + onInput={handleChange} + onClick={stopClickPropagation} + {...props} + /> + ); }; diff --git a/extensions/ql-vscode/src/view/results/AlertTableNoResults.tsx b/extensions/ql-vscode/src/view/results/AlertTableNoResults.tsx index 51bd4c5ff..5e10849cd 100644 --- a/extensions/ql-vscode/src/view/results/AlertTableNoResults.tsx +++ b/extensions/ql-vscode/src/view/results/AlertTableNoResults.tsx @@ -6,7 +6,7 @@ interface Props { showRawResults: () => void; } -export function AlertTableNoResults(props: Props): JSX.Element { +export function AlertTableNoResults(props: Props): React.JSX.Element { if (props.nonemptyRawResults) { return ( <span> diff --git a/extensions/ql-vscode/src/view/results/AlertTableTruncatedMessage.tsx b/extensions/ql-vscode/src/view/results/AlertTableTruncatedMessage.tsx index c2c1565ea..865ca8bd3 100644 --- a/extensions/ql-vscode/src/view/results/AlertTableTruncatedMessage.tsx +++ b/extensions/ql-vscode/src/view/results/AlertTableTruncatedMessage.tsx @@ -2,7 +2,9 @@ interface Props { numTruncatedResults: number; } -export function AlertTableTruncatedMessage(props: Props): JSX.Element | null { +export function AlertTableTruncatedMessage( + props: Props, +): React.JSX.Element | null { if (props.numTruncatedResults === 0) { return null; } diff --git a/extensions/ql-vscode/src/view/results/EmptyQueryResultsMessage.tsx b/extensions/ql-vscode/src/view/results/EmptyQueryResultsMessage.tsx index 93107ce41..da58e2d3f 100644 --- a/extensions/ql-vscode/src/view/results/EmptyQueryResultsMessage.tsx +++ b/extensions/ql-vscode/src/view/results/EmptyQueryResultsMessage.tsx @@ -18,7 +18,7 @@ const Container = styled.span` text-align: center; `; -export function EmptyQueryResultsMessage(): JSX.Element { +export function EmptyQueryResultsMessage(): React.JSX.Element { return ( <Root> <Container> diff --git a/extensions/ql-vscode/src/view/results/ProblemsViewCheckbox.tsx b/extensions/ql-vscode/src/view/results/ProblemsViewCheckbox.tsx index de0e0f2f9..59a1a2bab 100644 --- a/extensions/ql-vscode/src/view/results/ProblemsViewCheckbox.tsx +++ b/extensions/ql-vscode/src/view/results/ProblemsViewCheckbox.tsx @@ -10,7 +10,7 @@ interface Props { handleCheckboxChanged: (event: React.ChangeEvent<HTMLInputElement>) => void; } -export function ProblemsViewCheckbox(props: Props): JSX.Element | null { +export function ProblemsViewCheckbox(props: Props): React.JSX.Element | null { const { selectedTable, problemsViewSelected, handleCheckboxChanged } = props; if (selectedTable !== ALERTS_TABLE_NAME) { diff --git a/extensions/ql-vscode/src/view/results/RawTableValue.tsx b/extensions/ql-vscode/src/view/results/RawTableValue.tsx index e9afdfe42..7036b415d 100644 --- a/extensions/ql-vscode/src/view/results/RawTableValue.tsx +++ b/extensions/ql-vscode/src/view/results/RawTableValue.tsx @@ -13,7 +13,7 @@ export default function RawTableValue({ value, databaseUri, onSelected, -}: Props): JSX.Element { +}: Props): React.JSX.Element { switch (value.type) { case "boolean": return <span>{value.value.toString()}</span>; diff --git a/extensions/ql-vscode/src/view/results/ResultCount.tsx b/extensions/ql-vscode/src/view/results/ResultCount.tsx index 49ae94284..2311a652a 100644 --- a/extensions/ql-vscode/src/view/results/ResultCount.tsx +++ b/extensions/ql-vscode/src/view/results/ResultCount.tsx @@ -14,7 +14,7 @@ function getResultCount(resultSet: ResultSet): number { } } -export function ResultCount(props: Props): JSX.Element | null { +export function ResultCount(props: Props): React.JSX.Element | null { if (!props.resultSet) { return null; } diff --git a/extensions/ql-vscode/src/view/results/locations/ClickableLocation.tsx b/extensions/ql-vscode/src/view/results/locations/ClickableLocation.tsx index c073b10f8..29f62c72f 100644 --- a/extensions/ql-vscode/src/view/results/locations/ClickableLocation.tsx +++ b/extensions/ql-vscode/src/view/results/locations/ClickableLocation.tsx @@ -23,7 +23,7 @@ export function ClickableLocation({ label, databaseUri, onClick: onClick, -}: Props): JSX.Element { +}: Props): React.JSX.Element { const handleClick = useCallback( (e: React.MouseEvent) => { e.preventDefault(); diff --git a/extensions/ql-vscode/src/view/results/locations/Location.tsx b/extensions/ql-vscode/src/view/results/locations/Location.tsx index 8f45c194d..85a325aeb 100644 --- a/extensions/ql-vscode/src/view/results/locations/Location.tsx +++ b/extensions/ql-vscode/src/view/results/locations/Location.tsx @@ -22,7 +22,7 @@ export function Location({ databaseUri, title, onClick, -}: Props): JSX.Element { +}: Props): React.JSX.Element { const displayLabel = useMemo(() => convertNonPrintableChars(label), [label]); if (loc === undefined) { diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx index 67f19c79b..3a72c3548 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx @@ -51,7 +51,7 @@ export function VariantAnalysis({ variantAnalysis: initialVariantAnalysis, repoStates: initialRepoStates = [], repoResults: initialRepoResults = [], -}: VariantAnalysisProps): JSX.Element { +}: VariantAnalysisProps): React.JSX.Element { const [variantAnalysis, setVariantAnalysis] = useState< VariantAnalysisDomainModel | undefined >(initialVariantAnalysis); diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisHeader.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisHeader.tsx index d6191808b..bdd9b5e11 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisHeader.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysisHeader.tsx @@ -21,6 +21,7 @@ import { defaultFilterSortState, filterAndSortRepositoriesWithResults, } from "../../variant-analysis/shared/variant-analysis-filter-sort"; +import { ViewTitle } from "../common"; type VariantAnalysisHeaderProps = { variantAnalysis: VariantAnalysis; @@ -50,6 +51,29 @@ const Row = styled.div` align-items: center; `; +const QueryInfo = ({ + variantAnalysis, + onOpenQueryFileClick, + onViewQueryTextClick, +}: { + variantAnalysis: VariantAnalysis; + onOpenQueryFileClick: () => void; + onViewQueryTextClick: () => void; +}) => { + if (variantAnalysis.queries) { + return <ViewTitle>{variantAnalysis.queries?.count} queries</ViewTitle>; + } else { + return ( + <QueryDetails + queryName={variantAnalysis.query.name} + queryFileName={basename(variantAnalysis.query.filePath)} + onOpenQueryFileClick={onOpenQueryFileClick} + onViewQueryTextClick={onViewQueryTextClick} + /> + ); + } +}; + export const VariantAnalysisHeader = ({ variantAnalysis, repositoryStates, @@ -117,9 +141,8 @@ export const VariantAnalysisHeader = ({ return ( <Container> <Row> - <QueryDetails - queryName={variantAnalysis.query.name} - queryFileName={basename(variantAnalysis.query.filePath)} + <QueryInfo + variantAnalysis={variantAnalysis} onOpenQueryFileClick={onOpenQueryFileClick} onViewQueryTextClick={onViewQueryTextClick} /> diff --git a/extensions/ql-vscode/src/view/webview-definition.ts b/extensions/ql-vscode/src/view/webview-definition.ts index b23b8150c..ff9265d11 100644 --- a/extensions/ql-vscode/src/view/webview-definition.ts +++ b/extensions/ql-vscode/src/view/webview-definition.ts @@ -1,3 +1,3 @@ export type WebviewDefinition = { - component: JSX.Element; + component: React.JSX.Element; }; diff --git a/extensions/ql-vscode/supported_cli_versions.json b/extensions/ql-vscode/supported_cli_versions.json index d29e63e4d..318248126 100644 --- a/extensions/ql-vscode/supported_cli_versions.json +++ b/extensions/ql-vscode/supported_cli_versions.json @@ -1,5 +1,5 @@ [ - "v2.16.0", + "v2.16.1", "v2.15.5", "v2.14.6", "v2.13.5", diff --git a/extensions/ql-vscode/test/e2e/README.md b/extensions/ql-vscode/test/e2e/README.md new file mode 100644 index 000000000..c91c80283 --- /dev/null +++ b/extensions/ql-vscode/test/e2e/README.md @@ -0,0 +1,20 @@ +## VS Code CodeQL E2E Tests + +When running the tests locally on a mac a different processor has to be emulated, which makes everythign VERY slow. Therefore we need to add higher timeouts in the test, so that they pass locally. + +### How to use locally + +Setup + +- install playwright if you haven't yet (`npx playwright install`) +- go to the e2e test folder on your terminal +- make sure docker is running +- run `docker-compose build` +- run `docker-compose up` + +Run tests + +- run `npx playwright test --ui` from the e2e test folder to follow the test while it's running. This UI has a 'locator' tool with which elements on the test screen can be found +- use `npx playwright test --debug` to follow the test in real time and interact with the interface, e.g. press enter or input into fields, stop and start + +During the test elements are created in the docker volume, e.g. the downloaded database or query data. This might interfer with other tests or when running a test twice. If that happens restart your docker volume by using `docker-compose down -v` and `docker-compose up`. Sometimes already existing queries from former runs change the input the extension needs. diff --git a/extensions/ql-vscode/test/e2e/docker-compose.yml b/extensions/ql-vscode/test/e2e/docker-compose.yml new file mode 100644 index 000000000..3cece128c --- /dev/null +++ b/extensions/ql-vscode/test/e2e/docker-compose.yml @@ -0,0 +1,55 @@ +version: "3.8" + +services: + code-server: + build: + context: docker + dockerfile: Dockerfile + platform: linux/amd64 + container_name: code-server + user: "1000" + volumes: + - local-data:/home/coder/.local/share/code-server + - local-user-data:/home/coder/.local/share/code-server/User + - ./docker/config/config.yaml:/home/coder/.config/code-server/config.yaml + - ./docker/User/settings.json:/home/coder/.local/share/code-server/User/settings.json + - project-data:/home/coder/project + ports: + - 8080:8080 + restart: unless-stopped + depends_on: + code-server-init: + condition: service_completed_successfully + code-server-init: + build: + context: docker + dockerfile: Dockerfile + platform: linux/amd64 + user: "1000" + volumes: + - local-data:/home/coder/.local/share/code-server + - local-user-data:/home/coder/.local/share/code-server/User + - ./docker/config/config.yaml:/home/coder/.config/code-server/config.yaml + - ./docker/User/settings.json:/home/coder/.local/share/code-server/User/settings.json + - project-data:/home/coder/project + entrypoint: | + /usr/bin/entrypoint.sh --install-extension GitHub.vscode-codeql + restart: "no" + depends_on: + - files-init + files-init: + image: alpine:3.19.0 + restart: "no" + # Since we're not running the code-server container using the same user as our host user, + # we need to set the permissions on the mounted volumes to match the user inside the container. + entrypoint: | + /bin/sh -c "chown 1000:1000 /home/coder/.local/share/code-server /home/coder/.local/share/code-server/User /home/coder/project" + volumes: + - local-data:/home/coder/.local/share/code-server + - local-user-data:/home/coder/.local/share/code-server/User + - project-data:/home/coder/project + +volumes: + local-data: + local-user-data: + project-data: diff --git a/extensions/ql-vscode/test/e2e/docker/Dockerfile b/extensions/ql-vscode/test/e2e/docker/Dockerfile new file mode 100644 index 000000000..e9774eef2 --- /dev/null +++ b/extensions/ql-vscode/test/e2e/docker/Dockerfile @@ -0,0 +1,16 @@ +FROM codercom/code-server:4.20.0 + +USER root + +RUN apt-get update \ + && apt-get install -y \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +RUN wget -q -O /tmp/codeql.zip https://github.com/github/codeql-cli-binaries/releases/download/v2.15.5/codeql-linux64.zip \ + && unzip -q /tmp/codeql.zip -d /opt \ + && rm -rf /tmp/codeql.zip + +ENV PATH="/opt/codeql:${PATH}" + +USER 1000 diff --git a/extensions/ql-vscode/test/e2e/docker/User/settings.json b/extensions/ql-vscode/test/e2e/docker/User/settings.json new file mode 100644 index 000000000..8dfd44b28 --- /dev/null +++ b/extensions/ql-vscode/test/e2e/docker/User/settings.json @@ -0,0 +1,6 @@ +{ + "workbench.startupEditor": "none", + "security.workspace.trust.enabled": false, + "codeQL.cli.executablePath": "/opt/codeql/codeql", + "codeQL.telemetry.enableTelemetry": false +} diff --git a/extensions/ql-vscode/test/e2e/docker/config/config.yaml b/extensions/ql-vscode/test/e2e/docker/config/config.yaml new file mode 100644 index 000000000..e1340d4e9 --- /dev/null +++ b/extensions/ql-vscode/test/e2e/docker/config/config.yaml @@ -0,0 +1,6 @@ +bind-addr: 127.0.0.1:8080 +auth: none +cert: false +disable-workspace-trust: true +disable-telemetry: true +disable-update-check: true diff --git a/extensions/ql-vscode/test/e2e/playwright.config.ts b/extensions/ql-vscode/test/e2e/playwright.config.ts new file mode 100644 index 000000000..74b7d8195 --- /dev/null +++ b/extensions/ql-vscode/test/e2e/playwright.config.ts @@ -0,0 +1,36 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: ".", + + timeout: 5 * 60 * 1000, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: "http://localhost:8080", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/extensions/ql-vscode/test/e2e/run-query.spec.ts b/extensions/ql-vscode/test/e2e/run-query.spec.ts new file mode 100644 index 000000000..6007a279f --- /dev/null +++ b/extensions/ql-vscode/test/e2e/run-query.spec.ts @@ -0,0 +1,79 @@ +import { test, expect } from "@playwright/test"; + +test("run query and open it from history", async ({ page }) => { + await page.goto("/?folder=/home/coder/project"); + + await page.getByRole("tab", { name: "CodeQL" }).locator("a").click(); + + // decline extension telemetry + await page.getByRole("button", { name: "No", exact: true }).click({ + timeout: 60000, + }); + + await page.keyboard.press("Control+Shift+P"); + await page.keyboard.type("Create Query"); + await page.keyboard.press("Enter"); + + await page.getByLabel("JavaScript, javascript").locator("a").click({ + timeout: 60000, + }); + + // select folder for first query + await page + .getByText( + "Results0 SelectedPress 'Enter' to confirm your input or 'Escape' to cancelOK", + ) + .press("Enter"); + + // download database + await page + .getByRole("button", { name: "Download database" }) + .click({ timeout: 60000 }); + await page.getByPlaceholder("https://github.com/<owner>/<").press("Enter"); + await page + .locator("#list_id_3_0") + .getByText("javascript") + .click({ timeout: 60000 }); + + await page.keyboard.press("Control+Shift+P"); + await page.keyboard.type("Run Query on selected"); + await page.keyboard.press("Enter"); + + // check if results page is visible + await expect(page.getByText("CodeQL Query Results")).toBeVisible({ + timeout: 600000, + }); + + // wait for query history item to be finished + await expect( + page + .locator("#list_id_6_0") + .getByLabel("Hello world on d3/d3 -") + .locator("div") + .first(), + ).toBeVisible({ timeout: 60000 }); + + // close results page and open query from history + await page + .getByLabel("CodeQL Query Results, Editor Group") + .getByLabel("Close (Ctrl+F4)") + .click(); + + await expect( + page + .frameLocator(".webview") + .frameLocator('iframe[title="CodeQL Query Results"]') + .getByText("#selectalerts32 resultsShow"), + ).not.toBeVisible(); + + await page + .locator("#list_id_6_0") + .getByLabel("Hello world on d3/d3 -") + .locator("div") + .first() + .click(); + + await expect( + page.getByLabel("CodeQL Query Results", { exact: true }).locator("div"), + ).toBeVisible({ timeout: 60000 }); +}); diff --git a/extensions/ql-vscode/test/unit-tests/codeql-cli/distribution/releases-api-consumer.test.ts b/extensions/ql-vscode/test/unit-tests/codeql-cli/distribution/releases-api-consumer.test.ts index 62055657c..a4d29f272 100644 --- a/extensions/ql-vscode/test/unit-tests/codeql-cli/distribution/releases-api-consumer.test.ts +++ b/extensions/ql-vscode/test/unit-tests/codeql-cli/distribution/releases-api-consumer.test.ts @@ -115,7 +115,7 @@ describe("Releases API consumer", () => { await expect( consumer.getLatestRelease(new Range("5.*.*")), - ).rejects.toThrowError(); + ).rejects.toThrow(); }); it("picked release passes additional compatibility test if an additional compatibility test is specified", async () => { @@ -140,7 +140,7 @@ describe("Releases API consumer", () => { (asset) => asset.name === "otherExampleAsset.txt", ), ), - ).rejects.toThrowError(); + ).rejects.toThrow(); }); it("picked release is the most recent prerelease when includePrereleases is set", async () => { diff --git a/extensions/ql-vscode/test/unit-tests/common/ql.test.ts b/extensions/ql-vscode/test/unit-tests/common/ql.test.ts index 2f4a1cac9..9991ab5ab 100644 --- a/extensions/ql-vscode/test/unit-tests/common/ql.test.ts +++ b/extensions/ql-vscode/test/unit-tests/common/ql.test.ts @@ -2,9 +2,9 @@ import { join } from "path"; import { dirSync } from "tmp-promise"; import type { DirResult } from "tmp"; import { writeFile } from "fs-extra"; -import { getQlPackPath } from "../../../src/common/ql"; +import { getQlPackFilePath } from "../../../src/common/ql"; -describe("getQlPackPath", () => { +describe("getQlPackFilePath", () => { let tmpDir: DirResult; beforeEach(() => { @@ -22,14 +22,14 @@ describe("getQlPackPath", () => { it("should find a qlpack.yml when it exists", async () => { await writeFile(join(tmpDir.name, "qlpack.yml"), "name: test"); - const result = await getQlPackPath(tmpDir.name); + const result = await getQlPackFilePath(tmpDir.name); expect(result).toEqual(join(tmpDir.name, "qlpack.yml")); }); it("should find a codeql-pack.yml when it exists", async () => { await writeFile(join(tmpDir.name, "codeql-pack.yml"), "name: test"); - const result = await getQlPackPath(tmpDir.name); + const result = await getQlPackFilePath(tmpDir.name); expect(result).toEqual(join(tmpDir.name, "codeql-pack.yml")); }); @@ -37,12 +37,12 @@ describe("getQlPackPath", () => { await writeFile(join(tmpDir.name, "qlpack.yml"), "name: test"); await writeFile(join(tmpDir.name, "codeql-pack.yml"), "name: test"); - const result = await getQlPackPath(tmpDir.name); + const result = await getQlPackFilePath(tmpDir.name); expect(result).toEqual(join(tmpDir.name, "qlpack.yml")); }); it("should find nothing when it doesn't exist", async () => { - const result = await getQlPackPath(tmpDir.name); + const result = await getQlPackFilePath(tmpDir.name); expect(result).toEqual(undefined); }); }); diff --git a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-results-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-results-manager.test.ts index 5faec408f..5035aaf52 100644 --- a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-results-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-results-manager.test.ts @@ -154,7 +154,7 @@ describe(VariantAnalysisResultsManager.name, () => { async function* generateInParts() { const partLength = fileContents.length / 5; for (let i = 0; i < 5; i++) { - yield fileContents.slice(i * partLength, (i + 1) * partLength); + yield fileContents.subarray(i * partLength, (i + 1) * partLength); } } diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts index 97fe6065f..b223845fd 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts @@ -1,4 +1,4 @@ -import { CancellationTokenSource, commands, Uri, window } from "vscode"; +import { CancellationTokenSource, commands, window, Uri } from "vscode"; import { extLogger } from "../../../../src/common/logging/vscode"; import { setRemoteControllerRepo } from "../../../../src/config"; import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client"; @@ -26,6 +26,7 @@ import type { ExtensionPackMetadata } from "../../../../src/model-editor/extensi import type { QlPackLockFile } from "../../../../src/packaging/qlpack-lock-file"; //import { expect } from "@jest/globals"; import "../../../matchers/toExistInCodeQLPack"; +import type { QlPackDetails } from "../../../../src/variant-analysis/ql-pack-details"; describe("Variant Analysis Manager", () => { let cli: CodeQLCliServer; @@ -99,10 +100,18 @@ describe("Variant Analysis Manager", () => { }); it("should run a variant analysis that is part of a qlpack", async () => { - const fileUri = getFile("data-remote-qlpack/in-pack.ql"); + const filePath = getFileOrDir("data-remote-qlpack/in-pack.ql"); + const qlPackRootPath = getFileOrDir("data-remote-qlpack"); + const qlPackFilePath = getFileOrDir("data-remote-qlpack/qlpack.yml"); + const qlPackDetails: QlPackDetails = { + queryFiles: [filePath], + qlPackRootPath, + qlPackFilePath, + language: QueryLanguage.Javascript, + }; await variantAnalysisManager.runVariantAnalysis( - [fileUri], + qlPackDetails, progress, cancellationTokenSource.token, ); @@ -120,10 +129,17 @@ describe("Variant Analysis Manager", () => { }); it("should run a remote query that is not part of a qlpack", async () => { - const fileUri = getFile("data-remote-no-qlpack/in-pack.ql"); + const filePath = getFileOrDir("data-remote-no-qlpack/in-pack.ql"); + const qlPackRootPath = getFileOrDir("data-remote-no-qlpack"); + const qlPackDetails: QlPackDetails = { + queryFiles: [filePath], + qlPackRootPath, + qlPackFilePath: undefined, + language: QueryLanguage.Javascript, + }; await variantAnalysisManager.runVariantAnalysis( - [fileUri], + qlPackDetails, progress, cancellationTokenSource.token, ); @@ -141,10 +157,22 @@ describe("Variant Analysis Manager", () => { }); it("should run a remote query that is nested inside a qlpack", async () => { - const fileUri = getFile("data-remote-qlpack-nested/subfolder/in-pack.ql"); + const filePath = getFileOrDir( + "data-remote-qlpack-nested/subfolder/in-pack.ql", + ); + const qlPackRootPath = getFileOrDir("data-remote-qlpack-nested"); + const qlPackFilePath = getFileOrDir( + "data-remote-qlpack-nested/codeql-pack.yml", + ); + const qlPackDetails: QlPackDetails = { + queryFiles: [filePath], + qlPackRootPath, + qlPackFilePath, + language: QueryLanguage.Javascript, + }; await variantAnalysisManager.runVariantAnalysis( - [fileUri], + qlPackDetails, progress, cancellationTokenSource.token, ); @@ -162,10 +190,17 @@ describe("Variant Analysis Manager", () => { }); it("should cancel a run before uploading", async () => { - const fileUri = getFile("data-remote-no-qlpack/in-pack.ql"); + const filePath = getFileOrDir("data-remote-no-qlpack/in-pack.ql"); + const qlPackRootPath = getFileOrDir("data-remote-no-qlpack"); + const qlPackDetails: QlPackDetails = { + queryFiles: [filePath], + qlPackRootPath, + qlPackFilePath: undefined, + language: QueryLanguage.Javascript, + }; const promise = variantAnalysisManager.runVariantAnalysis( - [fileUri], + qlPackDetails, progress, cancellationTokenSource.token, ); @@ -202,6 +237,8 @@ describe("Variant Analysis Manager", () => { it("should run a remote query that is part of a qlpack", async () => { await doVariantAnalysisTest({ queryPath: "data-remote-qlpack/in-pack.ql", + qlPackRootPath: "data-remote-qlpack", + qlPackFilePath: "data-remote-qlpack/qlpack.yml", expectedPackName: "github/remote-query-pack", filesThatExist: ["in-pack.ql", "lib.qll"], filesThatDoNotExist: [], @@ -212,6 +249,8 @@ describe("Variant Analysis Manager", () => { it("should run a remote query that is not part of a qlpack", async () => { await doVariantAnalysisTest({ queryPath: "data-remote-no-qlpack/in-pack.ql", + qlPackRootPath: "data-remote-no-qlpack", + qlPackFilePath: undefined, expectedPackName: "codeql-remote/query", filesThatExist: ["in-pack.ql"], filesThatDoNotExist: ["lib.qll", "not-in-pack.ql"], @@ -222,6 +261,8 @@ describe("Variant Analysis Manager", () => { it("should run a remote query that is nested inside a qlpack", async () => { await doVariantAnalysisTest({ queryPath: "data-remote-qlpack-nested/subfolder/in-pack.ql", + qlPackRootPath: "data-remote-qlpack-nested", + qlPackFilePath: "data-remote-qlpack-nested/codeql-pack.yml", expectedPackName: "github/remote-query-pack", filesThatExist: ["subfolder/in-pack.ql", "otherfolder/lib.qll"], filesThatDoNotExist: ["subfolder/not-in-pack.ql"], @@ -239,6 +280,8 @@ describe("Variant Analysis Manager", () => { await cli.setUseExtensionPacks(true); await doVariantAnalysisTest({ queryPath: "data-remote-qlpack-nested/subfolder/in-pack.ql", + qlPackRootPath: "data-remote-qlpack-nested", + qlPackFilePath: "data-remote-qlpack-nested/codeql-pack.yml", expectedPackName: "github/remote-query-pack", filesThatExist: [ "subfolder/in-pack.ql", @@ -275,16 +318,23 @@ describe("Variant Analysis Manager", () => { const queryToRun = "Security/CWE/CWE-020/ExternalAPIsUsedWithUntrustedData.ql"; - const extraQuery = "Telemetry/ExtractorInformation.ql"; + // Recent versions of the CLI don't preserve queries with extensible predicates in MRVA packs, + // because all the necessary info is in the `.packinfo` file. + const extraQueries = + (await cli.cliConstraints.preservesExtensiblePredicatesInMrvaPack()) + ? ["Telemetry/ExtractorInformation.ql"] + : []; + + const qlPackRootPath = join(process.env.TEST_CODEQL_PATH, "java/ql/src"); + const queryPath = join(qlPackRootPath, queryToRun); + const qlPackFilePath = join(qlPackRootPath, "qlpack.yml"); await doVariantAnalysisTest({ - queryPath: join( - process.env.TEST_CODEQL_PATH, - "java/ql/src", - queryToRun, - ), + queryPath, + qlPackRootPath, + qlPackFilePath, expectedPackName: "codeql/java-queries", - filesThatExist: [queryToRun, extraQuery], + filesThatExist: [queryToRun, ...extraQueries], filesThatDoNotExist: [], qlxFilesThatExist: [], dependenciesToCheck: ["codeql/java-all"], @@ -295,6 +345,8 @@ describe("Variant Analysis Manager", () => { async function doVariantAnalysisTest({ queryPath, + qlPackRootPath, + qlPackFilePath, expectedPackName, filesThatExist, qlxFilesThatExist, @@ -306,6 +358,8 @@ describe("Variant Analysis Manager", () => { checkVersion = true, }: { queryPath: string; + qlPackRootPath: string; + qlPackFilePath: string | undefined; expectedPackName: string; filesThatExist: string[]; qlxFilesThatExist: string[]; @@ -313,9 +367,16 @@ describe("Variant Analysis Manager", () => { dependenciesToCheck?: string[]; checkVersion?: boolean; }) { - const fileUri = getFile(queryPath); + const filePath = getFileOrDir(queryPath); + const qlPackDetails: QlPackDetails = { + queryFiles: [filePath], + qlPackRootPath: getFileOrDir(qlPackRootPath), + qlPackFilePath: qlPackFilePath && getFileOrDir(qlPackFilePath), + language: QueryLanguage.Javascript, + }; + await variantAnalysisManager.runVariantAnalysis( - [fileUri], + qlPackDetails, progress, cancellationTokenSource.token, ); @@ -324,7 +385,7 @@ describe("Variant Analysis Manager", () => { expect(executeCommandSpy).toHaveBeenCalledWith( "codeQL.monitorNewVariantAnalysis", expect.objectContaining({ - query: expect.objectContaining({ filePath: fileUri.fsPath }), + query: expect.objectContaining({ filePath }), }), ); @@ -390,16 +451,19 @@ describe("Variant Analysis Manager", () => { ); } - function getFile(file: string): Uri { - if (isAbsolute(file)) { - return Uri.file(file); + function getFileOrDir(path: string): string { + // Use `Uri.file(path).fsPath` to make sure the path is in the correct format for the OS (i.e. forward/backward slashes). + if (isAbsolute(path)) { + return Uri.file(path).fsPath; } else { - return Uri.file(join(baseDir, file)); + return Uri.file(join(baseDir, path)).fsPath; } } }); describe("runVariantAnalysisFromPublishedPack", () => { + // Temporarily disabling this until we add a way to receive multiple queries in the + // runVariantAnalysis function. it("should download pack for correct language and identify problem queries", async () => { const showQuickPickSpy = jest .spyOn(window, "showQuickPick") @@ -419,15 +483,17 @@ describe("Variant Analysis Manager", () => { expect(showQuickPickSpy).toHaveBeenCalledTimes(1); expect(runVariantAnalysisMock).toHaveBeenCalledTimes(1); - const queries: Uri[] = runVariantAnalysisMock.mock.calls[0][0]; + console.log(runVariantAnalysisMock.mock.calls[0][0]); + const queries: string[] = + runVariantAnalysisMock.mock.calls[0][0].queryFiles; // Should include queries. Just check that at least one known query exists. // It doesn't particularly matter which query we check for. expect( - queries.find((q) => q.fsPath.includes("PostMessageStar.ql")), + queries.find((q) => q.includes("PostMessageStar.ql")), ).toBeDefined(); // Should not include non-problem queries. expect( - queries.find((q) => q.fsPath.includes("LinesOfCode.ql")), + queries.find((q) => q.includes("LinesOfCode.ql")), ).not.toBeDefined(); }); }); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts index b98d018ca..04e555fd1 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts @@ -86,6 +86,14 @@ describe("Variant Analysis Submission Integration", () => { it("shows the error message", async () => { await showQlDocument("query.ql"); + // Select target language for your query + quickPickSpy.mockResolvedValueOnce( + mockedQuickPickItem({ + label: "JavaScript", + language: "javascript", + }), + ); + await commandManager.execute("codeQL.runVariantAnalysis"); expect(showErrorMessageSpy).toHaveBeenCalledWith( diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-queries/local-databases.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-queries/local-databases.test.ts index f86543d62..1fdfa67db 100644 --- a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-queries/local-databases.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-queries/local-databases.test.ts @@ -296,9 +296,7 @@ describe("local databases", () => { Uri.parse("file:/sourceArchive-uri/"), ); (db as any).contents.sourceArchiveUri = undefined; - expect(() => db.resolveSourceFile("abc")).toThrowError( - "Scheme is missing", - ); + expect(() => db.resolveSourceFile("abc")).toThrow("Scheme is missing"); }); it("should fail to resolve when not a file uri", () => { @@ -308,7 +306,7 @@ describe("local databases", () => { Uri.parse("file:/sourceArchive-uri/"), ); (db as any).contents.sourceArchiveUri = undefined; - expect(() => db.resolveSourceFile("http://abc")).toThrowError( + expect(() => db.resolveSourceFile("http://abc")).toThrow( "Invalid uri scheme", ); }); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/databases/github-databases/api.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/databases/github-databases/api.test.ts index e18b26a5a..f5dbadd6c 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/databases/github-databases/api.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/databases/github-databases/api.test.ts @@ -123,7 +123,7 @@ describe("listDatabases", () => { it("throws an error", async () => { await expect( listDatabases(owner, repo, credentials, config), - ).rejects.toThrowError("Not found"); + ).rejects.toThrow("Not found"); }); }); @@ -150,7 +150,7 @@ describe("listDatabases", () => { it("throws an error", async () => { await expect( listDatabases(owner, repo, credentials, config), - ).rejects.toThrowError("Internal server error"); + ).rejects.toThrow("Internal server error"); }); }); }); @@ -199,7 +199,7 @@ describe("listDatabases", () => { it("throws an error", async () => { await expect( listDatabases(owner, repo, credentials, config), - ).rejects.toThrowError("Internal server error"); + ).rejects.toThrow("Internal server error"); expect(mockListCodeqlDatabases).not.toHaveBeenCalled(); }); }); @@ -270,7 +270,7 @@ describe("listDatabases", () => { it("throws an error", async () => { await expect( listDatabases(owner, repo, credentials, config), - ).rejects.toThrowError("Not found"); + ).rejects.toThrow("Not found"); }); }); @@ -297,7 +297,7 @@ describe("listDatabases", () => { it("throws an error", async () => { await expect( listDatabases(owner, repo, credentials, config), - ).rejects.toThrowError("Internal server error"); + ).rejects.toThrow("Internal server error"); }); }); }); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/databases/local-databases-ui.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/databases/local-databases-ui.test.ts index 05c324651..1d6020787 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/databases/local-databases-ui.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/databases/local-databases-ui.test.ts @@ -7,14 +7,42 @@ import { createFileSync, pathExistsSync, } from "fs-extra"; -import { Uri } from "vscode"; +import { CancellationTokenSource, Uri, window } from "vscode"; + +import type { + DatabaseImportQuickPickItems, + DatabaseQuickPickItem, + DatabaseSelectionQuickPickItem, +} from "../../../../src/databases/local-databases-ui"; import { DatabaseUI } from "../../../../src/databases/local-databases-ui"; import { testDisposeHandler } from "../../test-dispose-handler"; import { createMockApp } from "../../../__mocks__/appMock"; import { QueryLanguage } from "../../../../src/common/query-language"; +import { mockedQuickPickItem, mockedObject } from "../../utils/mocking.helpers"; describe("local-databases-ui", () => { + const storageDir = dirSync({ unsafeCleanup: true }).name; + const db1 = createDatabase(storageDir, "db1-imported", QueryLanguage.Cpp); + const db2 = createDatabase(storageDir, "db2-notimported", QueryLanguage.Cpp); + const db3 = createDatabase(storageDir, "db3-invalidlanguage", "hucairz"); + + // these two should be deleted + const db4 = createDatabase( + storageDir, + "db2-notimported-with-db-info", + QueryLanguage.Cpp, + ".dbinfo", + ); + const db5 = createDatabase( + storageDir, + "db2-notimported-with-codeql-database.yml", + QueryLanguage.Cpp, + "codeql-database.yml", + ); + + const app = createMockApp({}); + describe("fixDbUri", () => { const fixDbUri = (DatabaseUI.prototype as any).fixDbUri; it("should choose current directory normally", async () => { @@ -64,30 +92,6 @@ describe("local-databases-ui", () => { }); it("should delete orphaned databases", async () => { - const storageDir = dirSync({ unsafeCleanup: true }).name; - const db1 = createDatabase(storageDir, "db1-imported", QueryLanguage.Cpp); - const db2 = createDatabase( - storageDir, - "db2-notimported", - QueryLanguage.Cpp, - ); - const db3 = createDatabase(storageDir, "db3-invalidlanguage", "hucairz"); - - // these two should be deleted - const db4 = createDatabase( - storageDir, - "db2-notimported-with-db-info", - QueryLanguage.Cpp, - ".dbinfo", - ); - const db5 = createDatabase( - storageDir, - "db2-notimported-with-codeql-database.yml", - QueryLanguage.Cpp, - "codeql-database.yml", - ); - - const app = createMockApp({}); const databaseUI = new DatabaseUI( app, { @@ -98,6 +102,7 @@ describe("local-databases-ui", () => { onDidChangeCurrentDatabaseItem: () => { /**/ }, + setCurrentDatabaseItem: () => {}, } as any, { onLanguageContextChanged: () => { @@ -108,7 +113,6 @@ describe("local-databases-ui", () => { storageDir, storageDir, ); - await databaseUI.handleRemoveOrphanedDatabases(); expect(pathExistsSync(db1)).toBe(true); @@ -121,6 +125,130 @@ describe("local-databases-ui", () => { databaseUI.dispose(testDisposeHandler); }); + describe("getDatabaseItem", () => { + const progress = jest.fn(); + const token = new CancellationTokenSource().token; + describe("when there is a current database", () => { + const databaseUI = new DatabaseUI( + app, + { + databaseItems: [{ databaseUri: Uri.file(db1) }], + onDidChangeDatabaseItem: () => { + /**/ + }, + onDidChangeCurrentDatabaseItem: () => { + /**/ + }, + setCurrentDatabaseItem: () => {}, + currentDatabaseItem: { databaseUri: Uri.file(db1) }, + } as any, + { + onLanguageContextChanged: () => { + /**/ + }, + } as any, + {} as any, + storageDir, + storageDir, + ); + + it("should return current database", async () => { + const databaseItem = await databaseUI.getDatabaseItem(progress, token); + + expect(databaseItem).toEqual({ databaseUri: Uri.file(db1) }); + }); + }); + + describe("when there is no current database", () => { + const databaseManager = { + databaseItems: [ + { databaseUri: Uri.file(db1) }, + { databaseUri: Uri.file(db2) }, + ], + onDidChangeDatabaseItem: () => { + /**/ + }, + onDidChangeCurrentDatabaseItem: () => { + /**/ + }, + setCurrentDatabaseItem: () => {}, + currentDatabaseItem: undefined, + } as any; + + const databaseUI = new DatabaseUI( + app, + databaseManager, + { + onLanguageContextChanged: () => { + /**/ + }, + } as any, + {} as any, + storageDir, + storageDir, + ); + + it("should prompt for a database and select existing one", async () => { + const showQuickPickSpy = jest + .spyOn(window, "showQuickPick") + .mockResolvedValueOnce( + mockedQuickPickItem( + mockedObject<DatabaseSelectionQuickPickItem>({ + databaseKind: "existing", + }), + ), + ) + .mockResolvedValueOnce( + mockedQuickPickItem( + mockedObject<DatabaseQuickPickItem>({ + databaseItem: { databaseUri: Uri.file(db2) }, + }), + ), + ); + + const setCurrentDatabaseItemSpy = jest.spyOn( + databaseManager, + "setCurrentDatabaseItem", + ); + + await databaseUI.getDatabaseItem(progress, token); + + expect(showQuickPickSpy).toHaveBeenCalledTimes(2); + expect(setCurrentDatabaseItemSpy).toHaveBeenCalledWith({ + databaseUri: Uri.file(db2), + }); + }); + + it("should prompt for a database and import a new one", async () => { + const showQuickPickSpy = jest + .spyOn(window, "showQuickPick") + .mockResolvedValueOnce( + mockedQuickPickItem( + mockedObject<DatabaseSelectionQuickPickItem>({ + databaseKind: "new", + }), + ), + ) + .mockResolvedValueOnce( + mockedQuickPickItem( + mockedObject<DatabaseImportQuickPickItems>({ + importType: "github", + }), + ), + ); + + const handleChooseDatabaseGithubSpy = jest + .spyOn(databaseUI as any, "handleChooseDatabaseGithub") + .mockResolvedValue(undefined); + + await databaseUI.getDatabaseItem(progress, token); + + expect(showQuickPickSpy).toHaveBeenCalledTimes(2); + expect(handleChooseDatabaseGithubSpy).toHaveBeenCalledTimes(1); + }); + }); + }); + function createDatabase( storageDir: string, dbName: string, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/local-queries/query-resolver.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/local-queries/query-resolver.test.ts index 9e4dec27b..20114aac6 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/local-queries/query-resolver.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/local-queries/query-resolver.test.ts @@ -112,7 +112,7 @@ describe("resolveQueries", () => { "tags contain": ["ide-contextual-queries/print-ast"], }, ), - ).rejects.toThrowError( + ).rejects.toThrow( 'No my query queries (kind "graph", tagged "ide-contextual-queries/print-ast") could be found in the current library path (tried searching the following packs: my-qlpack). Try upgrading the CodeQL libraries. If that doesn\'t work, then my query queries are not yet available for this language.', ); }); diff --git a/extensions/ql-vscode/tsconfig.json b/extensions/ql-vscode/tsconfig.json index 19759b3dc..93f7cb0f2 100644 --- a/extensions/ql-vscode/tsconfig.json +++ b/extensions/ql-vscode/tsconfig.json @@ -24,5 +24,5 @@ "noEmit": true }, "include": ["src/**/*.ts"], - "exclude": ["node_modules", "test", "**/view"] + "exclude": ["node_modules", "*.config.ts", "test", "**/view"] }