diff --git a/.github/codeql/queries/unique-command-use.ql b/.github/codeql/queries/unique-command-use.ql index 9602bf397..7c1d9e196 100644 --- a/.github/codeql/queries/unique-command-use.ql +++ b/.github/codeql/queries/unique-command-use.ql @@ -72,6 +72,20 @@ override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() } } + + /** + * A usage of a command from the typescript code, by calling `CommandManager.execute`. + */ + class CommandUsageCommandManagerMethodCallExpr extends CommandUsage, MethodCallExpr { + CommandUsageCommandManagerMethodCallExpr() { + this.getCalleeName() = "execute" and + this.getReceiver().getType().unfold().(TypeReference).getTypeName().getName() = "CommandManager" and + this.getArgument(0).(StringLiteral).getValue().matches("%codeQL%") and + not this.getFile().getRelativePath().matches("extensions/ql-vscode/test/%") + } + + override string getCommandName() { result = this.getArgument(0).(StringLiteral).getValue() } + } /** * A usage of a command from any menu that isn't the command palette. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8719e252a..01d89b1e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ [fork]: https://github.com/github/vscode-codeql/fork [pr]: https://github.com/github/vscode-codeql/compare -[style]: https://primer.style +[style]: https://github.com/microsoft/vscode-webview-ui-toolkit [code-of-conduct]: CODE_OF_CONDUCT.md Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. diff --git a/extensions/ql-vscode/CHANGELOG.md b/extensions/ql-vscode/CHANGELOG.md index 83c4cd4c8..56dcf5fc9 100644 --- a/extensions/ql-vscode/CHANGELOG.md +++ b/extensions/ql-vscode/CHANGELOG.md @@ -2,6 +2,11 @@ ## [UNRELEASED] +## 1.8.1 - 23 March 2023 + +- Show data flow paths of a variant analysis in a new tab. [#2172](https://github.com/github/vscode-codeql/pull/2172) & [#2182](https://github.com/github/vscode-codeql/pull/2182) +- Show labels of entities in exported CSV results. [#2170](https://github.com/github/vscode-codeql/pull/2170) + ## 1.8.0 - 9 March 2023 - Send telemetry about unhandled errors happening within the extension. [#2125](https://github.com/github/vscode-codeql/pull/2125) diff --git a/extensions/ql-vscode/package-lock.json b/extensions/ql-vscode/package-lock.json index a39a7990d..3a2dee1bd 100644 --- a/extensions/ql-vscode/package-lock.json +++ b/extensions/ql-vscode/package-lock.json @@ -1,19 +1,17 @@ { "name": "vscode-codeql", - "version": "1.8.1", + "version": "1.8.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-codeql", - "version": "1.8.1", + "version": "1.8.2", "hasInstallScript": true, "license": "MIT", "dependencies": { "@octokit/plugin-retry": "^3.0.9", "@octokit/rest": "^19.0.4", - "@primer/octicons-react": "^17.6.0", - "@primer/react": "^35.0.0", "@vscode/codicons": "^0.0.31", "@vscode/webview-ui-toolkit": "^1.0.1", "ajv": "^8.11.0", @@ -23,7 +21,7 @@ "d3": "^7.6.1", "d3-graphviz": "^5.0.2", "fs-extra": "^10.0.1", - "glob-promise": "^4.2.2", + "glob-promise": "^6.0.2", "immutable": "^4.0.0", "js-yaml": "^4.1.0", "minimist": "~1.2.6", @@ -56,7 +54,7 @@ "@babel/core": "^7.18.13", "@babel/plugin-transform-modules-commonjs": "^7.18.6", "@faker-js/faker": "^7.5.0", - "@octokit/plugin-throttling": "^4.3.2", + "@octokit/plugin-throttling": "^5.0.1", "@storybook/addon-actions": "^6.5.10", "@storybook/addon-essentials": "^6.5.10", "@storybook/addon-interactions": "^6.5.10", @@ -91,6 +89,7 @@ "@types/semver": "~7.2.0", "@types/stream-chain": "~2.0.1", "@types/stream-json": "~1.7.1", + "@types/styled-components": "^5.1.11", "@types/tar-stream": "^2.2.2", "@types/through2": "^2.0.36", "@types/tmp": "^0.1.0", @@ -105,7 +104,6 @@ "@vscode/vsce": "^2.15.0", "ansi-colors": "^4.1.1", "applicationinsights": "^2.3.5", - "babel-loader": "^8.2.5", "cross-env": "^7.0.3", "css-loader": "~3.1.0", "del": "^6.0.0", @@ -130,12 +128,12 @@ "jest": "^29.0.3", "jest-environment-jsdom": "^29.0.3", "jest-runner-vscode": "^3.0.1", - "lint-staged": "~10.2.2", + "lint-staged": "~13.2.0", "mini-css-extract-plugin": "^2.6.1", "npm-run-all": "^4.1.5", "patch-package": "^6.5.0", "prettier": "^2.7.1", - "tar-stream": "^2.2.0", + "tar-stream": "^3.0.0", "through2": "^4.0.2", "ts-jest": "^29.0.1", "ts-json-schema-generator": "^1.1.2", @@ -143,8 +141,8 @@ "ts-node": "^10.7.0", "ts-protoc-gen": "^0.9.0", "typescript": "^4.5.5", - "webpack": "^5.62.2", - "webpack-cli": "^4.6.0" + "webpack": "^5.76.0", + "webpack-cli": "^5.0.1" }, "engines": { "node": "^16.13.0", @@ -2256,6 +2254,7 @@ "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -2526,17 +2525,17 @@ } }, "node_modules/@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", "dependencies": { - "@emotion/memoize": "0.7.4" + "@emotion/memoize": "^0.8.0" } }, "node_modules/@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" }, "node_modules/@emotion/stylis": { "version": "0.8.5", @@ -2679,26 +2678,6 @@ "integrity": "sha512-gIhjdJp/c2beaIWWIlsXdqXVRUz3r2BxBCpfz/F3JXHvSAQ1paMYjLH+maEATtENg+k5eLV7gA+9yPp762ieuw==", "dev": true }, - "node_modules/@github/combobox-nav": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.7.tgz", - "integrity": "sha512-Webx0W5iTpkk5Chy9dB/1BEUORQ0qrwui8HaaVBiy75W2VOJg96WTuKj1rXENAJ3XTMhdEF53bn0LYfvP0EKvg==" - }, - "node_modules/@github/markdown-toolbar-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.1.1.tgz", - "integrity": "sha512-J++rpd5H9baztabJQB82h26jtueOeBRSTqetk9Cri+Lj/s28ndu6Tovn0uHQaOKtBWDobFunk9b5pP5vcqt7cA==" - }, - "node_modules/@github/paste-markdown": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@github/paste-markdown/-/paste-markdown-1.4.2.tgz", - "integrity": "sha512-ZwSgPyo9nA6TRngXV0QnFT4e5ujeOGxRDWN2aa6qfimz2o2VOsJ9bFGuGvB723nvzq5z9zKr6JWGtvK7MSJj3w==" - }, - "node_modules/@github/relative-time-element": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.2.3.tgz", - "integrity": "sha512-M5NaacbpSmKUk6tYQ8r/LokSHeIb6D7rTTOqZ/htQX3b7KGKiDXI0t/WF7UDy58Go0NNfEahE9WzGEKcSjqfrA==" - }, "node_modules/@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", @@ -5074,11 +5053,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@lit-labs/react": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.1.1.tgz", - "integrity": "sha512-9TC+/ZWb6BJlWCyUr14FKFlaGnyKpeEDorufXozQgke/VoVrslUQNaL7nBmrAWdNrmzx5jWgi8lFmWwrxMjnlA==" - }, "node_modules/@mdx-js/mdx": { "version": "1.6.22", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz", @@ -5618,12 +5592,12 @@ } }, "node_modules/@octokit/plugin-throttling": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-4.3.2.tgz", - "integrity": "sha512-ZaCK599h3tzcoy0Jtdab95jgmD7X9iAk59E2E7hYKCAmnURaI4WpzwL9vckImilybUGrjY1JOWJapDs2N2D3vw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-5.0.1.tgz", + "integrity": "sha512-I4qxs7wYvYlFuY3PAUGWAVPhFXG3RwnvTiSr5Fu/Auz7bYhDLnzS2MjwV8nGLq/FPrWwYiweeZrI5yjs1YG4tQ==", "dev": true, "dependencies": { - "@octokit/types": "^8.0.0", + "@octokit/types": "^9.0.0", "bottleneck": "^2.15.3" }, "engines": { @@ -5634,18 +5608,18 @@ } }, "node_modules/@octokit/plugin-throttling/node_modules/@octokit/openapi-types": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz", - "integrity": "sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz", + "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==", "dev": true }, "node_modules/@octokit/plugin-throttling/node_modules/@octokit/types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz", - "integrity": "sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz", + "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==", "dev": true, "dependencies": { - "@octokit/openapi-types": "^14.0.0" + "@octokit/openapi-types": "^16.0.0" } }, "node_modules/@octokit/request": { @@ -6002,94 +5976,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/@primer/behaviors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.3.3.tgz", - "integrity": "sha512-iHMRuu8YWDJIdqCi1krx0cyFNeqszNKTOb0dXFu2wQ5BeIqxqPJLD7rjZ2Vjf/+YaPSbWuIQE1H6TaGMMsDfdA==" - }, - "node_modules/@primer/octicons-react": { - "version": "17.12.0", - "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-17.12.0.tgz", - "integrity": "sha512-GUjV6IT1Ck2lu2GVu1loR0e2dcX2e9aIVmX5rBdR7HJnRdYKY7XH+FnYj7y9sEmzUdNEDswxkefiQlfGrBXlzA==", - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/@primer/primitives": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.11.1.tgz", - "integrity": "sha512-Sdss4XG96nqBqrTAyg+RuFOj+U5wkICK8n2hafcyT+lpSlZoIwcbmhyFjmDy9f88FkhHe2q0uCpQ8PjKd8ILTQ==" - }, - "node_modules/@primer/react": { - "version": "35.21.0", - "resolved": "https://registry.npmjs.org/@primer/react/-/react-35.21.0.tgz", - "integrity": "sha512-TiJ7UdwVWA9zW9YtWfNcqe/3DQSpIu31kWTRDTGSfrJzkeFHUQgKOe/oIJAMIXtg2IPNjMzxZ8wVE8OztTpidw==", - "dependencies": { - "@github/combobox-nav": "^2.1.5", - "@github/markdown-toolbar-element": "^2.1.0", - "@github/paste-markdown": "^1.4.0", - "@github/relative-time-element": "^4.1.2", - "@lit-labs/react": "1.1.1", - "@primer/behaviors": "1.3.3", - "@primer/octicons-react": "^18.0.0", - "@primer/primitives": "7.11.1", - "@react-aria/ssr": "^3.1.0", - "@styled-system/css": "^5.1.5", - "@styled-system/props": "^5.1.5", - "@styled-system/theme-get": "^5.1.2", - "@types/styled-components": "^5.1.11", - "@types/styled-system": "^5.1.12", - "@types/styled-system__css": "^5.0.16", - "@types/styled-system__theme-get": "^5.0.1", - "classnames": "^2.3.1", - "color2k": "^2.0.0", - "deepmerge": "^4.2.2", - "focus-visible": "^5.2.0", - "fzy.js": "0.4.1", - "history": "^5.0.0", - "react-intersection-observer": "9.4.1", - "styled-system": "^5.1.5" - }, - "engines": { - "node": ">=12", - "npm": ">=7" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0", - "styled-components": "4.x || 5.x" - } - }, - "node_modules/@primer/react/node_modules/@primer/octicons-react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-18.2.0.tgz", - "integrity": "sha512-taT0l99qztqU9NnNo0HCutm3mEjBmXcaqWlmCLvU2MrKVrMZF3HuwaguPhNl/rNk8+mpgtDSTTDPBlpI0MtPPA==", - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/@primer/react/node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "node_modules/@react-aria/ssr": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.1.0.tgz", - "integrity": "sha512-RxqQKmE8sO7TGdrcSlHTcVzMP450hqowtBSd2bBS9oPlcokVkaGq28c3Rwa8ty5ctw4EBCjXqjP7xdcKMGDzug==", - "dependencies": { - "@babel/runtime": "^7.6.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1" - } - }, "node_modules/@sinclair/typebox": { "version": "0.24.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.41.tgz", @@ -12560,124 +12446,6 @@ "node": ">=8" } }, - "node_modules/@styled-system/background": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz", - "integrity": "sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/border": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz", - "integrity": "sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/color": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz", - "integrity": "sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/core": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz", - "integrity": "sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==", - "dependencies": { - "object-assign": "^4.1.1" - } - }, - "node_modules/@styled-system/css": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz", - "integrity": "sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==" - }, - "node_modules/@styled-system/flexbox": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz", - "integrity": "sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/grid": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz", - "integrity": "sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/layout": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz", - "integrity": "sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/position": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz", - "integrity": "sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/props": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/props/-/props-5.1.5.tgz", - "integrity": "sha512-FXhbzq2KueZpGaHxaDm8dowIEWqIMcgsKs6tBl6Y6S0njG9vC8dBMI6WSLDnzMoSqIX3nSKHmOmpzpoihdDewg==", - "dependencies": { - "styled-system": "^5.1.5" - } - }, - "node_modules/@styled-system/shadow": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz", - "integrity": "sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/space": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz", - "integrity": "sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/theme-get": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/theme-get/-/theme-get-5.1.2.tgz", - "integrity": "sha512-afAYdRqrKfNIbVgmn/2Qet1HabxmpRnzhFwttbGr6F/mJ4RDS/Cmn+KHwHvNXangQsWw/5TfjpWV+rgcqqIcJQ==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/typography": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz", - "integrity": "sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/variant": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz", - "integrity": "sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==", - "dependencies": { - "@styled-system/core": "^5.1.2", - "@styled-system/css": "^5.1.5" - } - }, "node_modules/@testing-library/dom": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz", @@ -13473,6 +13241,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, "dependencies": { "@types/minimatch": "*", "@types/node": "*" @@ -13546,6 +13315,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, "dependencies": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -13712,9 +13482,9 @@ } }, "node_modules/@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" }, "node_modules/@types/ms": { "version": "0.7.31", @@ -13785,7 +13555,8 @@ "node_modules/@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true }, "node_modules/@types/qs": { "version": "6.9.7", @@ -13797,6 +13568,7 @@ "version": "18.0.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -13821,7 +13593,8 @@ "node_modules/@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true }, "node_modules/@types/semver": { "version": "7.2.0", @@ -13872,36 +13645,16 @@ } }, "node_modules/@types/styled-components": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.11.tgz", - "integrity": "sha512-u8g3bSw9KUiZY+S++gh+LlURGraqBe3MC5I5dygrNjGDHWWQfsmZZRTJ9K9oHU2CqWtxChWmJkDI/gp+TZPQMw==", + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, "dependencies": { "@types/hoist-non-react-statics": "*", "@types/react": "*", "csstype": "^3.0.2" } }, - "node_modules/@types/styled-system": { - "version": "5.1.12", - "resolved": "https://registry.npmjs.org/@types/styled-system/-/styled-system-5.1.12.tgz", - "integrity": "sha512-7x4BYKKfK9QewfsFC2x5r9BK/OrfX+JF/1P21jKPMHruawDw9gvG7bTZgTVk6YkzDO2JUlsk4i8hdiAepAhD0g==", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/styled-system__css": { - "version": "5.0.16", - "resolved": "https://registry.npmjs.org/@types/styled-system__css/-/styled-system__css-5.0.16.tgz", - "integrity": "sha512-Cji5miCIpR27m8yzH6y3cLU6106N4GVyPgUhBQ4nL7VxgoeAtRdAriKdGTnRgJzSpT3HyB7h5G//cDWOl0M1jQ==", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/styled-system__theme-get": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/styled-system__theme-get/-/styled-system__theme-get-5.0.1.tgz", - "integrity": "sha512-+i4VZ5wuYKMU8oKPmUlzc9r2RhpSNOK061Khtrr7X0sOQEcIyhUtrDusuMkp5ZR3D05Xopn3zybTPyUSQkKGAA==" - }, "node_modules/@types/tapable": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", @@ -14995,34 +14748,42 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.2.tgz", - "integrity": "sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.3.tgz", - "integrity": "sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", "dev": true, - "dependencies": { - "envinfo": "^7.7.3" + "engines": { + "node": ">=14.15.0" }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.1.tgz", - "integrity": "sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", "dev": true, + "engines": { + "node": ">=14.15.0" + }, "peerDependencies": { - "webpack-cli": "4.x.x" + "webpack": "5.x.x", + "webpack-cli": "5.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -15068,6 +14829,18 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -15491,6 +15264,21 @@ "node": ">= 6" } }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -15883,6 +15671,15 @@ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", "dev": true }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", @@ -16037,6 +15834,12 @@ "typed-rest-client": "^1.8.4" } }, + "node_modules/b4a": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.2.tgz", + "integrity": "sha512-YFqjbZ8iqX/wWJVmF1SSOB5TYDwsPd/sZzhSdu2PskElf55PjEe+0MhsEPgoa5eTK1VS/WqJMz9qwIFwZta+3g==", + "dev": true + }, "node_modules/babel-jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", @@ -18234,70 +18037,69 @@ } }, "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", "dev": true, "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/cli-truncate/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-truncate/node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/cli-truncate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" + "node": ">=12" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/cli-truncate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "node_modules/cli-truncate/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "node_modules/cli-truncate/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/cli-width": { @@ -18529,11 +18331,6 @@ "color-support": "bin.js" } }, - "node_modules/color2k": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz", - "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==" - }, "node_modules/colorette": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", @@ -18563,12 +18360,12 @@ } }, "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", + "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", "dev": true, "engines": { - "node": ">= 6" + "node": ">=14" } }, "node_modules/common-path-prefix": { @@ -19432,7 +19229,8 @@ "node_modules/csstype": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", + "dev": true }, "node_modules/currently-unhandled": { "version": "0.4.1", @@ -20026,6 +19824,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -20355,18 +20154,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/detect-package-manager/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/detect-package-manager/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -20701,6 +20488,12 @@ "object.defaults": "^1.1.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/easy-stack": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", @@ -20824,18 +20617,6 @@ "node": ">=6.9.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -20846,9 +20627,9 @@ } }, "node_modules/envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -22528,6 +22309,15 @@ "node": ">=4.0.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -22558,23 +22348,26 @@ "dev": true }, "node_modules/execa": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", - "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", + "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", "dev": true, "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=10" + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/execa/node_modules/cross-spawn": { @@ -22591,6 +22384,72 @@ "node": ">= 8" } }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/execa/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -22621,6 +22480,18 @@ "node": ">=8" } }, + "node_modules/execa/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/execa/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -23672,11 +23543,6 @@ "node": ">=10" } }, - "node_modules/focus-visible": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/focus-visible/-/focus-visible-5.2.0.tgz", - "integrity": "sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==" - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -24089,11 +23955,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fzy.js": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/fzy.js/-/fzy.js-0.4.1.tgz", - "integrity": "sha512-4sPVXf+9oGhzg2tYzgWe4hgAY0wEbkqeuKVEgdnqX8S8VcLosQsDjb0jV+f5uoQlf8INWId1w0IGoufAoik1TA==" - }, "node_modules/gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -24185,12 +24046,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true - }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -24211,25 +24066,15 @@ } }, "node_modules/get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=8" - } - }, - "node_modules/get-stream/node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-symbol-description": { @@ -24299,21 +24144,30 @@ } }, "node_modules/glob-promise": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz", - "integrity": "sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.2.tgz", + "integrity": "sha512-Ni2aDyD1ekD6x8/+K4hDriRDbzzfuK4yKpqSymJ4P7IxbtARiOOuU+k40kbHM0sLIlbf1Qh0qdMkAHMZYE6XJQ==", "dependencies": { - "@types/glob": "^7.1.3" + "@types/glob": "^8.0.0" }, "engines": { - "node": ">=12" + "node": ">=16" }, "funding": { "type": "individual", "url": "https://github.com/sponsors/ahmadnassri" }, "peerDependencies": { - "glob": "^7.1.6" + "glob": "^8.0.3" + } + }, + "node_modules/glob-promise/node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" } }, "node_modules/glob-stream": { @@ -25209,14 +25063,6 @@ "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.1.2.tgz", "integrity": "sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA==" }, - "node_modules/history": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.0.0.tgz", - "integrity": "sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==", - "dependencies": { - "@babel/runtime": "^7.7.6" - } - }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -25477,12 +25323,12 @@ "dev": true }, "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.0.tgz", + "integrity": "sha512-zyzVyMjpGBX2+6cDVZeFPCdtOtdsxOeseRhB9tkQ6xXmGUNrcnBzdEKPy3VPNYz+4gy1oukVOXcrJCunSyc6QQ==", "dev": true, "engines": { - "node": ">=8.12.0" + "node": ">=14.18.0" } }, "node_modules/husky": { @@ -25524,9 +25370,23 @@ } }, "node_modules/ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/iferr": { "version": "0.1.5", @@ -26108,15 +25968,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", @@ -26189,15 +26040,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -26679,18 +26521,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/jest-changed-files/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-changed-files/node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -31344,6 +31174,15 @@ "node": ">= 0.8" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -31360,42 +31199,33 @@ } }, "node_modules/lint-staged": { - "version": "10.2.11", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.11.tgz", - "integrity": "sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.0.tgz", + "integrity": "sha512-GbyK5iWinax5Dfw5obm2g2ccUiZXNGtAS4mCbJ0Lv4rq6iEtfBSjOYdcbOtAIFtM114t0vdpViDDetjVTSd8Vw==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "cli-truncate": "2.1.0", - "commander": "^5.1.0", - "cosmiconfig": "^6.0.0", - "debug": "^4.1.1", - "dedent": "^0.7.0", - "enquirer": "^2.3.5", - "execa": "^4.0.1", - "listr2": "^2.1.0", - "log-symbols": "^4.0.0", - "micromatch": "^4.0.2", + "chalk": "5.2.0", + "cli-truncate": "^3.1.0", + "commander": "^10.0.0", + "debug": "^4.3.4", + "execa": "^7.0.0", + "lilconfig": "2.1.0", + "listr2": "^5.0.7", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", - "stringify-object": "^3.3.0" + "object-inspect": "^1.12.3", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.2.1" }, "bin": { "lint-staged": "bin/lint-staged.js" - } - }, - "node_modules/lint-staged/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "dependencies": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" }, "engines": { - "node": ">=8" + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" } }, "node_modules/lint-staged/node_modules/braces": { @@ -31411,43 +31241,32 @@ } }, "node_modules/lint-staged/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" - } - }, - "node_modules/lint-staged/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/lint-staged/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/lint-staged/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/lint-staged/node_modules/fill-range": { @@ -31462,15 +31281,6 @@ "node": ">=8" } }, - "node_modules/lint-staged/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/lint-staged/node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -31481,16 +31291,16 @@ } }, "node_modules/lint-staged/node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=8.6" } }, "node_modules/lint-staged/node_modules/ms": { @@ -31499,16 +31309,16 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/lint-staged/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "node_modules/lint-staged/node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "bin": { + "pidtree": "bin/pidtree.js" }, "engines": { - "node": ">=8" + "node": ">=0.10" } }, "node_modules/lint-staged/node_modules/to-regex-range": { @@ -31523,54 +31333,76 @@ "node": ">=8.0" } }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/listenercount": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" }, "node_modules/listr2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.2.0.tgz", - "integrity": "sha512-Q8qbd7rgmEwDo1nSyHaWQeztfGsdL6rb4uh7BA+Q80AZiDET5rVntiU1+13mu2ZTDVaBVbvAD1Db11rnu3l9sg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", + "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", "dev": true, "dependencies": { - "chalk": "^4.0.0", "cli-truncate": "^2.1.0", - "figures": "^3.2.0", - "indent-string": "^4.0.0", + "colorette": "^2.0.19", "log-update": "^4.0.0", "p-map": "^4.0.0", - "rxjs": "^6.5.5", - "through": "^2.3.8" + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=10.0.0" + "node": "^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } } }, "node_modules/listr2/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/listr2/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "node_modules/listr2/node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" }, "engines": { - "node": ">=10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/listr2/node_modules/color-convert": { @@ -31591,25 +31423,41 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/listr2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/listr2/node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/listr2/node_modules/supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/load-json-file": { @@ -31828,28 +31676,24 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/log-update/node_modules/ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" }, "engines": { "node": ">=8" - } - }, - "node_modules/log-update/node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/log-update/node_modules/color-convert": { @@ -31882,6 +31726,9 @@ }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/log-update/node_modules/wrap-ansi": { @@ -32898,14 +32745,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, - "node_modules/msw/node_modules/rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/msw/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -32917,11 +32756,6 @@ "node": ">=8" } }, - "node_modules/msw/node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, "node_modules/msw/node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -33454,6 +33288,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -33497,9 +33332,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -34622,15 +34457,6 @@ "node": ">=8" } }, - "node_modules/please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "dependencies": { - "semver-compare": "^1.0.0" - } - }, "node_modules/plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", @@ -35574,14 +35400,6 @@ "react": "^16.8.4 || ^17.0.0" } }, - "node_modules/react-intersection-observer": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.4.1.tgz", - "integrity": "sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==", - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -35754,7 +35572,8 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.15.0", @@ -36354,6 +36173,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -36435,17 +36260,18 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "node_modules/rxjs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", - "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", - "dev": true, + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -36660,12 +36486,6 @@ "node": ">=10" } }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, "node_modules/semver-greatest-satisfied-range": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", @@ -36965,6 +36785,46 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -37545,20 +37405,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -37726,13 +37572,13 @@ } }, "node_modules/styled-components": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz", - "integrity": "sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", + "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^0.8.8", + "@emotion/is-prop-valid": "^1.1.0", "@emotion/stylis": "^0.8.4", "@emotion/unitless": "^0.7.4", "babel-plugin-styled-components": ">= 1.12.0", @@ -37765,26 +37611,6 @@ "node": ">=4" } }, - "node_modules/styled-system": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz", - "integrity": "sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==", - "dependencies": { - "@styled-system/background": "^5.1.2", - "@styled-system/border": "^5.1.5", - "@styled-system/color": "^5.1.2", - "@styled-system/core": "^5.1.2", - "@styled-system/flexbox": "^5.1.2", - "@styled-system/grid": "^5.1.2", - "@styled-system/layout": "^5.1.2", - "@styled-system/position": "^5.1.2", - "@styled-system/shadow": "^5.1.2", - "@styled-system/space": "^5.1.2", - "@styled-system/typography": "^5.1.2", - "@styled-system/variant": "^5.1.5", - "object-assign": "^4.1.1" - } - }, "node_modules/supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -37912,10 +37738,25 @@ "once": "^1.3.1" } }, - "node_modules/tar-stream": { + "node_modules/tar-fs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -37927,17 +37768,65 @@ "node": ">=6" } }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/tar-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.0.0.tgz", + "integrity": "sha512-O6OfUKBbQOqAhh6owTWmA730J/yZCYcpmZ1DBj2YX51ZQrt7d7NgzrR+CnO9wP6nt/viWZW2XeXLavX3/ZEbEg==", + "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "b4a": "^1.6.1", + "bl": "^6.0.0", + "streamx": "^2.12.5" + } + }, + "node_modules/tar-stream/node_modules/bl": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.0.tgz", + "integrity": "sha512-Ik9BVIMdcWzSOCpzDv2XpQ4rJ4oZBuk3ck6MgiOv0EopdgtohN2uSCrrLlkH1Jf0KnpZZMBA3D0bUMbCdj/jgA==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "node_modules/tar-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/tar/node_modules/chownr": { @@ -39957,12 +39846,6 @@ "integrity": "sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==", "dev": true }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", @@ -40488,9 +40371,9 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "node_modules/webpack": { - "version": "5.73.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.73.0.tgz", - "integrity": "sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==", + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", + "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -40498,11 +40381,11 @@ "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.3", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -40515,7 +40398,7 @@ "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "bin": { @@ -40535,42 +40418,42 @@ } }, "node_modules/webpack-cli": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.6.0.tgz", - "integrity": "sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.2", - "@webpack-cli/info": "^1.2.3", - "@webpack-cli/serve": "^1.3.1", - "colorette": "^1.2.1", - "commander": "^7.0.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", + "colorette": "^2.0.14", + "commander": "^9.4.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "4.x.x || 5.x.x" + "webpack": "5.x.x" }, "peerDependenciesMeta": { "@webpack-cli/generators": { "optional": true }, - "@webpack-cli/migrate": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -40579,13 +40462,19 @@ } } }, + "node_modules/webpack-cli/node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "engines": { - "node": ">= 10" + "node": "^12.20.0 || >=14" } }, "node_modules/webpack-cli/node_modules/cross-spawn": { @@ -40602,57 +40491,13 @@ "node": ">= 8" } }, - "node_modules/webpack-cli/node_modules/execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/webpack-cli/node_modules/get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-cli/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/webpack-cli/node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, "node_modules/webpack-cli/node_modules/path-key": { @@ -40665,15 +40510,15 @@ } }, "node_modules/webpack-cli/node_modules/rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "dependencies": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" } }, "node_modules/webpack-cli/node_modules/shebang-command": { @@ -40887,9 +40732,9 @@ } }, "node_modules/webpack/node_modules/enhanced-resolve": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", - "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -42917,6 +42762,7 @@ "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -43139,17 +42985,17 @@ "dev": true }, "@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", "requires": { - "@emotion/memoize": "0.7.4" + "@emotion/memoize": "^0.8.0" } }, "@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" }, "@emotion/stylis": { "version": "0.8.5", @@ -43251,26 +43097,6 @@ "integrity": "sha512-gIhjdJp/c2beaIWWIlsXdqXVRUz3r2BxBCpfz/F3JXHvSAQ1paMYjLH+maEATtENg+k5eLV7gA+9yPp762ieuw==", "dev": true }, - "@github/combobox-nav": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.7.tgz", - "integrity": "sha512-Webx0W5iTpkk5Chy9dB/1BEUORQ0qrwui8HaaVBiy75W2VOJg96WTuKj1rXENAJ3XTMhdEF53bn0LYfvP0EKvg==" - }, - "@github/markdown-toolbar-element": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.1.1.tgz", - "integrity": "sha512-J++rpd5H9baztabJQB82h26jtueOeBRSTqetk9Cri+Lj/s28ndu6Tovn0uHQaOKtBWDobFunk9b5pP5vcqt7cA==" - }, - "@github/paste-markdown": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@github/paste-markdown/-/paste-markdown-1.4.2.tgz", - "integrity": "sha512-ZwSgPyo9nA6TRngXV0QnFT4e5ujeOGxRDWN2aa6qfimz2o2VOsJ9bFGuGvB723nvzq5z9zKr6JWGtvK7MSJj3w==" - }, - "@github/relative-time-element": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.2.3.tgz", - "integrity": "sha512-M5NaacbpSmKUk6tYQ8r/LokSHeIb6D7rTTOqZ/htQX3b7KGKiDXI0t/WF7UDy58Go0NNfEahE9WzGEKcSjqfrA==" - }, "@gulp-sourcemaps/identity-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", @@ -45104,11 +44930,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "@lit-labs/react": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.1.1.tgz", - "integrity": "sha512-9TC+/ZWb6BJlWCyUr14FKFlaGnyKpeEDorufXozQgke/VoVrslUQNaL7nBmrAWdNrmzx5jWgi8lFmWwrxMjnlA==" - }, "@mdx-js/mdx": { "version": "1.6.22", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz", @@ -45549,28 +45370,28 @@ } }, "@octokit/plugin-throttling": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-4.3.2.tgz", - "integrity": "sha512-ZaCK599h3tzcoy0Jtdab95jgmD7X9iAk59E2E7hYKCAmnURaI4WpzwL9vckImilybUGrjY1JOWJapDs2N2D3vw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-5.0.1.tgz", + "integrity": "sha512-I4qxs7wYvYlFuY3PAUGWAVPhFXG3RwnvTiSr5Fu/Auz7bYhDLnzS2MjwV8nGLq/FPrWwYiweeZrI5yjs1YG4tQ==", "dev": true, "requires": { - "@octokit/types": "^8.0.0", + "@octokit/types": "^9.0.0", "bottleneck": "^2.15.3" }, "dependencies": { "@octokit/openapi-types": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz", - "integrity": "sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz", + "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==", "dev": true }, "@octokit/types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz", - "integrity": "sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz", + "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==", "dev": true, "requires": { - "@octokit/openapi-types": "^14.0.0" + "@octokit/openapi-types": "^16.0.0" } } } @@ -45816,72 +45637,6 @@ } } }, - "@primer/behaviors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.3.3.tgz", - "integrity": "sha512-iHMRuu8YWDJIdqCi1krx0cyFNeqszNKTOb0dXFu2wQ5BeIqxqPJLD7rjZ2Vjf/+YaPSbWuIQE1H6TaGMMsDfdA==" - }, - "@primer/octicons-react": { - "version": "17.12.0", - "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-17.12.0.tgz", - "integrity": "sha512-GUjV6IT1Ck2lu2GVu1loR0e2dcX2e9aIVmX5rBdR7HJnRdYKY7XH+FnYj7y9sEmzUdNEDswxkefiQlfGrBXlzA==" - }, - "@primer/primitives": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.11.1.tgz", - "integrity": "sha512-Sdss4XG96nqBqrTAyg+RuFOj+U5wkICK8n2hafcyT+lpSlZoIwcbmhyFjmDy9f88FkhHe2q0uCpQ8PjKd8ILTQ==" - }, - "@primer/react": { - "version": "35.21.0", - "resolved": "https://registry.npmjs.org/@primer/react/-/react-35.21.0.tgz", - "integrity": "sha512-TiJ7UdwVWA9zW9YtWfNcqe/3DQSpIu31kWTRDTGSfrJzkeFHUQgKOe/oIJAMIXtg2IPNjMzxZ8wVE8OztTpidw==", - "requires": { - "@github/combobox-nav": "^2.1.5", - "@github/markdown-toolbar-element": "^2.1.0", - "@github/paste-markdown": "^1.4.0", - "@github/relative-time-element": "^4.1.2", - "@lit-labs/react": "1.1.1", - "@primer/behaviors": "1.3.3", - "@primer/octicons-react": "^18.0.0", - "@primer/primitives": "7.11.1", - "@react-aria/ssr": "^3.1.0", - "@styled-system/css": "^5.1.5", - "@styled-system/props": "^5.1.5", - "@styled-system/theme-get": "^5.1.2", - "@types/styled-components": "^5.1.11", - "@types/styled-system": "^5.1.12", - "@types/styled-system__css": "^5.0.16", - "@types/styled-system__theme-get": "^5.0.1", - "classnames": "^2.3.1", - "color2k": "^2.0.0", - "deepmerge": "^4.2.2", - "focus-visible": "^5.2.0", - "fzy.js": "0.4.1", - "history": "^5.0.0", - "react-intersection-observer": "9.4.1", - "styled-system": "^5.1.5" - }, - "dependencies": { - "@primer/octicons-react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-18.2.0.tgz", - "integrity": "sha512-taT0l99qztqU9NnNo0HCutm3mEjBmXcaqWlmCLvU2MrKVrMZF3HuwaguPhNl/rNk8+mpgtDSTTDPBlpI0MtPPA==" - }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - } - } - }, - "@react-aria/ssr": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.1.0.tgz", - "integrity": "sha512-RxqQKmE8sO7TGdrcSlHTcVzMP450hqowtBSd2bBS9oPlcokVkaGq28c3Rwa8ty5ctw4EBCjXqjP7xdcKMGDzug==", - "requires": { - "@babel/runtime": "^7.6.2" - } - }, "@sinclair/typebox": { "version": "0.24.41", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.41.tgz", @@ -50687,124 +50442,6 @@ } } }, - "@styled-system/background": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz", - "integrity": "sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/border": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz", - "integrity": "sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/color": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz", - "integrity": "sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/core": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz", - "integrity": "sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==", - "requires": { - "object-assign": "^4.1.1" - } - }, - "@styled-system/css": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz", - "integrity": "sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==" - }, - "@styled-system/flexbox": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz", - "integrity": "sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/grid": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz", - "integrity": "sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/layout": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz", - "integrity": "sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/position": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz", - "integrity": "sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/props": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/props/-/props-5.1.5.tgz", - "integrity": "sha512-FXhbzq2KueZpGaHxaDm8dowIEWqIMcgsKs6tBl6Y6S0njG9vC8dBMI6WSLDnzMoSqIX3nSKHmOmpzpoihdDewg==", - "requires": { - "styled-system": "^5.1.5" - } - }, - "@styled-system/shadow": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz", - "integrity": "sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/space": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz", - "integrity": "sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/theme-get": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/theme-get/-/theme-get-5.1.2.tgz", - "integrity": "sha512-afAYdRqrKfNIbVgmn/2Qet1HabxmpRnzhFwttbGr6F/mJ4RDS/Cmn+KHwHvNXangQsWw/5TfjpWV+rgcqqIcJQ==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/typography": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz", - "integrity": "sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==", - "requires": { - "@styled-system/core": "^5.1.2" - } - }, - "@styled-system/variant": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz", - "integrity": "sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==", - "requires": { - "@styled-system/core": "^5.1.2", - "@styled-system/css": "^5.1.5" - } - }, "@testing-library/dom": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.17.1.tgz", @@ -51516,6 +51153,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, "requires": { "@types/minimatch": "*", "@types/node": "*" @@ -51588,6 +51226,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dev": true, "requires": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" @@ -51740,9 +51379,9 @@ } }, "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" }, "@types/ms": { "version": "0.7.31", @@ -51812,7 +51451,8 @@ "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true }, "@types/qs": { "version": "6.9.7", @@ -51824,6 +51464,7 @@ "version": "18.0.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "dev": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -51848,7 +51489,8 @@ "@types/scheduler": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true }, "@types/semver": { "version": "7.2.0", @@ -51899,36 +51541,16 @@ } }, "@types/styled-components": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.11.tgz", - "integrity": "sha512-u8g3bSw9KUiZY+S++gh+LlURGraqBe3MC5I5dygrNjGDHWWQfsmZZRTJ9K9oHU2CqWtxChWmJkDI/gp+TZPQMw==", + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, "requires": { "@types/hoist-non-react-statics": "*", "@types/react": "*", "csstype": "^3.0.2" } }, - "@types/styled-system": { - "version": "5.1.12", - "resolved": "https://registry.npmjs.org/@types/styled-system/-/styled-system-5.1.12.tgz", - "integrity": "sha512-7x4BYKKfK9QewfsFC2x5r9BK/OrfX+JF/1P21jKPMHruawDw9gvG7bTZgTVk6YkzDO2JUlsk4i8hdiAepAhD0g==", - "requires": { - "csstype": "^3.0.2" - } - }, - "@types/styled-system__css": { - "version": "5.0.16", - "resolved": "https://registry.npmjs.org/@types/styled-system__css/-/styled-system__css-5.0.16.tgz", - "integrity": "sha512-Cji5miCIpR27m8yzH6y3cLU6106N4GVyPgUhBQ4nL7VxgoeAtRdAriKdGTnRgJzSpT3HyB7h5G//cDWOl0M1jQ==", - "requires": { - "csstype": "^3.0.2" - } - }, - "@types/styled-system__theme-get": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/styled-system__theme-get/-/styled-system__theme-get-5.0.1.tgz", - "integrity": "sha512-+i4VZ5wuYKMU8oKPmUlzc9r2RhpSNOK061Khtrr7X0sOQEcIyhUtrDusuMkp5ZR3D05Xopn3zybTPyUSQkKGAA==" - }, "@types/tapable": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", @@ -52805,24 +52427,21 @@ } }, "@webpack-cli/configtest": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.2.tgz", - "integrity": "sha512-3OBzV2fBGZ5TBfdW50cha1lHDVf9vlvRXnjpVbJBa20pSZQaSkMJZiwA8V2vD9ogyeXn8nU5s5A6mHyf5jhMzA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.0.1.tgz", + "integrity": "sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A==", "dev": true }, "@webpack-cli/info": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.3.tgz", - "integrity": "sha512-lLek3/T7u40lTqzCGpC6CAbY6+vXhdhmwFRxZLMnRm6/sIF/7qMpT8MocXCRQfz0JAh63wpbXLMnsQ5162WS7Q==", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true }, "@webpack-cli/serve": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.1.tgz", - "integrity": "sha512-0qXvpeYO6vaNoRBI52/UsbcaBydJCggoBBnIo/ovQQdn6fug0BgwsjorV1hVS7fMqGVTZGcVxv8334gjmbj5hw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.1.tgz", + "integrity": "sha512-0G7tNyS+yW8TdgHwZKlDWYXFA6OJQnoLCQvYKkQP0Q2X205PSQ6RNUj0M+1OB/9gRQaUZ/ccYfaxd0nhaWKfjw==", "dev": true }, "@xmldom/xmldom": { @@ -52860,6 +52479,15 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -53161,6 +52789,18 @@ "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } } } }, @@ -53492,6 +53132,12 @@ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", "dev": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "async": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", @@ -53608,6 +53254,12 @@ "typed-rest-client": "^1.8.4" } }, + "b4a": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.2.tgz", + "integrity": "sha512-YFqjbZ8iqX/wWJVmF1SSOB5TYDwsPd/sZzhSdu2PskElf55PjEe+0MhsEPgoa5eTK1VS/WqJMz9qwIFwZta+3g==", + "dev": true + }, "babel-jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", @@ -55306,55 +54958,45 @@ } }, "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", "dev": true, "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" } } } @@ -55539,11 +55181,6 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, - "color2k": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz", - "integrity": "sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w==" - }, "colorette": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", @@ -55566,9 +55203,9 @@ "dev": true }, "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", + "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", "dev": true }, "common-path-prefix": { @@ -56269,7 +55906,8 @@ "csstype": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", - "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" + "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==", + "dev": true }, "currently-unhandled": { "version": "0.4.1", @@ -56719,7 +56357,8 @@ "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true }, "default-browser-id": { "version": "1.0.4", @@ -56965,12 +56604,6 @@ "strip-final-newline": "^2.0.0" } }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -57240,6 +56873,12 @@ "object.defaults": "^1.1.0" } }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "easy-stack": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", @@ -57347,15 +56986,6 @@ "tapable": "^1.0.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -57363,9 +56993,9 @@ "dev": true }, "envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, "errno": { @@ -58533,6 +58163,12 @@ "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", "dev": true }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -58560,20 +58196,20 @@ "dev": true }, "execa": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", - "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", + "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" }, "dependencies": { "cross-spawn": { @@ -58587,6 +58223,44 @@ "which": "^2.0.1" } }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + }, + "dependencies": { + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + } + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -58608,6 +58282,12 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -59459,11 +59139,6 @@ "tslib": "^1.9.3" } }, - "focus-visible": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/focus-visible/-/focus-visible-5.2.0.tgz", - "integrity": "sha512-Rwix9pBtC1Nuy5wysTmKy+UjbDJpIfg8eHjw0rjZ1mX4GNLz1Bmd16uDpI3Gk1i70Fgcs8Csg2lPm8HULFg9DQ==" - }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -59773,11 +59448,6 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, - "fzy.js": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/fzy.js/-/fzy.js-0.4.1.tgz", - "integrity": "sha512-4sPVXf+9oGhzg2tYzgWe4hgAY0wEbkqeuKVEgdnqX8S8VcLosQsDjb0jV+f5uoQlf8INWId1w0IGoufAoik1TA==" - }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -59853,12 +59523,6 @@ "has-symbols": "^1.0.3" } }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true - }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -59873,25 +59537,10 @@ "optional": true }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "dev": true, - "requires": { - "pump": "^3.0.0" - }, - "dependencies": { - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true }, "get-symbol-description": { "version": "1.0.0", @@ -59942,11 +59591,22 @@ } }, "glob-promise": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz", - "integrity": "sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-6.0.2.tgz", + "integrity": "sha512-Ni2aDyD1ekD6x8/+K4hDriRDbzzfuK4yKpqSymJ4P7IxbtARiOOuU+k40kbHM0sLIlbf1Qh0qdMkAHMZYE6XJQ==", "requires": { - "@types/glob": "^7.1.3" + "@types/glob": "^8.0.0" + }, + "dependencies": { + "@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "requires": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + } } }, "glob-stream": { @@ -60648,14 +60308,6 @@ "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.1.2.tgz", "integrity": "sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA==" }, - "history": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.0.0.tgz", - "integrity": "sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==", - "requires": { - "@babel/runtime": "^7.7.6" - } - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -60854,9 +60506,9 @@ } }, "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.0.tgz", + "integrity": "sha512-zyzVyMjpGBX2+6cDVZeFPCdtOtdsxOeseRhB9tkQ6xXmGUNrcnBzdEKPy3VPNYz+4gy1oukVOXcrJCunSyc6QQ==", "dev": true }, "husky": { @@ -60883,9 +60535,9 @@ } }, "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "iferr": { "version": "0.1.5", @@ -61306,12 +60958,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, "is-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", @@ -61366,12 +61012,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -61792,12 +61432,6 @@ "strip-final-newline": "^2.0.0" } }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -65291,6 +64925,12 @@ "resolve": "^1.1.7" } }, + "lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -65307,38 +64947,26 @@ } }, "lint-staged": { - "version": "10.2.11", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.11.tgz", - "integrity": "sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.0.tgz", + "integrity": "sha512-GbyK5iWinax5Dfw5obm2g2ccUiZXNGtAS4mCbJ0Lv4rq6iEtfBSjOYdcbOtAIFtM114t0vdpViDDetjVTSd8Vw==", "dev": true, "requires": { - "chalk": "^4.0.0", - "cli-truncate": "2.1.0", - "commander": "^5.1.0", - "cosmiconfig": "^6.0.0", - "debug": "^4.1.1", - "dedent": "^0.7.0", - "enquirer": "^2.3.5", - "execa": "^4.0.1", - "listr2": "^2.1.0", - "log-symbols": "^4.0.0", - "micromatch": "^4.0.2", + "chalk": "5.2.0", + "cli-truncate": "^3.1.0", + "commander": "^10.0.0", + "debug": "^4.3.4", + "execa": "^7.0.0", + "lilconfig": "2.1.0", + "listr2": "^5.0.7", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", - "stringify-object": "^3.3.0" + "object-inspect": "^1.12.3", + "pidtree": "^0.6.0", + "string-argv": "^0.3.1", + "yaml": "^2.2.1" }, "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -65349,37 +64977,18 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", "dev": true }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "fill-range": { @@ -65391,12 +65000,6 @@ "to-regex-range": "^5.0.1" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -65404,13 +65007,13 @@ "dev": true }, "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "ms": { @@ -65419,14 +65022,11 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true }, "to-regex-range": { "version": "5.0.1", @@ -65436,6 +65036,12 @@ "requires": { "is-number": "^7.0.0" } + }, + "yaml": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", + "dev": true } } }, @@ -65445,39 +65051,38 @@ "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" }, "listr2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.2.0.tgz", - "integrity": "sha512-Q8qbd7rgmEwDo1nSyHaWQeztfGsdL6rb4uh7BA+Q80AZiDET5rVntiU1+13mu2ZTDVaBVbvAD1Db11rnu3l9sg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz", + "integrity": "sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==", "dev": true, "requires": { - "chalk": "^4.0.0", "cli-truncate": "^2.1.0", - "figures": "^3.2.0", - "indent-string": "^4.0.0", + "colorette": "^2.0.19", "log-update": "^4.0.0", "p-map": "^4.0.0", - "rxjs": "^6.5.5", - "through": "^2.3.8" + "rfdc": "^1.3.0", + "rxjs": "^7.8.0", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" } }, "color-convert": { @@ -65495,19 +65100,32 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", "dev": true }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } } } @@ -65697,21 +65315,14 @@ }, "dependencies": { "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" } }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -66546,14 +66157,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, - "rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", - "requires": { - "tslib": "^2.1.0" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -66562,11 +66165,6 @@ "has-flag": "^4.0.0" } }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" - }, "type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -67001,7 +66599,8 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "object-copy": { "version": "0.1.0", @@ -67035,9 +66634,9 @@ } }, "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" }, "object-keys": { "version": "1.1.1", @@ -67889,15 +67488,6 @@ } } }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, "plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", @@ -68621,11 +68211,6 @@ "prop-types": "^15.0.0" } }, - "react-intersection-observer": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.4.1.tgz", - "integrity": "sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw==" - }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -68763,7 +68348,8 @@ "regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "regenerator-transform": { "version": "0.15.0", @@ -69223,6 +68809,12 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -69281,12 +68873,18 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, "rxjs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", - "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", - "dev": true, + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", + "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", "requires": { - "tslib": "^1.9.0" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + } } }, "safe-buffer": { @@ -69478,12 +69076,6 @@ } } }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, "semver-greatest-satisfied-range": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", @@ -69709,6 +69301,30 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + } + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -70196,17 +69812,6 @@ "es-abstract": "^1.19.5" } }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -70325,13 +69930,13 @@ } }, "styled-components": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz", - "integrity": "sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", + "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^0.8.8", + "@emotion/is-prop-valid": "^1.1.0", "@emotion/stylis": "^0.8.4", "@emotion/unitless": "^0.7.4", "babel-plugin-styled-components": ">= 1.12.0", @@ -70351,26 +69956,6 @@ } } }, - "styled-system": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz", - "integrity": "sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==", - "requires": { - "@styled-system/background": "^5.1.2", - "@styled-system/border": "^5.1.5", - "@styled-system/color": "^5.1.2", - "@styled-system/core": "^5.1.2", - "@styled-system/flexbox": "^5.1.2", - "@styled-system/grid": "^5.1.2", - "@styled-system/layout": "^5.1.2", - "@styled-system/position": "^5.1.2", - "@styled-system/shadow": "^5.1.2", - "@styled-system/space": "^5.1.2", - "@styled-system/typography": "^5.1.2", - "@styled-system/variant": "^5.1.5", - "object-assign": "^4.1.1" - } - }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -70492,30 +70077,76 @@ "end-of-stream": "^1.1.0", "once": "^1.3.1" } - } - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "dependencies": { + }, "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } + } + }, + "tar-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.0.0.tgz", + "integrity": "sha512-O6OfUKBbQOqAhh6owTWmA730J/yZCYcpmZ1DBj2YX51ZQrt7d7NgzrR+CnO9wP6nt/viWZW2XeXLavX3/ZEbEg==", + "dev": true, + "requires": { + "b4a": "^1.6.1", + "bl": "^6.0.0", + "streamx": "^2.12.5" + }, + "dependencies": { + "bl": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.0.tgz", + "integrity": "sha512-Ik9BVIMdcWzSOCpzDv2XpQ4rJ4oZBuk3ck6MgiOv0EopdgtohN2uSCrrLlkH1Jf0KnpZZMBA3D0bUMbCdj/jgA==", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", + "dev": true, + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + } } } }, @@ -72002,12 +71633,6 @@ "integrity": "sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==", "dev": true }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "v8-compile-cache-lib": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", @@ -72440,9 +72065,9 @@ "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, "webpack": { - "version": "5.73.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.73.0.tgz", - "integrity": "sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==", + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", + "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", @@ -72450,11 +72075,11 @@ "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.3", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -72467,7 +72092,7 @@ "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { @@ -72496,9 +72121,9 @@ } }, "enhanced-resolve": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", - "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -72531,31 +72156,36 @@ } }, "webpack-cli": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.6.0.tgz", - "integrity": "sha512-9YV+qTcGMjQFiY7Nb1kmnupvb1x40lfpj8pwdO/bom+sQiP4OBMKjHq29YQrlDWDPZO9r/qWaRRywKaRDKqBTA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.0.1.tgz", + "integrity": "sha512-S3KVAyfwUqr0Mo/ur3NzIp6jnerNpo7GUO6so51mxLi1spqsA17YcMXy0WOIJtBSnj748lthxC6XLbNKh/ZC+A==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.2", - "@webpack-cli/info": "^1.2.3", - "@webpack-cli/serve": "^1.3.1", - "colorette": "^1.2.1", - "commander": "^7.0.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", + "@webpack-cli/configtest": "^2.0.1", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.1", + "colorette": "^2.0.14", + "commander": "^9.4.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "v8-compile-cache": "^2.2.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "dependencies": { + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true }, "cross-spawn": { @@ -72569,39 +72199,10 @@ "which": "^2.0.1" } }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", - "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, "interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true }, "path-key": { @@ -72611,12 +72212,12 @@ "dev": true }, "rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "requires": { - "resolve": "^1.9.0" + "resolve": "^1.20.0" } }, "shebang-command": { diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index 2ee9f8878..21863ca86 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -4,7 +4,7 @@ "description": "CodeQL for Visual Studio Code", "author": "GitHub", "private": true, - "version": "1.8.1", + "version": "1.8.2", "publisher": "GitHub", "license": "MIT", "icon": "media/VS-marketplace-CodeQL-icon.png", @@ -56,6 +56,7 @@ "onCommand:codeQL.restartQueryServer", "onWebviewPanel:resultsView", "onWebviewPanel:codeQL.variantAnalysis", + "onWebviewPanel:codeQL.dataFlowPaths", "onFileSystem:codeql-zip-archive" ], "main": "./out/extension", @@ -544,7 +545,12 @@ "title": "CodeQL: Check for CLI Updates" }, { - "command": "codeQLQueryHistory.openQuery", + "command": "codeQLQueryHistory.openQueryTitleMenu", + "title": "View Query", + "icon": "$(edit)" + }, + { + "command": "codeQLQueryHistory.openQueryContextMenu", "title": "View Query", "icon": "$(edit)" }, @@ -554,7 +560,17 @@ "icon": "$(preview)" }, { - "command": "codeQLQueryHistory.removeHistoryItem", + "command": "codeQLQueryHistory.removeHistoryItemTitleMenu", + "title": "Delete", + "icon": "$(trash)" + }, + { + "command": "codeQLQueryHistory.removeHistoryItemContextMenu", + "title": "Delete", + "icon": "$(trash)" + }, + { + "command": "codeQLQueryHistory.removeHistoryItemContextInline", "title": "Delete", "icon": "$(trash)" }, @@ -741,7 +757,7 @@ "group": "navigation" }, { - "command": "codeQLQueryHistory.openQuery", + "command": "codeQLQueryHistory.openQueryTitleMenu", "when": "view == codeQLQueryHistory", "group": "navigation" }, @@ -751,7 +767,7 @@ "group": "navigation" }, { - "command": "codeQLQueryHistory.removeHistoryItem", + "command": "codeQLQueryHistory.removeHistoryItemTitleMenu", "when": "view == codeQLQueryHistory", "group": "navigation" }, @@ -848,17 +864,17 @@ "group": "inline" }, { - "command": "codeQLQueryHistory.openQuery", + "command": "codeQLQueryHistory.openQueryContextMenu", "group": "2_queryHistory@0", "when": "view == codeQLQueryHistory" }, { - "command": "codeQLQueryHistory.removeHistoryItem", + "command": "codeQLQueryHistory.removeHistoryItemContextMenu", "group": "7_queryHistory@0", "when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledRemoteResultsItemWithoutLogs || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem" }, { - "command": "codeQLQueryHistory.removeHistoryItem", + "command": "codeQLQueryHistory.removeHistoryItemContextInline", "group": "inline", "when": "viewItem == interpretedResultsItem || viewItem == rawResultsItem || viewItem == remoteResultsItem || viewItem == cancelledRemoteResultsItemWithoutLogs || viewItem == cancelledResultsItem || viewItem == cancelledRemoteResultsItem" }, @@ -1155,11 +1171,23 @@ "when": "false" }, { - "command": "codeQLQueryHistory.openQuery", + "command": "codeQLQueryHistory.openQueryTitleMenu", "when": "false" }, { - "command": "codeQLQueryHistory.removeHistoryItem", + "command": "codeQLQueryHistory.openQueryContextMenu", + "when": "false" + }, + { + "command": "codeQLQueryHistory.removeHistoryItemTitleMenu", + "when": "false" + }, + { + "command": "codeQLQueryHistory.removeHistoryItemContextMenu", + "when": "false" + }, + { + "command": "codeQLQueryHistory.removeHistoryItemContextInline", "when": "false" }, { @@ -1405,8 +1433,6 @@ "dependencies": { "@octokit/plugin-retry": "^3.0.9", "@octokit/rest": "^19.0.4", - "@primer/octicons-react": "^17.6.0", - "@primer/react": "^35.0.0", "@vscode/codicons": "^0.0.31", "@vscode/webview-ui-toolkit": "^1.0.1", "ajv": "^8.11.0", @@ -1416,7 +1442,7 @@ "d3": "^7.6.1", "d3-graphviz": "^5.0.2", "fs-extra": "^10.0.1", - "glob-promise": "^4.2.2", + "glob-promise": "^6.0.2", "immutable": "^4.0.0", "js-yaml": "^4.1.0", "minimist": "~1.2.6", @@ -1449,7 +1475,7 @@ "@babel/core": "^7.18.13", "@babel/plugin-transform-modules-commonjs": "^7.18.6", "@faker-js/faker": "^7.5.0", - "@octokit/plugin-throttling": "^4.3.2", + "@octokit/plugin-throttling": "^5.0.1", "@storybook/addon-actions": "^6.5.10", "@storybook/addon-essentials": "^6.5.10", "@storybook/addon-interactions": "^6.5.10", @@ -1484,6 +1510,7 @@ "@types/semver": "~7.2.0", "@types/stream-chain": "~2.0.1", "@types/stream-json": "~1.7.1", + "@types/styled-components": "^5.1.11", "@types/tar-stream": "^2.2.2", "@types/through2": "^2.0.36", "@types/tmp": "^0.1.0", @@ -1498,7 +1525,6 @@ "@vscode/vsce": "^2.15.0", "ansi-colors": "^4.1.1", "applicationinsights": "^2.3.5", - "babel-loader": "^8.2.5", "cross-env": "^7.0.3", "css-loader": "~3.1.0", "del": "^6.0.0", @@ -1523,12 +1549,12 @@ "jest": "^29.0.3", "jest-environment-jsdom": "^29.0.3", "jest-runner-vscode": "^3.0.1", - "lint-staged": "~10.2.2", + "lint-staged": "~13.2.0", "mini-css-extract-plugin": "^2.6.1", "npm-run-all": "^4.1.5", "patch-package": "^6.5.0", "prettier": "^2.7.1", - "tar-stream": "^2.2.0", + "tar-stream": "^3.0.0", "through2": "^4.0.2", "ts-jest": "^29.0.1", "ts-json-schema-generator": "^1.1.2", @@ -1536,8 +1562,8 @@ "ts-node": "^10.7.0", "ts-protoc-gen": "^0.9.0", "typescript": "^4.5.5", - "webpack": "^5.62.2", - "webpack-cli": "^4.6.0" + "webpack": "^5.76.0", + "webpack-cli": "^5.0.1" }, "lint-staged": { "./**/*.{json,css,scss}": [ diff --git a/extensions/ql-vscode/src/ast-cfg-commands.ts b/extensions/ql-vscode/src/ast-cfg-commands.ts new file mode 100644 index 000000000..49749be22 --- /dev/null +++ b/extensions/ql-vscode/src/ast-cfg-commands.ts @@ -0,0 +1,90 @@ +import { Uri, window } from "vscode"; +import { withProgress } from "./progress"; +import { AstViewer } from "./astViewer"; +import { + TemplatePrintAstProvider, + TemplatePrintCfgProvider, +} from "./contextual/templateProvider"; +import { compileAndRunQuery } from "./local-queries"; +import { QueryRunner } from "./queryRunner"; +import { QueryHistoryManager } from "./query-history/query-history-manager"; +import { DatabaseUI } from "./local-databases-ui"; +import { ResultsView } from "./interface"; +import { AstCfgCommands } from "./common/commands"; + +type AstCfgOptions = { + queryRunner: QueryRunner; + queryHistoryManager: QueryHistoryManager; + databaseUI: DatabaseUI; + localQueryResultsView: ResultsView; + queryStorageDir: string; + + astViewer: AstViewer; + astTemplateProvider: TemplatePrintAstProvider; + cfgTemplateProvider: TemplatePrintCfgProvider; +}; + +export function getAstCfgCommands({ + queryRunner, + queryHistoryManager, + databaseUI, + localQueryResultsView, + queryStorageDir, + astViewer, + astTemplateProvider, + cfgTemplateProvider, +}: AstCfgOptions): AstCfgCommands { + const viewAst = async (selectedFile: Uri) => + withProgress( + async (progress, token) => { + const ast = await astTemplateProvider.provideAst( + progress, + token, + selectedFile ?? window.activeTextEditor?.document.uri, + ); + if (ast) { + astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName); + } + }, + { + cancellable: true, + title: "Calculate AST", + }, + ); + + const viewCfg = async () => + withProgress( + async (progress, token) => { + const res = await cfgTemplateProvider.provideCfgUri( + window.activeTextEditor?.document, + ); + if (res) { + await compileAndRunQuery( + queryRunner, + queryHistoryManager, + databaseUI, + localQueryResultsView, + queryStorageDir, + false, + res[0], + progress, + token, + undefined, + ); + } + }, + { + title: "Calculating Control Flow Graph", + cancellable: true, + }, + ); + + return { + "codeQL.viewAst": viewAst, + "codeQL.viewAstContextExplorer": viewAst, + "codeQL.viewAstContextEditor": viewAst, + "codeQL.viewCfg": viewCfg, + "codeQL.viewCfgContextExplorer": viewCfg, + "codeQL.viewCfgContextEditor": viewCfg, + }; +} diff --git a/extensions/ql-vscode/src/astViewer.ts b/extensions/ql-vscode/src/astViewer.ts index 3c5d2bdf8..5105439f8 100644 --- a/extensions/ql-vscode/src/astViewer.ts +++ b/extensions/ql-vscode/src/astViewer.ts @@ -23,11 +23,11 @@ import { isWholeFileLoc, isLineColumnLoc, } from "./pure/bqrs-utils"; -import { commandRunner } from "./commandRunner"; import { DisposableObject } from "./pure/disposable-object"; import { showAndLogExceptionWithTelemetry } from "./helpers"; import { asError, getErrorMessage } from "./pure/helpers-pure"; import { redactableError } from "./pure/errors"; +import { AstViewerCommands } from "./common/commands"; export interface AstItem { id: BqrsId; @@ -55,15 +55,6 @@ class AstViewerDataProvider readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; - constructor() { - super(); - this.push( - commandRunner("codeQLAstViewer.gotoCode", async (item: AstItem) => { - await showLocation(item.fileLocation); - }), - ); - } - refresh(): void { this._onDidChangeTreeData.fire(undefined); } @@ -126,16 +117,20 @@ export class AstViewer extends DisposableObject { this.push(this.treeView); this.push(this.treeDataProvider); - this.push( - commandRunner("codeQLAstViewer.clear", async () => { - this.clear(); - }), - ); this.push( window.onDidChangeTextEditorSelection(this.updateTreeSelection, this), ); } + getCommands(): AstViewerCommands { + return { + "codeQLAstViewer.clear": async () => this.clear(), + "codeQLAstViewer.gotoCode": async (item: AstItem) => { + await showLocation(item.fileLocation); + }, + }; + } + updateRoots(roots: AstItem[], db: DatabaseItem, fileUri: Uri) { this.treeDataProvider.roots = roots; this.treeDataProvider.db = db; diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts index 321357da1..45c6a7f6c 100644 --- a/extensions/ql-vscode/src/cli.ts +++ b/extensions/ql-vscode/src/cli.ts @@ -9,7 +9,7 @@ import { Readable } from "stream"; import { StringDecoder } from "string_decoder"; import tk from "tree-kill"; import { promisify } from "util"; -import { CancellationToken, commands, Disposable, Uri } from "vscode"; +import { CancellationToken, Disposable, Uri } from "vscode"; import { BQRSInfo, DecodedBqrsChunk } from "./pure/bqrs-cli-types"; import { allowCanaryQueryServer, CliConfig } from "./config"; @@ -1284,11 +1284,25 @@ export class CodeQLCliServer implements Disposable { ); } - async packInstall(dir: string, forceUpdate = false) { + async packInstall( + dir: string, + { forceUpdate = false, workspaceFolders = [] as string[] } = {}, + ) { const args = [dir]; if (forceUpdate) { args.push("--mode", "update"); } + if (workspaceFolders?.length > 0) { + if (await this.cliConstraints.supportsAdditionalPacksInstall()) { + args.push( + // Allow prerelease packs from the ql submodule. + "--allow-prerelease", + // Allow the use of --additional-packs argument without issueing a warning + "--no-strict-mode", + ...this.getAdditionalPacksArg(workspaceFolders), + ); + } + } return this.runJsonCodeQlCliCommandWithAuthentication( ["pack", "install"], args, @@ -1361,7 +1375,7 @@ export class CodeQLCliServer implements Disposable { if (!this._version) { this._version = this.refreshVersion(); // this._version is only undefined upon config change, so we reset CLI-based context key only when necessary. - await commands.executeCommand( + await this.app.commands.execute( "setContext", "codeql.supportsEvalLog", await this.cliConstraints.supportsPerQueryEvalLog(), @@ -1692,6 +1706,13 @@ export class CliVersionConstraint { */ public static CLI_VERSION_WITH_QLPACKS_KIND = new SemVer("2.12.3"); + /** + * CLI version that supports the `--additional-packs` option for the `pack install` command. + */ + public static CLI_VERSION_WITH_ADDITIONAL_PACKS_INSTALL = new SemVer( + "2.12.4", + ); + constructor(private readonly cli: CodeQLCliServer) { /**/ } @@ -1755,4 +1776,10 @@ export class CliVersionConstraint { CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND, ); } + + async supportsAdditionalPacksInstall() { + return this.isVersionAtLeast( + CliVersionConstraint.CLI_VERSION_WITH_ADDITIONAL_PACKS_INSTALL, + ); + } } diff --git a/extensions/ql-vscode/src/commandRunner.ts b/extensions/ql-vscode/src/commandRunner.ts deleted file mode 100644 index 4ff281c04..000000000 --- a/extensions/ql-vscode/src/commandRunner.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { - CancellationToken, - ProgressOptions, - window as Window, - commands, - Disposable, - ProgressLocation, -} from "vscode"; -import { - showAndLogExceptionWithTelemetry, - showAndLogWarningMessage, -} from "./helpers"; -import { extLogger } from "./common"; -import { asError, getErrorMessage, getErrorStack } from "./pure/helpers-pure"; -import { telemetryListener } from "./telemetry"; -import { redactableError } from "./pure/errors"; - -export class UserCancellationException extends Error { - /** - * @param message The error message - * @param silent If silent is true, then this exception will avoid showing a warning message to the user. - */ - constructor(message?: string, public readonly silent = false) { - super(message); - } -} - -export interface ProgressUpdate { - /** - * The current step - */ - step: number; - /** - * The maximum step. This *should* be constant for a single job. - */ - maxStep: number; - /** - * The current progress message - */ - message: string; -} - -export type ProgressCallback = (p: ProgressUpdate) => void; - -/** - * A task that handles command invocations from `commandRunner` - * and includes a progress monitor. - * - * - * Arguments passed to the command handler are passed along, - * untouched to this `ProgressTask` instance. - * - * @param progress a progress handler function. Call this - * function with a `ProgressUpdate` instance in order to - * denote some progress being achieved on this task. - * @param token a cencellation token - * @param args arguments passed to this task passed on from - * `commands.registerCommand`. - */ -export type ProgressTask = ( - progress: ProgressCallback, - token: CancellationToken, - ...args: any[] -) => Thenable; - -/** - * A task that handles command invocations from `commandRunner`. - * Arguments passed to the command handler are passed along, - * untouched to this `NoProgressTask` instance. - * - * @param args arguments passed to this task passed on from - * `commands.registerCommand`. - */ -type NoProgressTask = (...args: any[]) => Promise; - -/** - * This mediates between the kind of progress callbacks we want to - * write (where we *set* current progress position and give - * `maxSteps`) and the kind vscode progress api expects us to write - * (which increment progress by a certain amount out of 100%). - * - * Where possible, the `commandRunner` function below should be used - * instead of this function. The commandRunner is meant for wrapping - * top-level commands and provides error handling and other support - * automatically. - * - * Only use this function if you need a progress monitor and the - * control flow does not always come from a command (eg- during - * extension activation, or from an internal language server - * request). - */ -export function withProgress( - options: ProgressOptions, - task: ProgressTask, - ...args: any[] -): Thenable { - let progressAchieved = 0; - return Window.withProgress(options, (progress, token) => { - return task( - (p) => { - const { message, step, maxStep } = p; - const increment = (100 * (step - progressAchieved)) / maxStep; - progressAchieved = step; - progress.report({ message, increment }); - }, - token, - ...args, - ); - }); -} - -/** - * A generic wrapper for command registration. This wrapper adds uniform error handling for commands. - * - * In this variant of the command runner, no progress monitor is used. - * - * @param commandId The ID of the command to register. - * @param task The task to run. It is passed directly to `commands.registerCommand`. Any - * arguments to the command handler are passed on to the task. - */ -export function commandRunner( - commandId: string, - task: NoProgressTask, -): Disposable { - return commands.registerCommand(commandId, async (...args: any[]) => { - const startTime = Date.now(); - let error: Error | undefined; - - try { - return await task(...args); - } catch (e) { - error = asError(e); - const errorMessage = redactableError(error)`${ - getErrorMessage(e) || e - } (${commandId})`; - const errorStack = getErrorStack(e); - if (e instanceof UserCancellationException) { - // User has cancelled this action manually - if (e.silent) { - void extLogger.log(errorMessage.fullMessage); - } else { - void showAndLogWarningMessage(errorMessage.fullMessage); - } - } else { - // Include the full stack in the error log only. - const fullMessage = errorStack - ? `${errorMessage.fullMessage}\n${errorStack}` - : errorMessage.fullMessage; - void showAndLogExceptionWithTelemetry(errorMessage, { - fullMessage, - extraTelemetryProperties: { - command: commandId, - }, - }); - } - return undefined; - } finally { - const executionTime = Date.now() - startTime; - telemetryListener?.sendCommandUsage(commandId, executionTime, error); - } - }); -} - -/** - * A generic wrapper for command registration. This wrapper adds uniform error handling, - * progress monitoring, and cancellation for commands. - * - * @param commandId The ID of the command to register. - * @param task The task to run. It is passed directly to `commands.registerCommand`. Any - * arguments to the command handler are passed on to the task after the progress callback - * and cancellation token. - * @param progressOptions Progress options to be sent to the progress monitor. - */ -export function commandRunnerWithProgress( - commandId: string, - task: ProgressTask, - progressOptions: Partial, - outputLogger = extLogger, -): Disposable { - return commands.registerCommand(commandId, async (...args: any[]) => { - const startTime = Date.now(); - let error: Error | undefined; - const progressOptionsWithDefaults = { - location: ProgressLocation.Notification, - ...progressOptions, - }; - try { - return await withProgress(progressOptionsWithDefaults, task, ...args); - } catch (e) { - error = asError(e); - const errorMessage = redactableError`${ - getErrorMessage(e) || e - } (${commandId})`; - const errorStack = getErrorStack(e); - if (e instanceof UserCancellationException) { - // User has cancelled this action manually - if (e.silent) { - void outputLogger.log(errorMessage.fullMessage); - } else { - void showAndLogWarningMessage(errorMessage.fullMessage, { - outputLogger, - }); - } - } else { - // Include the full stack in the error log only. - const fullMessage = errorStack - ? `${errorMessage.fullMessage}\n${errorStack}` - : errorMessage.fullMessage; - void showAndLogExceptionWithTelemetry(errorMessage, { - outputLogger, - fullMessage, - extraTelemetryProperties: { - command: commandId, - }, - }); - } - return undefined; - } finally { - const executionTime = Date.now() - startTime; - telemetryListener?.sendCommandUsage(commandId, executionTime, error); - } - }); -} - -/** - * Displays a progress monitor that indicates how much progess has been made - * reading from a stream. - * - * @param readable The stream to read progress from - * @param messagePrefix A prefix for displaying the message - * @param totalNumBytes Total number of bytes in this stream - * @param progress The progress callback used to set messages - */ -export function reportStreamProgress( - readable: NodeJS.ReadableStream, - messagePrefix: string, - totalNumBytes?: number, - progress?: ProgressCallback, -) { - if (progress && totalNumBytes) { - let numBytesDownloaded = 0; - const bytesToDisplayMB = (numBytes: number): string => - `${(numBytes / (1024 * 1024)).toFixed(1)} MB`; - const updateProgress = () => { - progress({ - step: numBytesDownloaded, - maxStep: totalNumBytes, - message: `${messagePrefix} [${bytesToDisplayMB( - numBytesDownloaded, - )} of ${bytesToDisplayMB(totalNumBytes)}]`, - }); - }; - - // Display the progress straight away rather than waiting for the first chunk. - updateProgress(); - - readable.on("data", (data) => { - numBytesDownloaded += data.length; - updateProgress(); - }); - } else if (progress) { - progress({ - step: 1, - maxStep: 2, - message: `${messagePrefix} (Size unknown)`, - }); - } -} diff --git a/extensions/ql-vscode/src/common/app.ts b/extensions/ql-vscode/src/common/app.ts index 67506a5fa..2e749c018 100644 --- a/extensions/ql-vscode/src/common/app.ts +++ b/extensions/ql-vscode/src/common/app.ts @@ -3,10 +3,10 @@ import { Disposable } from "../pure/disposable-object"; import { AppEventEmitter } from "./events"; import { Logger } from "./logging"; import { Memento } from "./memento"; +import { AppCommandManager } from "./commands"; export interface App { createEventEmitter(): AppEventEmitter; - executeCommand(command: string, ...args: any): Thenable; readonly mode: AppMode; readonly logger: Logger; readonly subscriptions: Disposable[]; @@ -15,6 +15,7 @@ export interface App { readonly workspaceStoragePath?: string; readonly workspaceState: Memento; readonly credentials: Credentials; + readonly commands: AppCommandManager; } export enum AppMode { diff --git a/extensions/ql-vscode/src/common/commands.ts b/extensions/ql-vscode/src/common/commands.ts new file mode 100644 index 000000000..55e47f22b --- /dev/null +++ b/extensions/ql-vscode/src/common/commands.ts @@ -0,0 +1,289 @@ +import type { CommandManager } from "../packages/commands"; +import type { Uri, Range, TextDocumentShowOptions } from "vscode"; +import type { AstItem } from "../astViewer"; +import type { DbTreeViewItem } from "../databases/ui/db-tree-view-item"; +import type { DatabaseItem } from "../local-databases"; +import type { QueryHistoryInfo } from "../query-history/query-history-info"; +import type { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort"; +import type { TestTreeNode } from "../test-tree-node"; +import type { + VariantAnalysis, + VariantAnalysisScannedRepository, + VariantAnalysisScannedRepositoryResult, +} from "../variant-analysis/shared/variant-analysis"; + +// A command function matching the signature that VS Code calls when +// a command on a selection is invoked. +export type SelectionCommandFunction = ( + singleItem: Item, + multiSelect: Item[], +) => Promise; + +// A command function matching the signature that VS Code calls when +// a command on a selection is invoked when canSelectMany is false. +export type SingleSelectionCommandFunction = ( + singleItem: Item, +) => Promise; + +/** + * Contains type definitions for all commands used by the extension. + * + * To add a new command first define its type here, then provide + * the implementation in the corresponding `getCommands` function. + */ + +// Builtin commands where the implementation is provided by VS Code and not by this extension. +// See https://code.visualstudio.com/api/references/commands +export type BuiltInVsCodeCommands = { + // The codeQLDatabases.focus command is provided by VS Code because we've registered the custom view + "codeQLDatabases.focus": () => Promise; + "markdown.showPreviewToSide": (uri: Uri) => Promise; + revealFileInOS: (uri: Uri) => Promise; + setContext: ( + key: `${"codeql" | "codeQL"}${string}`, + value: unknown, + ) => Promise; + "workbench.action.reloadWindow": () => Promise; + "vscode.diff": ( + leftSideResource: Uri, + rightSideResource: Uri, + title?: string, + columnOrOptions?: TextDocumentShowOptions, + ) => Promise; + "vscode.open": (uri: Uri) => Promise; + "vscode.openFolder": (uri: Uri) => Promise; +}; + +// Commands that are available before the extension is fully activated. +// These commands are *not* registered using the command manager, but can +// be invoked using the command manager. +export type PreActivationCommands = { + "codeQL.checkForUpdatesToCLI": () => Promise; +}; + +// Base commands not tied directly to a module like e.g. variant analysis. +export type BaseCommands = { + "codeQL.openDocumentation": () => Promise; + "codeQL.showLogs": () => Promise; + "codeQL.authenticateToGitHub": () => Promise; + + "codeQL.copyVersion": () => Promise; + "codeQL.restartQueryServer": () => Promise; +}; + +// Commands used when working with queries in the editor +export type QueryEditorCommands = { + "codeQL.openReferencedFile": (selectedQuery: Uri) => Promise; + "codeQL.openReferencedFileContextEditor": ( + selectedQuery: Uri, + ) => Promise; + "codeQL.openReferencedFileContextExplorer": ( + selectedQuery: Uri, + ) => Promise; + "codeQL.previewQueryHelp": (selectedQuery: Uri) => Promise; +}; + +// Commands used for running local queries +export type LocalQueryCommands = { + "codeQL.runQuery": (uri?: Uri) => Promise; + "codeQL.runQueryContextEditor": (uri?: Uri) => Promise; + "codeQL.runQueryOnMultipleDatabases": (uri?: Uri) => Promise; + "codeQL.runQueryOnMultipleDatabasesContextEditor": ( + uri?: Uri, + ) => Promise; + "codeQL.runQueries": SelectionCommandFunction; + "codeQL.quickEval": (uri: Uri) => Promise; + "codeQL.quickEvalContextEditor": (uri: Uri) => Promise; + "codeQL.codeLensQuickEval": (uri: Uri, range: Range) => Promise; + "codeQL.quickQuery": () => Promise; +}; + +export type ResultsViewCommands = { + "codeQLQueryResults.up": () => Promise; + "codeQLQueryResults.down": () => Promise; + "codeQLQueryResults.left": () => Promise; + "codeQLQueryResults.right": () => Promise; + "codeQLQueryResults.nextPathStep": () => Promise; + "codeQLQueryResults.previousPathStep": () => Promise; +}; + +// Commands used for the query history panel +export type QueryHistoryCommands = { + // Commands in the "navigation" group + "codeQLQueryHistory.sortByName": () => Promise; + "codeQLQueryHistory.sortByDate": () => Promise; + "codeQLQueryHistory.sortByCount": () => Promise; + + // Commands in the context menu or in the hover menu + "codeQLQueryHistory.openQueryTitleMenu": SelectionCommandFunction; + "codeQLQueryHistory.openQueryContextMenu": SelectionCommandFunction; + "codeQLQueryHistory.removeHistoryItemTitleMenu": SelectionCommandFunction; + "codeQLQueryHistory.removeHistoryItemContextMenu": SelectionCommandFunction; + "codeQLQueryHistory.removeHistoryItemContextInline": SelectionCommandFunction; + "codeQLQueryHistory.renameItem": SelectionCommandFunction; + "codeQLQueryHistory.compareWith": SelectionCommandFunction; + "codeQLQueryHistory.showEvalLog": SelectionCommandFunction; + "codeQLQueryHistory.showEvalLogSummary": SelectionCommandFunction; + "codeQLQueryHistory.showEvalLogViewer": SelectionCommandFunction; + "codeQLQueryHistory.showQueryLog": SelectionCommandFunction; + "codeQLQueryHistory.showQueryText": SelectionCommandFunction; + "codeQLQueryHistory.openQueryDirectory": SelectionCommandFunction; + "codeQLQueryHistory.cancel": SelectionCommandFunction; + "codeQLQueryHistory.exportResults": SelectionCommandFunction; + "codeQLQueryHistory.viewCsvResults": SelectionCommandFunction; + "codeQLQueryHistory.viewCsvAlerts": SelectionCommandFunction; + "codeQLQueryHistory.viewSarifAlerts": SelectionCommandFunction; + "codeQLQueryHistory.viewDil": SelectionCommandFunction; + "codeQLQueryHistory.itemClicked": SelectionCommandFunction; + "codeQLQueryHistory.openOnGithub": SelectionCommandFunction; + "codeQLQueryHistory.copyRepoList": SelectionCommandFunction; + + // Commands in the command palette + "codeQL.exportSelectedVariantAnalysisResults": () => Promise; +}; + +// Commands used for the local databases panel +export type LocalDatabasesCommands = { + // Command palette commands + "codeQL.chooseDatabaseFolder": () => Promise; + "codeQL.chooseDatabaseArchive": () => Promise; + "codeQL.chooseDatabaseInternet": () => Promise; + "codeQL.chooseDatabaseGithub": () => Promise; + "codeQL.upgradeCurrentDatabase": () => Promise; + "codeQL.clearCache": () => Promise; + + // Explorer context menu + "codeQL.setCurrentDatabase": (uri: Uri) => Promise; + + // Database panel view title commands + "codeQLDatabases.chooseDatabaseFolder": () => Promise; + "codeQLDatabases.chooseDatabaseArchive": () => Promise; + "codeQLDatabases.chooseDatabaseInternet": () => Promise; + "codeQLDatabases.chooseDatabaseGithub": () => Promise; + "codeQLDatabases.sortByName": () => Promise; + "codeQLDatabases.sortByDateAdded": () => Promise; + + // Database panel context menu + "codeQLDatabases.setCurrentDatabase": ( + databaseItem: DatabaseItem, + ) => Promise; + + // Database panel selection commands + "codeQLDatabases.removeDatabase": SelectionCommandFunction; + "codeQLDatabases.upgradeDatabase": SelectionCommandFunction; + "codeQLDatabases.renameDatabase": SelectionCommandFunction; + "codeQLDatabases.openDatabaseFolder": SelectionCommandFunction; + "codeQLDatabases.addDatabaseSource": SelectionCommandFunction; + + // Codespace template commands + "codeQL.setDefaultTourDatabase": () => Promise; + + // Internal commands + "codeQLDatabases.removeOrphanedDatabases": () => Promise; +}; + +// Commands tied to variant analysis +export type VariantAnalysisCommands = { + "codeQL.autoDownloadVariantAnalysisResult": ( + scannedRepo: VariantAnalysisScannedRepository, + variantAnalysisSummary: VariantAnalysis, + ) => Promise; + "codeQL.copyVariantAnalysisRepoList": ( + variantAnalysisId: number, + filterSort?: RepositoriesFilterSortStateWithIds, + ) => Promise; + "codeQL.loadVariantAnalysisRepoResults": ( + variantAnalysisId: number, + repositoryFullName: string, + ) => Promise; + "codeQL.monitorVariantAnalysis": ( + variantAnalysis: VariantAnalysis, + ) => Promise; + "codeQL.openVariantAnalysisLogs": ( + variantAnalysisId: number, + ) => Promise; + "codeQL.openVariantAnalysisView": ( + variantAnalysisId: number, + ) => Promise; + "codeQL.runVariantAnalysis": (uri?: Uri) => Promise; + "codeQL.runVariantAnalysisContextEditor": (uri?: Uri) => Promise; +}; + +export type DatabasePanelCommands = { + "codeQLVariantAnalysisRepositories.openConfigFile": () => Promise; + "codeQLVariantAnalysisRepositories.addNewDatabase": () => Promise; + "codeQLVariantAnalysisRepositories.addNewList": () => Promise; + "codeQLVariantAnalysisRepositories.setupControllerRepository": () => Promise; + + "codeQLVariantAnalysisRepositories.setSelectedItem": SingleSelectionCommandFunction; + "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu": SingleSelectionCommandFunction; + "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu": SingleSelectionCommandFunction; + "codeQLVariantAnalysisRepositories.renameItemContextMenu": SingleSelectionCommandFunction; + "codeQLVariantAnalysisRepositories.removeItemContextMenu": SingleSelectionCommandFunction; +}; + +export type AstCfgCommands = { + "codeQL.viewAst": (selectedFile: Uri) => Promise; + "codeQL.viewAstContextExplorer": (selectedFile: Uri) => Promise; + "codeQL.viewAstContextEditor": (selectedFile: Uri) => Promise; + "codeQL.viewCfg": () => Promise; + "codeQL.viewCfgContextExplorer": () => Promise; + "codeQL.viewCfgContextEditor": () => Promise; +}; + +export type AstViewerCommands = { + "codeQLAstViewer.clear": () => Promise; + "codeQLAstViewer.gotoCode": (item: AstItem) => Promise; +}; + +export type PackagingCommands = { + "codeQL.installPackDependencies": () => Promise; + "codeQL.downloadPacks": () => Promise; +}; + +export type EvalLogViewerCommands = { + "codeQLEvalLogViewer.clear": () => Promise; +}; + +export type SummaryLanguageSupportCommands = { + "codeQL.gotoQL": () => Promise; +}; + +export type TestUICommands = { + "codeQLTests.showOutputDifferences": (node: TestTreeNode) => Promise; + "codeQLTests.acceptOutput": (node: TestTreeNode) => Promise; +}; + +export type MockGitHubApiServerCommands = { + "codeQL.mockGitHubApiServer.startRecording": () => Promise; + "codeQL.mockGitHubApiServer.saveScenario": () => Promise; + "codeQL.mockGitHubApiServer.cancelRecording": () => Promise; + "codeQL.mockGitHubApiServer.loadScenario": () => Promise; + "codeQL.mockGitHubApiServer.unloadScenario": () => Promise; +}; + +// All commands where the implementation is provided by this activated extension. +export type AllExtensionCommands = BaseCommands & + QueryEditorCommands & + ResultsViewCommands & + QueryHistoryCommands & + LocalDatabasesCommands & + VariantAnalysisCommands & + DatabasePanelCommands & + AstCfgCommands & + AstViewerCommands & + PackagingCommands & + EvalLogViewerCommands & + SummaryLanguageSupportCommands & + Partial & + MockGitHubApiServerCommands; + +export type AllCommands = AllExtensionCommands & + PreActivationCommands & + BuiltInVsCodeCommands; + +export type AppCommandManager = CommandManager; + +// Separate command manager because it uses a different logger +export type QueryServerCommands = LocalQueryCommands; +export type QueryServerCommandManager = CommandManager; diff --git a/extensions/ql-vscode/src/common/logging/index.ts b/extensions/ql-vscode/src/common/logging/index.ts index 35ed72fd7..7bc5d444b 100644 --- a/extensions/ql-vscode/src/common/logging/index.ts +++ b/extensions/ql-vscode/src/common/logging/index.ts @@ -1,3 +1,4 @@ export * from "./logger"; +export * from "./tee-logger"; export * from "./vscode/loggers"; export * from "./vscode/output-channel-logger"; diff --git a/extensions/ql-vscode/src/common/logging/logger.ts b/extensions/ql-vscode/src/common/logging/logger.ts index 89fc1e54b..61187585a 100644 --- a/extensions/ql-vscode/src/common/logging/logger.ts +++ b/extensions/ql-vscode/src/common/logging/logger.ts @@ -1,9 +1,6 @@ export interface LogOptions { // If false, don't output a trailing newline for the log entry. Default true. trailingNewline?: boolean; - - // If specified, add this log entry to the log file at the specified location. - additionalLogLocation?: string; } export interface Logger { @@ -25,11 +22,4 @@ export interface Logger { * @param preserveFocus When `true` the channel will not take focus. */ show(preserveFocus?: boolean): void; - - /** - * Remove the log at the specified location. - * - * @param location log to remove - */ - removeAdditionalLogLocation(location: string | undefined): void; } diff --git a/extensions/ql-vscode/src/common/logging/tee-logger.ts b/extensions/ql-vscode/src/common/logging/tee-logger.ts new file mode 100644 index 000000000..ddd6162eb --- /dev/null +++ b/extensions/ql-vscode/src/common/logging/tee-logger.ts @@ -0,0 +1,68 @@ +import { appendFile, ensureFile } from "fs-extra"; +import { isAbsolute } from "path"; +import { getErrorMessage } from "../../pure/helpers-pure"; +import { Logger, LogOptions } from "./logger"; + +/** + * An implementation of {@link Logger} that sends the output both to another {@link Logger} + * and to a file. + * + * The first time a message is written, an additional banner is written to the underlying logger + * pointing the user to the "side log" file. + */ +export class TeeLogger implements Logger { + private emittedRedirectMessage = false; + private error = false; + + public constructor( + private readonly logger: Logger, + private readonly location: string, + ) { + if (!isAbsolute(location)) { + throw new Error( + `Additional Log Location must be an absolute path: ${location}`, + ); + } + } + + async log(message: string, options = {} as LogOptions): Promise { + if (!this.emittedRedirectMessage) { + this.emittedRedirectMessage = true; + const msg = `| Log being saved to ${this.location} |`; + const separator = new Array(msg.length).fill("-").join(""); + await this.logger.log(separator); + await this.logger.log(msg); + await this.logger.log(separator); + } + + if (!this.error) { + try { + const trailingNewline = options.trailingNewline ?? true; + await ensureFile(this.location); + + await appendFile( + this.location, + message + (trailingNewline ? "\n" : ""), + { + encoding: "utf8", + }, + ); + } catch (e) { + // Write an error message to the primary log, and stop trying to write to the side log. + this.error = true; + const errorMessage = getErrorMessage(e); + await this.logger.log( + `Error writing to additional log file: ${errorMessage}`, + ); + } + } + + if (!this.error) { + await this.logger.log(message, options); + } + } + + show(preserveFocus?: boolean): void { + this.logger.show(preserveFocus); + } +} diff --git a/extensions/ql-vscode/src/common/logging/vscode/output-channel-logger.ts b/extensions/ql-vscode/src/common/logging/vscode/output-channel-logger.ts index ed0baa0e2..4ea5db56d 100644 --- a/extensions/ql-vscode/src/common/logging/vscode/output-channel-logger.ts +++ b/extensions/ql-vscode/src/common/logging/vscode/output-channel-logger.ts @@ -1,6 +1,4 @@ import { window as Window, OutputChannel, Progress } from "vscode"; -import { ensureFile, appendFile } from "fs-extra"; -import { isAbsolute } from "path"; import { Logger, LogOptions } from "../logger"; import { DisposableObject } from "../../../pure/disposable-object"; @@ -9,10 +7,6 @@ import { DisposableObject } from "../../../pure/disposable-object"; */ export class OutputChannelLogger extends DisposableObject implements Logger { public readonly outputChannel: OutputChannel; - private readonly additionalLocations = new Map< - string, - AdditionalLogLocation - >(); isCustomLogDirectory: boolean; constructor(title: string) { @@ -32,27 +26,6 @@ export class OutputChannelLogger extends DisposableObject implements Logger { } else { this.outputChannel.append(message); } - - if (options.additionalLogLocation) { - if (!isAbsolute(options.additionalLogLocation)) { - throw new Error( - `Additional Log Location must be an absolute path: ${options.additionalLogLocation}`, - ); - } - const logPath = options.additionalLogLocation; - let additional = this.additionalLocations.get(logPath); - if (!additional) { - const msg = `| Log being saved to ${logPath} |`; - const separator = new Array(msg.length).fill("-").join(""); - this.outputChannel.appendLine(separator); - this.outputChannel.appendLine(msg); - this.outputChannel.appendLine(separator); - additional = new AdditionalLogLocation(logPath); - this.additionalLocations.set(logPath, additional); - } - - await additional.log(message, options); - } } catch (e) { if (e instanceof Error && e.message === "Channel has been closed") { // Output channel is closed logging to console instead @@ -69,31 +42,6 @@ export class OutputChannelLogger extends DisposableObject implements Logger { show(preserveFocus?: boolean): void { this.outputChannel.show(preserveFocus); } - - removeAdditionalLogLocation(location: string | undefined): void { - if (location) { - this.additionalLocations.delete(location); - } - } -} - -class AdditionalLogLocation { - constructor(private location: string) {} - - async log(message: string, options = {} as LogOptions): Promise { - if (options.trailingNewline === undefined) { - options.trailingNewline = true; - } - await ensureFile(this.location); - - await appendFile( - this.location, - message + (options.trailingNewline ? "\n" : ""), - { - encoding: "utf8", - }, - ); - } } export type ProgressReporter = Progress<{ message: string }>; diff --git a/extensions/ql-vscode/src/common/vscode/commands.ts b/extensions/ql-vscode/src/common/vscode/commands.ts new file mode 100644 index 000000000..3d71ae004 --- /dev/null +++ b/extensions/ql-vscode/src/common/vscode/commands.ts @@ -0,0 +1,100 @@ +import { commands, Disposable } from "vscode"; +import { CommandFunction, CommandManager } from "../../packages/commands"; +import { extLogger, OutputChannelLogger } from "../logging"; +import { + asError, + getErrorMessage, + getErrorStack, +} from "../../pure/helpers-pure"; +import { redactableError } from "../../pure/errors"; +import { UserCancellationException } from "../../progress"; +import { + showAndLogExceptionWithTelemetry, + showAndLogWarningMessage, +} from "../../helpers"; +import { telemetryListener } from "../../telemetry"; + +/** + * Create a command manager for VSCode, wrapping registerCommandWithErrorHandling + * and vscode.executeCommand. + */ +export function createVSCodeCommandManager< + Commands extends Record, +>(outputLogger?: OutputChannelLogger): CommandManager { + return new CommandManager((commandId, task) => { + return registerCommandWithErrorHandling(commandId, task, outputLogger); + }, wrapExecuteCommand); +} + +/** + * A wrapper for command registration. This wrapper adds uniform error handling for commands. + * + * @param commandId The ID of the command to register. + * @param task The task to run. It is passed directly to `commands.registerCommand`. Any + * arguments to the command handler are passed on to the task. + */ +export function registerCommandWithErrorHandling( + commandId: string, + task: (...args: any[]) => Promise, + outputLogger = extLogger, +): Disposable { + return commands.registerCommand(commandId, async (...args: any[]) => { + const startTime = Date.now(); + let error: Error | undefined; + + try { + return await task(...args); + } catch (e) { + error = asError(e); + const errorMessage = redactableError(error)`${ + getErrorMessage(e) || e + } (${commandId})`; + const errorStack = getErrorStack(e); + if (e instanceof UserCancellationException) { + // User has cancelled this action manually + if (e.silent) { + void outputLogger.log(errorMessage.fullMessage); + } else { + void showAndLogWarningMessage(errorMessage.fullMessage, { + outputLogger, + }); + } + } else { + // Include the full stack in the error log only. + const fullMessage = errorStack + ? `${errorMessage.fullMessage}\n${errorStack}` + : errorMessage.fullMessage; + void showAndLogExceptionWithTelemetry(errorMessage, { + outputLogger, + fullMessage, + extraTelemetryProperties: { + command: commandId, + }, + }); + } + return undefined; + } finally { + const executionTime = Date.now() - startTime; + telemetryListener?.sendCommandUsage(commandId, executionTime, error); + } + }); +} + +/** + * wrapExecuteCommand wraps commands.executeCommand to satisfy that the + * type is a Promise. Type script does not seem to be smart enough + * to figure out that `ReturnType` is actually + * a Promise, so we need to add a second layer of wrapping and unwrapping + * (The `Promise, + CommandName extends keyof Commands & string = keyof Commands & string, +>( + commandName: CommandName, + ...args: Parameters +): Promise>> { + return await commands.executeCommand< + Awaited> + >(commandName, ...args); +} diff --git a/extensions/ql-vscode/src/common/vscode/vscode-app.ts b/extensions/ql-vscode/src/common/vscode/vscode-app.ts index 8f54341b6..a8f9da973 100644 --- a/extensions/ql-vscode/src/common/vscode/vscode-app.ts +++ b/extensions/ql-vscode/src/common/vscode/vscode-app.ts @@ -3,17 +3,24 @@ import { VSCodeCredentials } from "../../authentication"; import { Disposable } from "../../pure/disposable-object"; import { App, AppMode } from "../app"; import { AppEventEmitter } from "../events"; -import { extLogger, Logger } from "../logging"; +import { extLogger, Logger, queryServerLogger } from "../logging"; import { Memento } from "../memento"; import { VSCodeAppEventEmitter } from "./events"; +import { AppCommandManager, QueryServerCommandManager } from "../commands"; +import { createVSCodeCommandManager } from "./commands"; export class ExtensionApp implements App { public readonly credentials: VSCodeCredentials; + public readonly commands: AppCommandManager; + public readonly queryServerCommands: QueryServerCommandManager; public constructor( public readonly extensionContext: vscode.ExtensionContext, ) { this.credentials = new VSCodeCredentials(); + this.commands = createVSCodeCommandManager(); + this.queryServerCommands = createVSCodeCommandManager(queryServerLogger); + extensionContext.subscriptions.push(this.commands); } public get extensionPath(): string { @@ -54,8 +61,4 @@ export class ExtensionApp implements App { public createEventEmitter(): AppEventEmitter { return new VSCodeAppEventEmitter(); } - - public executeCommand(command: string, ...args: any): Thenable { - return vscode.commands.executeCommand(command, ...args); - } } diff --git a/extensions/ql-vscode/src/compare/compare-view.ts b/extensions/ql-vscode/src/compare/compare-view.ts index 46c35562a..9ffa224a4 100644 --- a/extensions/ql-vscode/src/compare/compare-view.ts +++ b/extensions/ql-vscode/src/compare/compare-view.ts @@ -20,6 +20,8 @@ import { assertNever, getErrorMessage } from "../pure/helpers-pure"; import { HistoryItemLabelProvider } from "../query-history/history-item-label-provider"; import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview"; import { telemetryListener } from "../telemetry"; +import { redactableError } from "../pure/errors"; +import { showAndLogExceptionWithTelemetry } from "../helpers"; interface ComparePair { from: CompletedLocalQueryInfo; @@ -139,6 +141,14 @@ export class CompareView extends AbstractWebview< telemetryListener?.sendUIInteraction(msg.action); break; + case "unhandledError": + void showAndLogExceptionWithTelemetry( + redactableError( + msg.error, + )`Unhandled error in result comparison view: ${msg.error.message}`, + ); + break; + default: assertNever(msg); } diff --git a/extensions/ql-vscode/src/contextual/locationFinder.ts b/extensions/ql-vscode/src/contextual/locationFinder.ts index 86af86211..317861613 100644 --- a/extensions/ql-vscode/src/contextual/locationFinder.ts +++ b/extensions/ql-vscode/src/contextual/locationFinder.ts @@ -11,7 +11,7 @@ import { import { CodeQLCliServer } from "../cli"; import { DatabaseManager, DatabaseItem } from "../local-databases"; import fileRangeFromURI from "./fileRangeFromURI"; -import { ProgressCallback } from "../commandRunner"; +import { ProgressCallback } from "../progress"; import { KeyType } from "./keyType"; import { qlpackOfDatabase, diff --git a/extensions/ql-vscode/src/contextual/queryResolver.ts b/extensions/ql-vscode/src/contextual/queryResolver.ts index 550d1b6b4..40cf87cc6 100644 --- a/extensions/ql-vscode/src/contextual/queryResolver.ts +++ b/extensions/ql-vscode/src/contextual/queryResolver.ts @@ -16,7 +16,7 @@ import { DatabaseItem } from "../local-databases"; import { extLogger } from "../common"; import { createInitialQueryInfo } from "../run-queries-shared"; import { CancellationToken, Uri } from "vscode"; -import { ProgressCallback } from "../commandRunner"; +import { ProgressCallback } from "../progress"; import { QueryRunner } from "../queryRunner"; import { redactableError } from "../pure/errors"; import { QLPACK_FILENAMES } from "../pure/ql"; diff --git a/extensions/ql-vscode/src/contextual/templateProvider.ts b/extensions/ql-vscode/src/contextual/templateProvider.ts index 448d36732..ddc23346c 100644 --- a/extensions/ql-vscode/src/contextual/templateProvider.ts +++ b/extensions/ql-vscode/src/contextual/templateProvider.ts @@ -4,7 +4,6 @@ import { Location, LocationLink, Position, - ProgressLocation, ReferenceContext, ReferenceProvider, TextDocument, @@ -19,7 +18,7 @@ import { import { CodeQLCliServer } from "../cli"; import { DatabaseManager } from "../local-databases"; import { CachedOperation } from "../helpers"; -import { ProgressCallback, withProgress } from "../commandRunner"; +import { ProgressCallback, withProgress } from "../progress"; import AstBuilder from "./astBuilder"; import { KeyType } from "./keyType"; import { @@ -73,11 +72,6 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider { private async getDefinitions(uriString: string): Promise { return withProgress( - { - location: ProgressLocation.Notification, - cancellable: true, - title: "Finding definitions", - }, async (progress, token) => { return getLocationsForUriString( this.cli, @@ -91,6 +85,10 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider { (src, _dest) => src === uriString, ); }, + { + cancellable: true, + title: "Finding definitions", + }, ); } } @@ -136,11 +134,6 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider { private async getReferences(uriString: string): Promise { return withProgress( - { - location: ProgressLocation.Notification, - cancellable: true, - title: "Finding references", - }, async (progress, token) => { return getLocationsForUriString( this.cli, @@ -154,6 +147,10 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider { (src, _dest) => src === uriString, ); }, + { + cancellable: true, + title: "Finding references", + }, ); } } diff --git a/extensions/ql-vscode/src/databaseFetcher.ts b/extensions/ql-vscode/src/databaseFetcher.ts index 1e31e7857..cd6c9acf9 100644 --- a/extensions/ql-vscode/src/databaseFetcher.ts +++ b/extensions/ql-vscode/src/databaseFetcher.ts @@ -1,7 +1,7 @@ import fetch, { Response } from "node-fetch"; import { zip } from "zip-a-folder"; import { Open } from "unzipper"; -import { Uri, CancellationToken, commands, window } from "vscode"; +import { Uri, CancellationToken, window } from "vscode"; import { CodeQLCliServer } from "./cli"; import { ensureDir, @@ -18,7 +18,7 @@ import { retry } from "@octokit/plugin-retry"; import { DatabaseManager, DatabaseItem } from "./local-databases"; import { showAndLogInformationMessage, tmpDir } from "./helpers"; -import { reportStreamProgress, ProgressCallback } from "./commandRunner"; +import { reportStreamProgress, ProgressCallback } from "./progress"; import { extLogger } from "./common"; import { getErrorMessage } from "./pure/helpers-pure"; import { @@ -26,6 +26,7 @@ import { isValidGitHubNwo, } from "./common/github-url-identifier-helper"; import { Credentials } from "./common/authentication"; +import { AppCommandManager } from "./common/commands"; /** * Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file. @@ -34,6 +35,7 @@ import { Credentials } from "./common/authentication"; * @param storagePath where to store the unzipped database. */ export async function promptImportInternetDatabase( + commandManager: AppCommandManager, databaseManager: DatabaseManager, storagePath: string, progress: ProgressCallback, @@ -61,7 +63,7 @@ export async function promptImportInternetDatabase( ); if (item) { - await commands.executeCommand("codeQLDatabases.focus"); + await commandManager.execute("codeQLDatabases.focus"); void showAndLogInformationMessage( "Database downloaded and imported successfully.", ); @@ -78,6 +80,7 @@ export async function promptImportInternetDatabase( * @param storagePath where to store the unzipped database. */ export async function promptImportGithubDatabase( + commandManager: AppCommandManager, databaseManager: DatabaseManager, storagePath: string, credentials: Credentials | undefined, @@ -141,7 +144,7 @@ export async function promptImportGithubDatabase( cli, ); if (item) { - await commands.executeCommand("codeQLDatabases.focus"); + await commandManager.execute("codeQLDatabases.focus"); void showAndLogInformationMessage( "Database downloaded and imported successfully.", ); @@ -158,6 +161,7 @@ export async function promptImportGithubDatabase( * @param storagePath where to store the unzipped database. */ export async function importArchiveDatabase( + commandManager: AppCommandManager, databaseUrl: string, databaseManager: DatabaseManager, storagePath: string, @@ -177,7 +181,7 @@ export async function importArchiveDatabase( cli, ); if (item) { - await commands.executeCommand("codeQLDatabases.focus"); + await commandManager.execute("codeQLDatabases.focus"); void showAndLogInformationMessage( "Database unzipped and imported successfully.", ); diff --git a/extensions/ql-vscode/src/databases/config/db-config-store.ts b/extensions/ql-vscode/src/databases/config/db-config-store.ts index 54a3c549d..3013d164f 100644 --- a/extensions/ql-vscode/src/databases/config/db-config-store.ts +++ b/extensions/ql-vscode/src/databases/config/db-config-store.ts @@ -391,14 +391,14 @@ export class DbConfigStore extends DisposableObject { if (this.configErrors.length === 0) { this.config = newConfig; - await this.app.executeCommand( + await this.app.commands.execute( "setContext", "codeQLVariantAnalysisRepositories.configError", false, ); } else { this.config = undefined; - await this.app.executeCommand( + await this.app.commands.execute( "setContext", "codeQLVariantAnalysisRepositories.configError", true, @@ -426,14 +426,14 @@ export class DbConfigStore extends DisposableObject { if (this.configErrors.length === 0) { this.config = newConfig; - void this.app.executeCommand( + void this.app.commands.execute( "setContext", "codeQLVariantAnalysisRepositories.configError", false, ); } else { this.config = undefined; - void this.app.executeCommand( + void this.app.commands.execute( "setContext", "codeQLVariantAnalysisRepositories.configError", true, diff --git a/extensions/ql-vscode/src/databases/db-module.ts b/extensions/ql-vscode/src/databases/db-module.ts index ceb46779a..8d47f75a8 100644 --- a/extensions/ql-vscode/src/databases/db-module.ts +++ b/extensions/ql-vscode/src/databases/db-module.ts @@ -6,10 +6,12 @@ import { DbConfigStore } from "./config/db-config-store"; import { DbManager } from "./db-manager"; import { DbPanel } from "./ui/db-panel"; import { DbSelectionDecorationProvider } from "./ui/db-selection-decoration-provider"; +import { DatabasePanelCommands } from "../common/commands"; export class DbModule extends DisposableObject { public readonly dbManager: DbManager; private readonly dbConfigStore: DbConfigStore; + private dbPanel: DbPanel | undefined; private constructor(app: App) { super(); @@ -26,15 +28,24 @@ export class DbModule extends DisposableObject { return dbModule; } + public getCommands(): DatabasePanelCommands { + if (!this.dbPanel) { + throw new Error("Database panel not initialized"); + } + + return { + ...this.dbPanel.getCommands(), + }; + } + private async initialize(app: App): Promise { void extLogger.log("Initializing database module"); await this.dbConfigStore.initialize(); - const dbPanel = new DbPanel(this.dbManager, app.credentials); - await dbPanel.initialize(); + this.dbPanel = new DbPanel(app, this.dbManager); - this.push(dbPanel); + this.push(this.dbPanel); this.push(this.dbConfigStore); const dbSelectionDecorationProvider = new DbSelectionDecorationProvider(); diff --git a/extensions/ql-vscode/src/databases/ui/db-panel.ts b/extensions/ql-vscode/src/databases/ui/db-panel.ts index f9eccc5fc..80a0eec5b 100644 --- a/extensions/ql-vscode/src/databases/ui/db-panel.ts +++ b/extensions/ql-vscode/src/databases/ui/db-panel.ts @@ -1,5 +1,4 @@ import { - commands, QuickPickItem, TreeView, TreeViewExpansionEvent, @@ -7,7 +6,7 @@ import { window, workspace, } from "vscode"; -import { commandRunner, UserCancellationException } from "../../commandRunner"; +import { UserCancellationException } from "../../progress"; import { getNwoFromGitHubUrl, isValidGitHubNwo, @@ -31,7 +30,8 @@ import { DbTreeViewItem } from "./db-tree-view-item"; import { getGitHubUrl } from "./db-tree-view-item-action"; import { getControllerRepo } from "../../variant-analysis/run-remote-query"; import { getErrorMessage } from "../../pure/helpers-pure"; -import { Credentials } from "../../common/authentication"; +import { DatabasePanelCommands } from "../../common/commands"; +import { App } from "../../common/app"; export interface RemoteDatabaseQuickPickItem extends QuickPickItem { kind: string; @@ -46,8 +46,8 @@ export class DbPanel extends DisposableObject { private readonly treeView: TreeView; public constructor( + private readonly app: App, private readonly dbManager: DbManager, - private readonly credentials: Credentials, ) { super(); @@ -72,58 +72,28 @@ export class DbPanel extends DisposableObject { this.push(this.treeView); } - public async initialize(): Promise { - this.push( - commandRunner("codeQLVariantAnalysisRepositories.openConfigFile", () => - this.openConfigFile(), - ), - ); - this.push( - commandRunner("codeQLVariantAnalysisRepositories.addNewDatabase", () => - this.addNewRemoteDatabase(), - ), - ); - this.push( - commandRunner("codeQLVariantAnalysisRepositories.addNewList", () => - this.addNewList(), - ), - ); - this.push( - commandRunner( - "codeQLVariantAnalysisRepositories.setSelectedItem", - (treeViewItem: DbTreeViewItem) => this.setSelectedItem(treeViewItem), - ), - ); - this.push( - commandRunner( - "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu", - (treeViewItem: DbTreeViewItem) => this.setSelectedItem(treeViewItem), - ), - ); - this.push( - commandRunner( - "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu", - (treeViewItem: DbTreeViewItem) => this.openOnGitHub(treeViewItem), - ), - ); - this.push( - commandRunner( - "codeQLVariantAnalysisRepositories.renameItemContextMenu", - (treeViewItem: DbTreeViewItem) => this.renameItem(treeViewItem), - ), - ); - this.push( - commandRunner( - "codeQLVariantAnalysisRepositories.removeItemContextMenu", - (treeViewItem: DbTreeViewItem) => this.removeItem(treeViewItem), - ), - ); - this.push( - commandRunner( - "codeQLVariantAnalysisRepositories.setupControllerRepository", - () => this.setupControllerRepository(), - ), - ); + public getCommands(): DatabasePanelCommands { + return { + "codeQLVariantAnalysisRepositories.openConfigFile": + this.openConfigFile.bind(this), + "codeQLVariantAnalysisRepositories.addNewDatabase": + this.addNewRemoteDatabase.bind(this), + "codeQLVariantAnalysisRepositories.addNewList": + this.addNewList.bind(this), + "codeQLVariantAnalysisRepositories.setupControllerRepository": + this.setupControllerRepository.bind(this), + + "codeQLVariantAnalysisRepositories.setSelectedItem": + this.setSelectedItem.bind(this), + "codeQLVariantAnalysisRepositories.setSelectedItemContextMenu": + this.setSelectedItem.bind(this), + "codeQLVariantAnalysisRepositories.openOnGitHubContextMenu": + this.openOnGitHub.bind(this), + "codeQLVariantAnalysisRepositories.renameItemContextMenu": + this.renameItem.bind(this), + "codeQLVariantAnalysisRepositories.removeItemContextMenu": + this.removeItem.bind(this), + }; } private async openConfigFile(): Promise { @@ -398,13 +368,13 @@ export class DbPanel extends DisposableObject { ); } - await commands.executeCommand("vscode.open", Uri.parse(githubUrl)); + await this.app.commands.execute("vscode.open", Uri.parse(githubUrl)); } private async setupControllerRepository(): Promise { try { // This will also validate that the controller repository is valid - await getControllerRepo(this.credentials); + await getControllerRepo(this.app.credentials); } catch (e: unknown) { if (e instanceof UserCancellationException) { return; diff --git a/extensions/ql-vscode/src/distribution.ts b/extensions/ql-vscode/src/distribution.ts index 119c82244..017da698d 100644 --- a/extensions/ql-vscode/src/distribution.ts +++ b/extensions/ql-vscode/src/distribution.ts @@ -14,7 +14,7 @@ import { } from "./helpers"; import { extLogger } from "./common"; import { getCodeQlCliVersion } from "./cli-version"; -import { ProgressCallback, reportStreamProgress } from "./commandRunner"; +import { ProgressCallback, reportStreamProgress } from "./progress"; import { codeQlLauncherName, deprecatedCodeQlLauncherName, diff --git a/extensions/ql-vscode/src/eval-log-viewer.ts b/extensions/ql-vscode/src/eval-log-viewer.ts index c2f4b129b..b71d6c22f 100644 --- a/extensions/ql-vscode/src/eval-log-viewer.ts +++ b/extensions/ql-vscode/src/eval-log-viewer.ts @@ -8,11 +8,11 @@ import { EventEmitter, TreeItemCollapsibleState, } from "vscode"; -import { commandRunner } from "./commandRunner"; import { DisposableObject } from "./pure/disposable-object"; import { showAndLogExceptionWithTelemetry } from "./helpers"; import { asError, getErrorMessage } from "./pure/helpers-pure"; import { redactableError } from "./pure/errors"; +import { EvalLogViewerCommands } from "./common/commands"; export interface EvalLogTreeItem { label?: string; @@ -80,11 +80,12 @@ export class EvalLogViewer extends DisposableObject { this.push(this.treeView); this.push(this.treeDataProvider); - this.push( - commandRunner("codeQLEvalLogViewer.clear", async () => { - this.clear(); - }), - ); + } + + public getCommands(): EvalLogViewerCommands { + return { + "codeQLEvalLogViewer.clear": async () => this.clear(), + }; } private clear(): void { diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index e1ca5caef..a3fc19032 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -1,27 +1,21 @@ import "source-map-support/register"; import { CancellationToken, - CancellationTokenSource, - commands, Disposable, env, ExtensionContext, extensions, languages, ProgressLocation, - ProgressOptions, - QuickPickItem, - Range, Uri, version as vscodeVersion, window as Window, - window, workspace, } from "vscode"; import { LanguageClient } from "vscode-languageclient/node"; import { arch, platform } from "os"; import { ensureDir } from "fs-extra"; -import { basename, join } from "path"; +import { join } from "path"; import { dirSync } from "tmp-promise"; import { testExplorerExtensionId, TestHub } from "vscode-test-adapter-api"; import { lt, parse } from "semver"; @@ -36,14 +30,12 @@ import { CodeQLCliServer } from "./cli"; import { CliConfigListener, DistributionConfigListener, - isCanary, joinOrderWarningThreshold, - MAX_QUERIES, QueryHistoryConfigListener, QueryServerConfigListener, } from "./config"; import { install } from "./languageSupport"; -import { DatabaseItem, DatabaseManager } from "./local-databases"; +import { DatabaseManager } from "./local-databases"; import { DatabaseUI } from "./local-databases-ui"; import { TemplatePrintAstProvider, @@ -62,7 +54,6 @@ import { GithubRateLimitedError, } from "./distribution"; import { - findLanguage, showAndLogErrorMessage, showAndLogExceptionWithTelemetry, showAndLogInformationMessage, @@ -71,6 +62,7 @@ import { showInformationMessageWithAction, tmpDir, tmpDirDisposal, + prepareCodeTour, } from "./helpers"; import { asError, @@ -88,55 +80,49 @@ import { queryServerLogger, } from "./common"; import { QueryHistoryManager } from "./query-history/query-history-manager"; -import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results"; +import { CompletedLocalQueryInfo } from "./query-results"; import { QueryServerClient as LegacyQueryServerClient } from "./legacy-query-server/queryserver-client"; import { QueryServerClient } from "./query-server/queryserver-client"; -import { displayQuickQuery } from "./quick-query"; import { QLTestAdapterFactory } from "./test-adapter"; import { TestUIService } from "./test-ui"; import { CompareView } from "./compare/compare-view"; -import { gatherQlFiles } from "./pure/files"; import { initializeTelemetry } from "./telemetry"; -import { - commandRunner, - commandRunnerWithProgress, - ProgressCallback, - ProgressUpdate, - withProgress, -} from "./commandRunner"; +import { ProgressCallback, withProgress } from "./progress"; import { CodeQlStatusBarHandler } from "./status-bar"; -import { - handleDownloadPacks, - handleInstallPackDependencies, -} from "./packaging"; +import { getPackagingCommands } from "./packaging"; import { HistoryItemLabelProvider } from "./query-history/history-item-label-provider"; -import { - exportSelectedVariantAnalysisResults, - exportVariantAnalysisResults, -} from "./variant-analysis/export-results"; import { EvalLogViewer } from "./eval-log-viewer"; import { SummaryLanguageSupport } from "./log-insights/summary-language-support"; import { JoinOrderScannerProvider } from "./log-insights/join-order"; import { LogScannerService } from "./log-insights/log-scanner-service"; -import { createInitialQueryInfo } from "./run-queries-shared"; import { LegacyQueryRunner } from "./legacy-query-server/legacyRunner"; import { NewQueryRunner } from "./query-server/query-runner"; import { QueryRunner } from "./queryRunner"; import { VariantAnalysisView } from "./variant-analysis/variant-analysis-view"; import { VariantAnalysisViewSerializer } from "./variant-analysis/variant-analysis-view-serializer"; -import { - VariantAnalysis, - VariantAnalysisScannedRepository, -} from "./variant-analysis/shared/variant-analysis"; import { VariantAnalysisManager } from "./variant-analysis/variant-analysis-manager"; import { createVariantAnalysisContentProvider } from "./variant-analysis/variant-analysis-content-provider"; import { VSCodeMockGitHubApiServer } from "./mocks/vscode-mock-gh-api-server"; import { VariantAnalysisResultsManager } from "./variant-analysis/variant-analysis-results-manager"; import { ExtensionApp } from "./common/vscode/vscode-app"; -import { RepositoriesFilterSortStateWithIds } from "./pure/variant-analysis-filter-sort"; import { DbModule } from "./databases/db-module"; import { redactableError } from "./pure/errors"; import { QueryHistoryDirs } from "./query-history/query-history-dirs"; +import { + AllExtensionCommands, + BaseCommands, + PreActivationCommands, + QueryServerCommands, + TestUICommands, +} from "./common/commands"; +import { + getLocalQueryCommands, + showResultsForCompletedQuery, +} from "./local-queries"; +import { getAstCfgCommands } from "./ast-cfg-commands"; +import { getQueryEditorCommands } from "./query-editor"; +import { App } from "./common/app"; +import { registerCommandWithErrorHandling } from "./common/vscode/commands"; /** * extension.ts @@ -168,6 +154,64 @@ let isInstallingOrUpdatingDistribution = false; const extensionId = "GitHub.vscode-codeql"; const extension = extensions.getExtension(extensionId); +/** + * Return all commands that are not tied to the more specific managers. + */ +function getCommands( + app: App, + cliServer: CodeQLCliServer, + queryRunner: QueryRunner, +): BaseCommands { + const getCliVersion = async () => { + try { + return await cliServer.getVersion(); + } catch { + return ""; + } + }; + + return { + "codeQL.openDocumentation": async () => { + await env.openExternal(Uri.parse("https://codeql.github.com/docs/")); + }, + "codeQL.restartQueryServer": async () => + withProgress( + async (progress: ProgressCallback, token: CancellationToken) => { + // We restart the CLI server too, to ensure they are the same version + cliServer.restartCliServer(); + await queryRunner.restartQueryServer(progress, token); + void showAndLogInformationMessage("CodeQL Query Server restarted.", { + outputLogger: queryServerLogger, + }); + }, + { + title: "Restarting Query Server", + }, + ), + "codeQL.copyVersion": async () => { + const text = `CodeQL extension version: ${ + extension?.packageJSON.version + } \nCodeQL CLI version: ${await getCliVersion()} \nPlatform: ${platform()} ${arch()}`; + await env.clipboard.writeText(text); + void showAndLogInformationMessage(text); + }, + "codeQL.authenticateToGitHub": async () => { + /** + * Credentials for authenticating to GitHub. + * These are used when making API calls. + */ + const octokit = await app.credentials.getOctokit(); + const userInfo = await octokit.users.getAuthenticated(); + void showAndLogInformationMessage( + `Authenticated to GitHub as user: ${userInfo.data.login}`, + ); + }, + "codeQL.showLogs": async () => { + extLogger.show(); + }, + }; +} + /** * If the user tries to execute vscode commands after extension activation is failed, give * a sensible error message. @@ -192,7 +236,11 @@ function registerErrorStubs( stubbedCommands.forEach((command) => { if (excludedCommands.indexOf(command) === -1) { - errorStubs.push(commandRunner(command, stubGenerator(command))); + // This is purposefully using `registerCommandWithErrorHandling` instead of the command manager because these + // commands are untyped and registered pre-activation. + errorStubs.push( + registerCommandWithErrorHandling(command, stubGenerator(command)), + ); } }); } @@ -212,6 +260,16 @@ export interface CodeQLExtensionInterface { readonly dispose: () => void; } +interface DistributionUpdateConfig { + isUserInitiated: boolean; + shouldDisplayMessageWhenNoUpdates: boolean; + allowAutoUpdating: boolean; +} + +const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation"; + +const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE; + // This is the minimum version of vscode that we _want_ to support. We want to update the language server library, but that // requires 1.67 or later. If we change the minimum version in the package.json, then anyone on an older version of vscode // silently be unable to upgrade. So, the solution is to first bump the minimum version here and release. Then @@ -243,6 +301,8 @@ export async function activate( addUnhandledRejectionListener(); install(); + const app = new ExtensionApp(ctx); + const codelensProvider = new QuickEvalCodeLensProvider(); languages.registerCodeLensProvider( { scheme: "file", language: "ql" }, @@ -250,15 +310,12 @@ export async function activate( ); ctx.subscriptions.push(distributionConfigListener); - const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE; const distributionManager = new DistributionManager( distributionConfigListener, codeQlVersionRange, ctx, ); - const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation"; - registerErrorStubs([checkForUpdatesCommand], (command) => async () => { void showAndLogErrorMessage( `Can't execute ${command}: waiting to finish loading CodeQL CLI.`, @@ -268,239 +325,36 @@ export async function activate( // Checking the vscode version should not block extension activation. void assertVSCodeVersionGreaterThan(MIN_VERSION, ctx); - interface DistributionUpdateConfig { - isUserInitiated: boolean; - shouldDisplayMessageWhenNoUpdates: boolean; - allowAutoUpdating: boolean; - } - - async function installOrUpdateDistributionWithProgressTitle( - progressTitle: string, - config: DistributionUpdateConfig, - ): Promise { - const minSecondsSinceLastUpdateCheck = config.isUserInitiated ? 0 : 86400; - const noUpdatesLoggingFunc = config.shouldDisplayMessageWhenNoUpdates - ? showAndLogInformationMessage - : async (message: string) => void extLogger.log(message); - const result = - await distributionManager.checkForUpdatesToExtensionManagedDistribution( - minSecondsSinceLastUpdateCheck, - ); - - // We do want to auto update if there is no distribution at all - const allowAutoUpdating = - config.allowAutoUpdating || - !(await distributionManager.hasDistribution()); - - switch (result.kind) { - case DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult: - void extLogger.log( - "Didn't perform CodeQL CLI update check since a check was already performed within the previous " + - `${minSecondsSinceLastUpdateCheck} seconds.`, - ); - break; - case DistributionUpdateCheckResultKind.AlreadyUpToDate: - await noUpdatesLoggingFunc("CodeQL CLI already up to date."); - break; - case DistributionUpdateCheckResultKind.InvalidLocation: - await noUpdatesLoggingFunc( - "CodeQL CLI is installed externally so could not be updated.", - ); - break; - case DistributionUpdateCheckResultKind.UpdateAvailable: - if (beganMainExtensionActivation || !allowAutoUpdating) { - const updateAvailableMessage = - `Version "${result.updatedRelease.name}" of the CodeQL CLI is now available. ` + - "Do you wish to upgrade?"; - await ctx.globalState.update(shouldUpdateOnNextActivationKey, true); - if ( - await showInformationMessageWithAction( - updateAvailableMessage, - "Restart and Upgrade", - ) - ) { - await commands.executeCommand("workbench.action.reloadWindow"); - } - } else { - const progressOptions: ProgressOptions = { - title: progressTitle, - location: ProgressLocation.Notification, - }; - - await withProgress(progressOptions, (progress) => - distributionManager.installExtensionManagedDistributionRelease( - result.updatedRelease, - progress, - ), - ); - - await ctx.globalState.update(shouldUpdateOnNextActivationKey, false); - void showAndLogInformationMessage( - `CodeQL CLI updated to version "${result.updatedRelease.name}".`, - ); - } - break; - default: - assertNever(result); - } - } - - async function installOrUpdateDistribution( - config: DistributionUpdateConfig, - ): Promise { - if (isInstallingOrUpdatingDistribution) { - throw new Error("Already installing or updating CodeQL CLI"); - } - isInstallingOrUpdatingDistribution = true; - const codeQlInstalled = - (await distributionManager.getCodeQlPathWithoutVersionCheck()) !== - undefined; - const willUpdateCodeQl = ctx.globalState.get( - shouldUpdateOnNextActivationKey, - ); - const messageText = willUpdateCodeQl - ? "Updating CodeQL CLI" - : codeQlInstalled - ? "Checking for updates to CodeQL CLI" - : "Installing CodeQL CLI"; - - try { - await installOrUpdateDistributionWithProgressTitle(messageText, config); - } catch (e) { - // Don't rethrow the exception, because if the config is changed, we want to be able to retry installing - // or updating the distribution. - const alertFunction = - codeQlInstalled && !config.isUserInitiated - ? showAndLogWarningMessage - : showAndLogErrorMessage; - const taskDescription = `${ - willUpdateCodeQl - ? "update" - : codeQlInstalled - ? "check for updates to" - : "install" - } CodeQL CLI`; - - if (e instanceof GithubRateLimitedError) { - void alertFunction( - `Rate limited while trying to ${taskDescription}. Please try again after ` + - `your rate limit window resets at ${e.rateLimitResetDate.toLocaleString( - env.language, - )}.`, - ); - } else if (e instanceof GithubApiError) { - void alertFunction( - `Encountered GitHub API error while trying to ${taskDescription}. ${e}`, - ); - } - void alertFunction(`Unable to ${taskDescription}. ${e}`); - } finally { - isInstallingOrUpdatingDistribution = false; - } - } - - async function getDistributionDisplayingDistributionWarnings(): Promise { - const result = await distributionManager.getDistribution(); - switch (result.kind) { - case FindDistributionResultKind.CompatibleDistribution: - void extLogger.log( - `Found compatible version of CodeQL CLI (version ${result.version.raw})`, - ); - break; - case FindDistributionResultKind.IncompatibleDistribution: { - const fixGuidanceMessage = (() => { - switch (result.distribution.kind) { - case DistributionKind.ExtensionManaged: - return 'Please update the CodeQL CLI by running the "CodeQL: Check for CLI Updates" command.'; - case DistributionKind.CustomPathConfig: - return `Please update the \"CodeQL CLI Executable Path\" setting to point to a CLI in the version range ${codeQlVersionRange}.`; - case DistributionKind.PathEnvironmentVariable: - return ( - `Please update the CodeQL CLI on your PATH to a version compatible with ${codeQlVersionRange}, or ` + - `set the \"CodeQL CLI Executable Path\" setting to the path of a CLI version compatible with ${codeQlVersionRange}.` - ); - } - })(); - - void showAndLogWarningMessage( - `The current version of the CodeQL CLI (${result.version.raw}) ` + - `is incompatible with this extension. ${fixGuidanceMessage}`, - ); - break; - } - case FindDistributionResultKind.UnknownCompatibilityDistribution: - void showAndLogWarningMessage( - "Compatibility with the configured CodeQL CLI could not be determined. " + - "You may experience problems using the extension.", - ); - break; - case FindDistributionResultKind.NoDistribution: - void showAndLogErrorMessage("The CodeQL CLI could not be found."); - break; - default: - assertNever(result); - } - return result; - } - - async function installOrUpdateThenTryActivate( - config: DistributionUpdateConfig, - ): Promise> { - await installOrUpdateDistribution(config); - - // Display the warnings even if the extension has already activated. - const distributionResult = - await getDistributionDisplayingDistributionWarnings(); - let extensionInterface: CodeQLExtensionInterface | Record = - {}; - if ( - !beganMainExtensionActivation && - distributionResult.kind !== FindDistributionResultKind.NoDistribution - ) { - extensionInterface = await activateWithInstalledDistribution( - ctx, - distributionManager, - distributionConfigListener, - ); - } else if ( - distributionResult.kind === FindDistributionResultKind.NoDistribution - ) { - registerErrorStubs([checkForUpdatesCommand], (command) => async () => { - const installActionName = "Install CodeQL CLI"; - const chosenAction = await showAndLogErrorMessage( - `Can't execute ${command}: missing CodeQL CLI.`, - { - items: [installActionName], - }, - ); - if (chosenAction === installActionName) { - await installOrUpdateThenTryActivate({ - isUserInitiated: true, - shouldDisplayMessageWhenNoUpdates: false, - allowAutoUpdating: true, - }); - } - }); - } - return extensionInterface; - } - ctx.subscriptions.push( distributionConfigListener.onDidChangeConfiguration(() => - installOrUpdateThenTryActivate({ - isUserInitiated: true, - shouldDisplayMessageWhenNoUpdates: false, - allowAutoUpdating: true, - }), + installOrUpdateThenTryActivate( + ctx, + app, + distributionManager, + distributionConfigListener, + { + isUserInitiated: true, + shouldDisplayMessageWhenNoUpdates: false, + allowAutoUpdating: true, + }, + ), ), ); ctx.subscriptions.push( - commandRunner(checkForUpdatesCommand, () => - installOrUpdateThenTryActivate({ - isUserInitiated: true, - shouldDisplayMessageWhenNoUpdates: true, - allowAutoUpdating: true, - }), + // This is purposefully using `registerCommandWithErrorHandling` directly instead of the command manager + // because this command is registered pre-activation. + registerCommandWithErrorHandling(checkForUpdatesCommand, () => + installOrUpdateThenTryActivate( + ctx, + app, + distributionManager, + distributionConfigListener, + { + isUserInitiated: true, + shouldDisplayMessageWhenNoUpdates: true, + allowAutoUpdating: true, + }, + ), ), ); @@ -510,14 +364,20 @@ export async function activate( variantAnalysisViewSerializer, ); - const codeQlExtension = await installOrUpdateThenTryActivate({ - isUserInitiated: !!ctx.globalState.get(shouldUpdateOnNextActivationKey), - shouldDisplayMessageWhenNoUpdates: false, + const codeQlExtension = await installOrUpdateThenTryActivate( + ctx, + app, + distributionManager, + distributionConfigListener, + { + isUserInitiated: !!ctx.globalState.get(shouldUpdateOnNextActivationKey), + shouldDisplayMessageWhenNoUpdates: false, - // only auto update on startup if the user has previously requested an update - // otherwise, ask user to accept the update - allowAutoUpdating: !!ctx.globalState.get(shouldUpdateOnNextActivationKey), - }); + // only auto update on startup if the user has previously requested an update + // otherwise, ask user to accept the update + allowAutoUpdating: !!ctx.globalState.get(shouldUpdateOnNextActivationKey), + }, + ); variantAnalysisViewSerializer.onExtensionLoaded( codeQlExtension.variantAnalysisManager, @@ -526,6 +386,245 @@ export async function activate( return codeQlExtension; } +async function installOrUpdateDistributionWithProgressTitle( + ctx: ExtensionContext, + app: ExtensionApp, + distributionManager: DistributionManager, + progressTitle: string, + config: DistributionUpdateConfig, +): Promise { + const minSecondsSinceLastUpdateCheck = config.isUserInitiated ? 0 : 86400; + const noUpdatesLoggingFunc = config.shouldDisplayMessageWhenNoUpdates + ? showAndLogInformationMessage + : async (message: string) => void extLogger.log(message); + const result = + await distributionManager.checkForUpdatesToExtensionManagedDistribution( + minSecondsSinceLastUpdateCheck, + ); + + // We do want to auto update if there is no distribution at all + const allowAutoUpdating = + config.allowAutoUpdating || !(await distributionManager.hasDistribution()); + + switch (result.kind) { + case DistributionUpdateCheckResultKind.AlreadyCheckedRecentlyResult: + void extLogger.log( + "Didn't perform CodeQL CLI update check since a check was already performed within the previous " + + `${minSecondsSinceLastUpdateCheck} seconds.`, + ); + break; + case DistributionUpdateCheckResultKind.AlreadyUpToDate: + await noUpdatesLoggingFunc("CodeQL CLI already up to date."); + break; + case DistributionUpdateCheckResultKind.InvalidLocation: + await noUpdatesLoggingFunc( + "CodeQL CLI is installed externally so could not be updated.", + ); + break; + case DistributionUpdateCheckResultKind.UpdateAvailable: + if (beganMainExtensionActivation || !allowAutoUpdating) { + const updateAvailableMessage = + `Version "${result.updatedRelease.name}" of the CodeQL CLI is now available. ` + + "Do you wish to upgrade?"; + await ctx.globalState.update(shouldUpdateOnNextActivationKey, true); + if ( + await showInformationMessageWithAction( + updateAvailableMessage, + "Restart and Upgrade", + ) + ) { + await app.commands.execute("workbench.action.reloadWindow"); + } + } else { + await withProgress( + (progress) => + distributionManager.installExtensionManagedDistributionRelease( + result.updatedRelease, + progress, + ), + { + title: progressTitle, + }, + ); + + await ctx.globalState.update(shouldUpdateOnNextActivationKey, false); + void showAndLogInformationMessage( + `CodeQL CLI updated to version "${result.updatedRelease.name}".`, + ); + } + break; + default: + assertNever(result); + } +} + +async function installOrUpdateDistribution( + ctx: ExtensionContext, + app: ExtensionApp, + distributionManager: DistributionManager, + config: DistributionUpdateConfig, +): Promise { + if (isInstallingOrUpdatingDistribution) { + throw new Error("Already installing or updating CodeQL CLI"); + } + isInstallingOrUpdatingDistribution = true; + const codeQlInstalled = + (await distributionManager.getCodeQlPathWithoutVersionCheck()) !== + undefined; + const willUpdateCodeQl = ctx.globalState.get(shouldUpdateOnNextActivationKey); + const messageText = willUpdateCodeQl + ? "Updating CodeQL CLI" + : codeQlInstalled + ? "Checking for updates to CodeQL CLI" + : "Installing CodeQL CLI"; + + try { + await installOrUpdateDistributionWithProgressTitle( + ctx, + app, + distributionManager, + messageText, + config, + ); + } catch (e) { + // Don't rethrow the exception, because if the config is changed, we want to be able to retry installing + // or updating the distribution. + const alertFunction = + codeQlInstalled && !config.isUserInitiated + ? showAndLogWarningMessage + : showAndLogErrorMessage; + const taskDescription = `${ + willUpdateCodeQl + ? "update" + : codeQlInstalled + ? "check for updates to" + : "install" + } CodeQL CLI`; + + if (e instanceof GithubRateLimitedError) { + void alertFunction( + `Rate limited while trying to ${taskDescription}. Please try again after ` + + `your rate limit window resets at ${e.rateLimitResetDate.toLocaleString( + env.language, + )}.`, + ); + } else if (e instanceof GithubApiError) { + void alertFunction( + `Encountered GitHub API error while trying to ${taskDescription}. ${e}`, + ); + } + void alertFunction(`Unable to ${taskDescription}. ${e}`); + } finally { + isInstallingOrUpdatingDistribution = false; + } +} + +async function getDistributionDisplayingDistributionWarnings( + distributionManager: DistributionManager, +): Promise { + const result = await distributionManager.getDistribution(); + switch (result.kind) { + case FindDistributionResultKind.CompatibleDistribution: + void extLogger.log( + `Found compatible version of CodeQL CLI (version ${result.version.raw})`, + ); + break; + case FindDistributionResultKind.IncompatibleDistribution: { + const fixGuidanceMessage = (() => { + switch (result.distribution.kind) { + case DistributionKind.ExtensionManaged: + return 'Please update the CodeQL CLI by running the "CodeQL: Check for CLI Updates" command.'; + case DistributionKind.CustomPathConfig: + return `Please update the \"CodeQL CLI Executable Path\" setting to point to a CLI in the version range ${codeQlVersionRange}.`; + case DistributionKind.PathEnvironmentVariable: + return ( + `Please update the CodeQL CLI on your PATH to a version compatible with ${codeQlVersionRange}, or ` + + `set the \"CodeQL CLI Executable Path\" setting to the path of a CLI version compatible with ${codeQlVersionRange}.` + ); + } + })(); + + void showAndLogWarningMessage( + `The current version of the CodeQL CLI (${result.version.raw}) ` + + `is incompatible with this extension. ${fixGuidanceMessage}`, + ); + break; + } + case FindDistributionResultKind.UnknownCompatibilityDistribution: + void showAndLogWarningMessage( + "Compatibility with the configured CodeQL CLI could not be determined. " + + "You may experience problems using the extension.", + ); + break; + case FindDistributionResultKind.NoDistribution: + void showAndLogErrorMessage("The CodeQL CLI could not be found."); + break; + default: + assertNever(result); + } + return result; +} + +async function installOrUpdateThenTryActivate( + ctx: ExtensionContext, + app: ExtensionApp, + distributionManager: DistributionManager, + distributionConfigListener: DistributionConfigListener, + config: DistributionUpdateConfig, +): Promise> { + await installOrUpdateDistribution(ctx, app, distributionManager, config); + + try { + await prepareCodeTour(app.commands); + } catch (e: unknown) { + void extLogger.log( + `Could not open tutorial workspace automatically: ${getErrorMessage(e)}`, + ); + } + + // Display the warnings even if the extension has already activated. + const distributionResult = + await getDistributionDisplayingDistributionWarnings(distributionManager); + let extensionInterface: CodeQLExtensionInterface | Record = {}; + if ( + !beganMainExtensionActivation && + distributionResult.kind !== FindDistributionResultKind.NoDistribution + ) { + extensionInterface = await activateWithInstalledDistribution( + ctx, + app, + distributionManager, + distributionConfigListener, + ); + } else if ( + distributionResult.kind === FindDistributionResultKind.NoDistribution + ) { + registerErrorStubs([checkForUpdatesCommand], (command) => async () => { + const installActionName = "Install CodeQL CLI"; + const chosenAction = await showAndLogErrorMessage( + `Can't execute ${command}: missing CodeQL CLI.`, + { + items: [installActionName], + }, + ); + if (chosenAction === installActionName) { + await installOrUpdateThenTryActivate( + ctx, + app, + distributionManager, + distributionConfigListener, + { + isUserInitiated: true, + shouldDisplayMessageWhenNoUpdates: false, + allowAutoUpdating: true, + }, + ); + } + }); + } + return extensionInterface; +} + const PACK_GLOBS = [ "**/codeql-pack.yml", "**/qlpack.yml", @@ -538,6 +637,7 @@ const PACK_GLOBS = [ async function activateWithInstalledDistribution( ctx: ExtensionContext, + app: ExtensionApp, distributionManager: DistributionManager, distributionConfigListener: DistributionConfigListener, ): Promise { @@ -546,8 +646,6 @@ async function activateWithInstalledDistribution( // of activation. errorStubs.forEach((stub) => stub.dispose()); - const app = new ExtensionApp(ctx); - void extLogger.log("Initializing configuration listener..."); const qlConfigurationListener = await QueryServerConfigListener.createQueryServerConfigListener( @@ -571,7 +669,12 @@ async function activateWithInstalledDistribution( ctx.subscriptions.push(statusBar); void extLogger.log("Initializing query server client."); - const qs = await createQueryServer(qlConfigurationListener, cliServer, ctx); + const qs = await createQueryServer( + app, + qlConfigurationListener, + cliServer, + ctx, + ); for (const glob of PACK_GLOBS) { const fsWatcher = workspace.createFileSystemWatcher(glob); @@ -582,7 +685,7 @@ async function activateWithInstalledDistribution( } void extLogger.log("Initializing database manager."); - const dbm = new DatabaseManager(ctx, qs, cliServer, extLogger); + const dbm = new DatabaseManager(ctx, app, qs, cliServer, extLogger); // Let this run async. void dbm.loadPersistedState(); @@ -596,7 +699,6 @@ async function activateWithInstalledDistribution( getContextStoragePath(ctx), ctx.extensionPath, ); - databaseUI.init(); ctx.subscriptions.push(databaseUI); void extLogger.log("Initializing evaluator log viewer."); @@ -607,9 +709,22 @@ async function activateWithInstalledDistribution( const queryHistoryConfigurationListener = new QueryHistoryConfigListener(); ctx.subscriptions.push(queryHistoryConfigurationListener); const showResults = async (item: CompletedLocalQueryInfo) => - showResultsForCompletedQuery(item, WebviewReveal.Forced); + showResultsForCompletedQuery( + localQueryResultsView, + item, + WebviewReveal.Forced, + ); const queryStorageDir = join(ctx.globalStorageUri.fsPath, "queries"); await ensureDir(queryStorageDir); + + // Store contextual queries in a temporary folder so that they are removed + // when the application closes. There is no need for the user to interact with them. + const contextualQueryStorageDir = join( + tmpDir.name, + "contextual-query-storage", + ); + await ensureDir(contextualQueryStorageDir); + const labelProvider = new HistoryItemLabelProvider( queryHistoryConfigurationListener, ); @@ -662,6 +777,7 @@ async function activateWithInstalledDistribution( }; const qhm = new QueryHistoryManager( + app, qs, dbm, localQueryResultsView, @@ -672,7 +788,7 @@ async function activateWithInstalledDistribution( queryHistoryConfigurationListener, labelProvider, async (from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo) => - showResultsForComparison(from, to), + showResultsForComparison(compareView, from, to), ); ctx.subscriptions.push(qhm); @@ -700,157 +816,6 @@ async function activateWithInstalledDistribution( void extLogger.log("Initializing source archive filesystem provider."); archiveFilesystemProvider_activate(ctx); - async function showResultsForComparison( - from: CompletedLocalQueryInfo, - to: CompletedLocalQueryInfo, - ): Promise { - try { - await compareView.showResults(from, to); - } catch (e) { - void showAndLogExceptionWithTelemetry( - redactableError(asError(e))`Failed to show results: ${getErrorMessage( - e, - )}`, - ); - } - } - - async function showResultsForCompletedQuery( - query: CompletedLocalQueryInfo, - forceReveal: WebviewReveal, - ): Promise { - await localQueryResultsView.showResults(query, forceReveal, false); - } - - async function compileAndRunQuery( - quickEval: boolean, - selectedQuery: Uri | undefined, - progress: ProgressCallback, - token: CancellationToken, - databaseItem: DatabaseItem | undefined, - range?: Range, - ): Promise { - if (qs !== undefined) { - // If no databaseItem is specified, use the database currently selected in the Databases UI - databaseItem = - databaseItem || (await databaseUI.getDatabaseItem(progress, token)); - if (databaseItem === undefined) { - throw new Error("Can't run query without a selected database"); - } - const databaseInfo = { - name: databaseItem.name, - databaseUri: databaseItem.databaseUri.toString(), - }; - - // handle cancellation from the history view. - const source = new CancellationTokenSource(); - token.onCancellationRequested(() => source.cancel()); - - const initialInfo = await createInitialQueryInfo( - selectedQuery, - databaseInfo, - quickEval, - range, - ); - const item = new LocalQueryInfo(initialInfo, source); - qhm.addQuery(item); - try { - const completedQueryInfo = await qs.compileAndRunQueryAgainstDatabase( - databaseItem, - initialInfo, - queryStorageDir, - progress, - source.token, - undefined, - item, - ); - qhm.completeQuery(item, completedQueryInfo); - await showResultsForCompletedQuery( - item as CompletedLocalQueryInfo, - WebviewReveal.Forced, - ); - // Note we must update the query history view after showing results as the - // display and sorting might depend on the number of results - } catch (e) { - const err = asError(e); - err.message = `Error running query: ${err.message}`; - item.failureReason = err.message; - throw e; - } finally { - await qhm.refreshTreeView(); - source.dispose(); - } - } - } - - async function compileAndRunQueryOnMultipleDatabases( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ): Promise { - let filteredDBs = dbm.databaseItems; - if (filteredDBs.length === 0) { - void showAndLogErrorMessage( - "No databases found. Please add a suitable database to your workspace.", - ); - return; - } - // If possible, only show databases with the right language (otherwise show all databases). - const queryLanguage = await findLanguage(cliServer, uri); - if (queryLanguage) { - filteredDBs = dbm.databaseItems.filter( - (db) => db.language === queryLanguage, - ); - if (filteredDBs.length === 0) { - void showAndLogErrorMessage( - `No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`, - ); - return; - } - } - const quickPickItems = filteredDBs.map((dbItem) => ({ - databaseItem: dbItem, - label: dbItem.name, - description: dbItem.language, - })); - /** - * Databases that were selected in the quick pick menu. - */ - const quickpick = await window.showQuickPick( - quickPickItems, - { canPickMany: true, ignoreFocusOut: true }, - ); - if (quickpick !== undefined) { - // Collect all skipped databases and display them at the end (instead of popping up individual errors) - const skippedDatabases = []; - const errors = []; - for (const item of quickpick) { - try { - await compileAndRunQuery( - false, - uri, - progress, - token, - item.databaseItem, - ); - } catch (e) { - skippedDatabases.push(item.label); - errors.push(getErrorMessage(e)); - } - } - if (skippedDatabases.length > 0) { - void extLogger.log(`Errors:\n${errors.join("\n")}`); - void showAndLogWarningMessage( - `The following databases were skipped:\n${skippedDatabases.join( - "\n", - )}.\nFor details about the errors, see the logs.`, - ); - } - } else { - void showAndLogErrorMessage("No databases selected."); - } - } - const qhelpTmpDir = dirSync({ prefix: "qhelp_", keep: false, @@ -858,43 +823,6 @@ async function activateWithInstalledDistribution( }); ctx.subscriptions.push({ dispose: qhelpTmpDir.removeCallback }); - async function previewQueryHelp(selectedQuery: Uri): Promise { - // selectedQuery is unpopulated when executing through the command palette - const pathToQhelp = selectedQuery - ? selectedQuery.fsPath - : window.activeTextEditor?.document.uri.fsPath; - if (pathToQhelp) { - // Create temporary directory - const relativePathToMd = `${basename(pathToQhelp, ".qhelp")}.md`; - const absolutePathToMd = join(qhelpTmpDir.name, relativePathToMd); - const uri = Uri.file(absolutePathToMd); - try { - await cliServer.generateQueryHelp(pathToQhelp, absolutePathToMd); - await commands.executeCommand("markdown.showPreviewToSide", uri); - } catch (e) { - const errorMessage = getErrorMessage(e).includes( - "Generating qhelp in markdown", - ) - ? redactableError`Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.` - : redactableError`Could not open a preview of the generated file (${absolutePathToMd}).`; - void showAndLogExceptionWithTelemetry(errorMessage, { - fullMessage: `${errorMessage}\n${getErrorMessage(e)}`, - }); - } - } - } - - async function openReferencedFile(selectedQuery: Uri): Promise { - // If no file is selected, the path of the file in the editor is selected - const path = - selectedQuery?.fsPath || window.activeTextEditor?.document.uri.fsPath; - if (qs !== undefined && path) { - const resolved = await cliServer.resolveQlref(path); - const uri = Uri.file(resolved.resolvedPath); - await window.showTextDocument(uri, { preview: false }); - } - } - ctx.subscriptions.push(tmpDirDisposal); void extLogger.log("Initializing CodeQL language server."); @@ -921,6 +849,7 @@ async function activateWithInstalledDistribution( const testExplorerExtension = extensions.getExtension( testExplorerExtensionId, ); + let testUiCommands: Partial = {}; if (testExplorerExtension) { const testHub = testExplorerExtension.exports; const testAdapterFactory = new QLTestAdapterFactory( @@ -930,555 +859,87 @@ async function activateWithInstalledDistribution( ); ctx.subscriptions.push(testAdapterFactory); - const testUIService = new TestUIService(testHub); + const testUIService = new TestUIService(app, testHub); ctx.subscriptions.push(testUIService); + + testUiCommands = testUIService.getCommands(); } + const astViewer = new AstViewer(); + const astTemplateProvider = new TemplatePrintAstProvider( + cliServer, + qs, + dbm, + contextualQueryStorageDir, + ); + const cfgTemplateProvider = new TemplatePrintCfgProvider(cliServer, dbm); + + ctx.subscriptions.push(astViewer); + + const summaryLanguageSupport = new SummaryLanguageSupport(); + ctx.subscriptions.push(summaryLanguageSupport); + + const mockServer = new VSCodeMockGitHubApiServer(ctx); + ctx.subscriptions.push(mockServer); + void extLogger.log("Registering top-level command palette commands."); - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.runQuery", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ) => await compileAndRunQuery(false, uri, progress, token, undefined), - { - title: "Running query", - cancellable: true, - }, - // Open the query server logger on error since that's usually where the interesting errors appear. - queryServerLogger, - ), - ); - - // Since we are tracking extension usage through commands, this command mirrors the runQuery command - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.runQueryContextEditor", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ) => await compileAndRunQuery(false, uri, progress, token, undefined), - { - title: "Running query", - cancellable: true, - }, - - // Open the query server logger on error since that's usually where the interesting errors appear. - queryServerLogger, - ), - ); - interface DatabaseQuickPickItem extends QuickPickItem { - databaseItem: DatabaseItem; - } - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.runQueryOnMultipleDatabases", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ) => await compileAndRunQueryOnMultipleDatabases(progress, token, uri), - { - title: "Running query on selected databases", - cancellable: true, - }, - ), - ); - // Since we are tracking extension usage through commands, this command mirrors the runQueryOnMultipleDatabases command - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.runQueryOnMultipleDatabasesContextEditor", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ) => await compileAndRunQueryOnMultipleDatabases(progress, token, uri), - { - title: "Running query on selected databases", - cancellable: true, - }, - ), - ); - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.runQueries", - async ( - progress: ProgressCallback, - token: CancellationToken, - _: Uri | undefined, - multi: Uri[], - ) => { - const maxQueryCount = MAX_QUERIES.getValue() as number; - const [files, dirFound] = await gatherQlFiles( - multi.map((uri) => uri.fsPath), - ); - if (files.length > maxQueryCount) { - throw new Error( - `You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`, - ); - } - // warn user and display selected files when a directory is selected because some ql - // files may be hidden from the user. - if (dirFound) { - const fileString = files.map((file) => basename(file)).join(", "); - const res = await showBinaryChoiceDialog( - `You are about to run ${files.length} queries: ${fileString} Do you want to continue?`, - ); - if (!res) { - return; - } - } - const queryUris = files.map((path) => Uri.parse(`file:${path}`, true)); - - // Use a wrapped progress so that messages appear with the queries remaining in it. - let queriesRemaining = queryUris.length; - function wrappedProgress(update: ProgressUpdate) { - const message = - queriesRemaining > 1 - ? `${queriesRemaining} remaining. ${update.message}` - : update.message; - progress({ - ...update, - message, - }); - } - - wrappedProgress({ - maxStep: queryUris.length, - step: queryUris.length - queriesRemaining, - message: "", - }); - - await Promise.all( - queryUris.map(async (uri) => - compileAndRunQuery( - false, - uri, - wrappedProgress, - token, - undefined, - ).then(() => queriesRemaining--), - ), - ); - }, - { - title: "Running queries", - cancellable: true, - }, - - // Open the query server logger on error since that's usually where the interesting errors appear. - queryServerLogger, - ), - ); - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.quickEval", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ) => await compileAndRunQuery(true, uri, progress, token, undefined), - { - title: "Running query", - cancellable: true, - }, - // Open the query server logger on error since that's usually where the interesting errors appear. - queryServerLogger, - ), - ); - - // Since we are tracking extension usage through commands, this command mirrors the "codeQL.quickEval" command - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.quickEvalContextEditor", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ) => await compileAndRunQuery(true, uri, progress, token, undefined), - { - title: "Running query", - cancellable: true, - }, - // Open the query server logger on error since that's usually where the interesting errors appear. - queryServerLogger, - ), - ); - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.codeLensQuickEval", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri, - range: Range, - ) => - await compileAndRunQuery(true, uri, progress, token, undefined, range), - { - title: "Running query", - cancellable: true, - }, - - // Open the query server logger on error since that's usually where the interesting errors appear. - queryServerLogger, - ), - ); - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.quickQuery", - async (progress: ProgressCallback, token: CancellationToken) => - displayQuickQuery(ctx, cliServer, databaseUI, progress, token), - { - title: "Run Quick Query", - }, - - // Open the query server logger on error since that's usually where the interesting errors appear. - queryServerLogger, - ), - ); - - async function runVariantAnalysis( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ): Promise { - progress({ - maxStep: 5, - step: 0, - message: "Getting credentials", - }); - - await variantAnalysisManager.runVariantAnalysis( - uri || window.activeTextEditor?.document.uri, - progress, - token, - ); - } - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.runVariantAnalysis", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ) => await runVariantAnalysis(progress, token, uri), - { - title: "Run Variant Analysis", - cancellable: true, - }, - ), - ); - - // Since we are tracking extension usage through commands, this command mirrors the "codeQL.runVariantAnalysis" command - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.runVariantAnalysisContextEditor", - async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri | undefined, - ) => await runVariantAnalysis(progress, token, uri), - { - title: "Run Variant Analysis", - cancellable: true, - }, - ), - ); - - ctx.subscriptions.push( - commandRunner( - "codeQL.openVariantAnalysisLogs", - async (variantAnalysisId: number) => { - await variantAnalysisManager.openVariantAnalysisLogs(variantAnalysisId); - }, - ), - ); - - ctx.subscriptions.push( - commandRunner( - "codeQL.copyVariantAnalysisRepoList", - async ( - variantAnalysisId: number, - filterSort?: RepositoriesFilterSortStateWithIds, - ) => { - await variantAnalysisManager.copyRepoListToClipboard( - variantAnalysisId, - filterSort, - ); - }, - ), - ); - - ctx.subscriptions.push( - commandRunner( - "codeQL.monitorVariantAnalysis", - async (variantAnalysis: VariantAnalysis, token: CancellationToken) => { - await variantAnalysisManager.monitorVariantAnalysis( - variantAnalysis, - token, - ); - }, - ), - ); - - ctx.subscriptions.push( - commandRunner( - "codeQL.autoDownloadVariantAnalysisResult", - async ( - scannedRepo: VariantAnalysisScannedRepository, - variantAnalysisSummary: VariantAnalysis, - token: CancellationToken, - ) => { - await variantAnalysisManager.enqueueDownload( - scannedRepo, - variantAnalysisSummary, - token, - ); - }, - ), - ); - - ctx.subscriptions.push( - commandRunner( - "codeQL.cancelVariantAnalysis", - async (variantAnalysisId: number) => { - await variantAnalysisManager.cancelVariantAnalysis(variantAnalysisId); - }, - ), - ); - - ctx.subscriptions.push( - commandRunner("codeQL.exportSelectedVariantAnalysisResults", async () => { - await exportSelectedVariantAnalysisResults(qhm); + const allCommands: AllExtensionCommands = { + ...getCommands(app, cliServer, qs), + ...getQueryEditorCommands({ + commandManager: app.commands, + queryRunner: qs, + cliServer, + qhelpTmpDir: qhelpTmpDir.name, }), - ); - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.exportVariantAnalysisResults", - async ( - progress: ProgressCallback, - token: CancellationToken, - variantAnalysisId: number, - filterSort?: RepositoriesFilterSortStateWithIds, - ) => { - await exportVariantAnalysisResults( - variantAnalysisManager, - variantAnalysisId, - filterSort, - app.credentials, - progress, - token, - ); - }, - { - title: "Exporting variant analysis results", - cancellable: true, - }, - ), - ); - - ctx.subscriptions.push( - commandRunner( - "codeQL.loadVariantAnalysisRepoResults", - async (variantAnalysisId: number, repositoryFullName: string) => { - await variantAnalysisManager.loadResults( - variantAnalysisId, - repositoryFullName, - ); - }, - ), - ); - - // The "openVariantAnalysisView" command is internal-only. - ctx.subscriptions.push( - commandRunner( - "codeQL.openVariantAnalysisView", - async (variantAnalysisId: number) => { - await variantAnalysisManager.showView(variantAnalysisId); - }, - ), - ); - - ctx.subscriptions.push( - commandRunner( - "codeQL.openVariantAnalysisQueryText", - async (variantAnalysisId: number) => { - await variantAnalysisManager.openQueryText(variantAnalysisId); - }, - ), - ); - - ctx.subscriptions.push( - commandRunner( - "codeQL.openVariantAnalysisQueryFile", - async (variantAnalysisId: number) => { - await variantAnalysisManager.openQueryFile(variantAnalysisId); - }, - ), - ); - - ctx.subscriptions.push( - commandRunner("codeQL.openReferencedFile", openReferencedFile), - ); - - // Since we are tracking extension usage through commands, this command mirrors the "codeQL.openReferencedFile" command - ctx.subscriptions.push( - commandRunner("codeQL.openReferencedFileContextEditor", openReferencedFile), - ); - - // Since we are tracking extension usage through commands, this command mirrors the "codeQL.openReferencedFile" command - ctx.subscriptions.push( - commandRunner( - "codeQL.openReferencedFileContextExplorer", - openReferencedFile, - ), - ); - - ctx.subscriptions.push( - commandRunner("codeQL.previewQueryHelp", previewQueryHelp), - ); - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.restartQueryServer", - async (progress: ProgressCallback, token: CancellationToken) => { - // We restart the CLI server too, to ensure they are the same version - cliServer.restartCliServer(); - await qs.restartQueryServer(progress, token); - void showAndLogInformationMessage("CodeQL Query Server restarted.", { - outputLogger: queryServerLogger, - }); - }, - { - title: "Restarting Query Server", - }, - ), - ); - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.chooseDatabaseFolder", - (progress: ProgressCallback, token: CancellationToken) => - databaseUI.handleChooseDatabaseFolder(progress, token), - { - title: "Choose a Database from a Folder", - }, - ), - ); - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.chooseDatabaseArchive", - (progress: ProgressCallback, token: CancellationToken) => - databaseUI.handleChooseDatabaseArchive(progress, token), - { - title: "Choose a Database from an Archive", - }, - ), - ); - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.chooseDatabaseGithub", - async (progress: ProgressCallback, token: CancellationToken) => { - const credentials = isCanary() ? app.credentials : undefined; - await databaseUI.handleChooseDatabaseGithub( - credentials, - progress, - token, - ); - }, - { - title: "Adding database from GitHub", - }, - ), - ); - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.chooseDatabaseInternet", - (progress: ProgressCallback, token: CancellationToken) => - databaseUI.handleChooseDatabaseInternet(progress, token), - - { - title: "Adding database from URL", - }, - ), - ); - - ctx.subscriptions.push( - commandRunner("codeQL.openDocumentation", async () => - env.openExternal(Uri.parse("https://codeql.github.com/docs/")), - ), - ); - - ctx.subscriptions.push( - commandRunner("codeQL.copyVersion", async () => { - const text = `CodeQL extension version: ${ - extension?.packageJSON.version - } \nCodeQL CLI version: ${await getCliVersion()} \nPlatform: ${platform()} ${arch()}`; - await env.clipboard.writeText(text); - void showAndLogInformationMessage(text); + ...localQueryResultsView.getCommands(), + ...qhm.getCommands(), + ...variantAnalysisManager.getCommands(), + ...databaseUI.getCommands(), + ...dbModule.getCommands(), + ...getAstCfgCommands({ + queryRunner: qs, + queryHistoryManager: qhm, + databaseUI, + localQueryResultsView, + queryStorageDir, + astViewer, + astTemplateProvider, + cfgTemplateProvider, }), - ); - - const getCliVersion = async () => { - try { - return await cliServer.getVersion(); - } catch { - return ""; - } + ...astViewer.getCommands(), + ...getPackagingCommands({ + cliServer, + }), + ...evalLogViewer.getCommands(), + ...summaryLanguageSupport.getCommands(), + ...testUiCommands, + ...mockServer.getCommands(), }; - ctx.subscriptions.push( - commandRunner("codeQL.authenticateToGitHub", async () => { - /** - * Credentials for authenticating to GitHub. - * These are used when making API calls. - */ - const octokit = await app.credentials.getOctokit(); - const userInfo = await octokit.users.getAuthenticated(); - void showAndLogInformationMessage( - `Authenticated to GitHub as user: ${userInfo.data.login}`, - ); + for (const [commandName, command] of Object.entries(allCommands)) { + app.commands.register(commandName as keyof AllExtensionCommands, command); + } + + const queryServerCommands: QueryServerCommands = { + ...getLocalQueryCommands({ + app, + queryRunner: qs, + queryHistoryManager: qhm, + databaseManager: dbm, + cliServer, + databaseUI, + localQueryResultsView, + queryStorageDir, }), - ); + }; - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.installPackDependencies", - async (progress: ProgressCallback) => - await handleInstallPackDependencies(cliServer, progress), - { - title: "Installing pack dependencies", - }, - ), - ); - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.downloadPacks", - async (progress: ProgressCallback) => - await handleDownloadPacks(cliServer, progress), - { - title: "Downloading packs", - }, - ), - ); - - ctx.subscriptions.push( - commandRunner("codeQL.showLogs", async () => { - extLogger.show(); - }), - ); - - ctx.subscriptions.push(new SummaryLanguageSupport()); + for (const [commandName, command] of Object.entries(queryServerCommands)) { + app.queryServerCommands.register( + commandName as keyof QueryServerCommands, + command, + ); + } void extLogger.log("Starting language server."); await client.start(); @@ -1490,13 +951,6 @@ async function activateWithInstalledDistribution( // Jump-to-definition and find-references void extLogger.log("Registering jump-to-definition handlers."); - // Store contextual queries in a temporary folder so that they are removed - // when the application closes. There is no need for the user to interact with them. - const contextualQueryStorageDir = join( - tmpDir.name, - "contextual-query-storage", - ); - await ensureDir(contextualQueryStorageDir); languages.registerDefinitionProvider( { scheme: zipArchiveScheme }, new TemplateQueryDefinitionProvider( @@ -1517,169 +971,7 @@ async function activateWithInstalledDistribution( ), ); - const astViewer = new AstViewer(); - const printAstTemplateProvider = new TemplatePrintAstProvider( - cliServer, - qs, - dbm, - contextualQueryStorageDir, - ); - const cfgTemplateProvider = new TemplatePrintCfgProvider(cliServer, dbm); - - ctx.subscriptions.push(astViewer); - - async function viewAst( - progress: ProgressCallback, - token: CancellationToken, - selectedFile: Uri, - ): Promise { - const ast = await printAstTemplateProvider.provideAst( - progress, - token, - selectedFile ?? window.activeTextEditor?.document.uri, - ); - if (ast) { - astViewer.updateRoots(await ast.getRoots(), ast.db, ast.fileName); - } - } - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.viewAst", - async ( - progress: ProgressCallback, - token: CancellationToken, - selectedFile: Uri, - ) => await viewAst(progress, token, selectedFile), - { - cancellable: true, - title: "Calculate AST", - }, - ), - ); - - // Since we are tracking extension usage through commands, this command mirrors the "codeQL.viewAst" command - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.viewAstContextExplorer", - async ( - progress: ProgressCallback, - token: CancellationToken, - selectedFile: Uri, - ) => await viewAst(progress, token, selectedFile), - { - cancellable: true, - title: "Calculate AST", - }, - ), - ); - - // Since we are tracking extension usage through commands, this command mirrors the "codeQL.viewAst" command - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.viewAstContextEditor", - async ( - progress: ProgressCallback, - token: CancellationToken, - selectedFile: Uri, - ) => await viewAst(progress, token, selectedFile), - { - cancellable: true, - title: "Calculate AST", - }, - ), - ); - - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.viewCfg", - async (progress: ProgressCallback, token: CancellationToken) => { - const res = await cfgTemplateProvider.provideCfgUri( - window.activeTextEditor?.document, - ); - if (res) { - await compileAndRunQuery(false, res[0], progress, token, undefined); - } - }, - { - title: "Calculating Control Flow Graph", - cancellable: true, - }, - ), - ); - - // Since we are tracking extension usage through commands, this command mirrors the "codeQL.viewCfg" command - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.viewCfgContextExplorer", - async (progress: ProgressCallback, token: CancellationToken) => { - const res = await cfgTemplateProvider.provideCfgUri( - window.activeTextEditor?.document, - ); - if (res) { - await compileAndRunQuery(false, res[0], progress, token, undefined); - } - }, - { - title: "Calculating Control Flow Graph", - cancellable: true, - }, - ), - ); - - // Since we are tracking extension usage through commands, this command mirrors the "codeQL.viewCfg" command - ctx.subscriptions.push( - commandRunnerWithProgress( - "codeQL.viewCfgContextEditor", - async (progress: ProgressCallback, token: CancellationToken) => { - const res = await cfgTemplateProvider.provideCfgUri( - window.activeTextEditor?.document, - ); - if (res) { - await compileAndRunQuery(false, res[0], progress, token, undefined); - } - }, - { - title: "Calculating Control Flow Graph", - cancellable: true, - }, - ), - ); - - const mockServer = new VSCodeMockGitHubApiServer(ctx); - ctx.subscriptions.push(mockServer); - ctx.subscriptions.push( - commandRunner( - "codeQL.mockGitHubApiServer.startRecording", - async () => await mockServer.startRecording(), - ), - ); - ctx.subscriptions.push( - commandRunner( - "codeQL.mockGitHubApiServer.saveScenario", - async () => await mockServer.saveScenario(), - ), - ); - ctx.subscriptions.push( - commandRunner( - "codeQL.mockGitHubApiServer.cancelRecording", - async () => await mockServer.cancelRecording(), - ), - ); - ctx.subscriptions.push( - commandRunner( - "codeQL.mockGitHubApiServer.loadScenario", - async () => await mockServer.loadScenario(), - ), - ); - ctx.subscriptions.push( - commandRunner( - "codeQL.mockGitHubApiServer.unloadScenario", - async () => await mockServer.unloadScenario(), - ), - ); - - await commands.executeCommand("codeQLDatabases.removeOrphanedDatabases"); + await app.commands.execute("codeQLDatabases.removeOrphanedDatabases"); void extLogger.log("Reading query history"); await qhm.readQueryHistory(); @@ -1700,6 +992,22 @@ async function activateWithInstalledDistribution( }; } +async function showResultsForComparison( + compareView: CompareView, + from: CompletedLocalQueryInfo, + to: CompletedLocalQueryInfo, +): Promise { + try { + await compareView.showResults(from, to); + } catch (e) { + void showAndLogExceptionWithTelemetry( + redactableError(asError(e))`Failed to show results: ${getErrorMessage( + e, + )}`, + ); + } +} + function addUnhandledRejectionListener() { const handler = (error: unknown) => { // This listener will be triggered for errors from other extensions as @@ -1734,7 +1042,8 @@ function addUnhandledRejectionListener() { // "uncaughtException" will trigger whenever an exception reaches the top level. // This covers extension initialization and any code within a `setTimeout`. // Notably this does not include exceptions thrown when executing commands, - // because `commandRunner` wraps the command body and handles errors. + // because `registerCommandWithErrorHandling` wraps the command body and + // handles errors. process.addListener("uncaughtException", handler); // "unhandledRejection" will trigger whenever any promise is rejected and it is @@ -1744,6 +1053,7 @@ function addUnhandledRejectionListener() { } async function createQueryServer( + app: ExtensionApp, qlConfigurationListener: QueryServerConfigListener, cliServer: CodeQLCliServer, ctx: ExtensionContext, @@ -1774,6 +1084,7 @@ async function createQueryServer( return new NewQueryRunner(qs); } else { const qs = new LegacyQueryServerClient( + app, qlConfigurationListener, cliServer, qsOpts, @@ -1795,7 +1106,8 @@ async function initializeLogging(ctx: ExtensionContext): Promise { ctx.subscriptions.push(ideServerLogger); } -const checkForUpdatesCommand = "codeQL.checkForUpdatesToCLI"; +const checkForUpdatesCommand: keyof PreActivationCommands = + "codeQL.checkForUpdatesToCLI" as const; const avoidVersionCheck = "avoid-version-check-at-startup"; const lastVersionChecked = "last-version-checked"; diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index d3245a287..2576f6b48 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -18,13 +18,15 @@ import { env, } from "vscode"; import { CodeQLCliServer, QlpacksInfo } from "./cli"; -import { UserCancellationException } from "./commandRunner"; +import { UserCancellationException } from "./progress"; import { extLogger, OutputChannelLogger } from "./common"; import { QueryMetadata } from "./pure/interface-types"; import { telemetryListener } from "./telemetry"; import { RedactableError } from "./pure/errors"; import { getQlPackPath } from "./pure/ql"; import { dbSchemeToLanguage } from "./common/query-language"; +import { isCodespacesTemplate } from "./config"; +import { AppCommandManager } from "./common/commands"; // Shared temporary folder for the extension. export const tmpDir = dirSync({ @@ -266,6 +268,53 @@ export function isFolderAlreadyInWorkspace(folderName: string) { ); } +/** Check if the current workspace is the CodeTour and open the workspace folder. + * Without this, we can't run the code tour correctly. + **/ +export async function prepareCodeTour( + commandManager: AppCommandManager, +): Promise { + if (workspace.workspaceFolders?.length) { + const currentFolder = workspace.workspaceFolders[0].uri.fsPath; + + const tutorialWorkspacePath = join( + currentFolder, + "tutorial.code-workspace", + ); + const toursFolderPath = join(currentFolder, ".tours"); + + /** We're opening the tutorial workspace, if we detect it. + * This will only happen if the following three conditions are met: + * - the .tours folder exists + * - the tutorial.code-workspace file exists + * - the CODESPACES_TEMPLATE setting doesn't exist (it's only set if the user has already opened + * the tutorial workspace so it's a good indicator that the user is in the folder but has ignored + * the prompt to open the workspace) + */ + if ( + (await pathExists(tutorialWorkspacePath)) && + (await pathExists(toursFolderPath)) && + !isCodespacesTemplate() + ) { + const answer = await showBinaryChoiceDialog( + "We've detected you're in the CodeQL Tour repo. We will need to open the workspace file to continue. Reload?", + ); + + if (!answer) { + return; + } + + const tutorialWorkspaceUri = Uri.parse(tutorialWorkspacePath); + + void extLogger.log( + `In prepareCodeTour() method, going to open the tutorial workspace file: ${tutorialWorkspacePath}`, + ); + + await commandManager.execute("vscode.openFolder", tutorialWorkspaceUri); + } + } +} + /** * Provides a utility method to invoke a function only if a minimum time interval has elapsed since * the last invocation of that function. diff --git a/extensions/ql-vscode/src/interface-utils.ts b/extensions/ql-vscode/src/interface-utils.ts index da01c3135..68232dacd 100644 --- a/extensions/ql-vscode/src/interface-utils.ts +++ b/extensions/ql-vscode/src/interface-utils.ts @@ -109,7 +109,11 @@ export function tryResolveLocation( } } -export type WebviewView = "results" | "compare" | "variant-analysis"; +export type WebviewView = + | "results" + | "compare" + | "variant-analysis" + | "data-flow-paths"; export interface WebviewMessage { t: string; diff --git a/extensions/ql-vscode/src/interface.ts b/extensions/ql-vscode/src/interface.ts index 17655fa7c..a7d7dcff1 100644 --- a/extensions/ql-vscode/src/interface.ts +++ b/extensions/ql-vscode/src/interface.ts @@ -42,7 +42,6 @@ import { ParsedResultSets, } from "./pure/interface-types"; import { Logger } from "./common"; -import { commandRunner } from "./commandRunner"; import { CompletedQueryInfo, interpretResultsSarif, @@ -72,6 +71,7 @@ import { isCanary, PAGE_SIZE } from "./config"; import { HistoryItemLabelProvider } from "./query-history/history-item-label-provider"; import { telemetryListener } from "./telemetry"; import { redactableError } from "./pure/errors"; +import { ResultsViewCommands } from "./common/commands"; /** * interface.ts @@ -179,21 +179,6 @@ export class ResultsView extends AbstractWebview< this.handleSelectionChange.bind(this), ), ); - const navigationCommands = { - "codeQLQueryResults.up": NavigationDirection.up, - "codeQLQueryResults.down": NavigationDirection.down, - "codeQLQueryResults.left": NavigationDirection.left, - "codeQLQueryResults.right": NavigationDirection.right, - // For backwards compatibility with keybindings set using an earlier version of the extension. - "codeQLQueryResults.nextPathStep": NavigationDirection.down, - "codeQLQueryResults.previousPathStep": NavigationDirection.up, - }; - void logger.log("Registering result view navigation commands."); - for (const [commandId, direction] of Object.entries(navigationCommands)) { - this.push( - commandRunner(commandId, this.navigateResultView.bind(this, direction)), - ); - } this.push( this.databaseManager.onDidChangeDatabaseItem(({ kind }) => { @@ -209,6 +194,36 @@ export class ResultsView extends AbstractWebview< ); } + public getCommands(): ResultsViewCommands { + return { + "codeQLQueryResults.up": this.navigateResultView.bind( + this, + NavigationDirection.up, + ), + "codeQLQueryResults.down": this.navigateResultView.bind( + this, + NavigationDirection.down, + ), + "codeQLQueryResults.left": this.navigateResultView.bind( + this, + NavigationDirection.left, + ), + "codeQLQueryResults.right": this.navigateResultView.bind( + this, + NavigationDirection.right, + ), + // For backwards compatibility with keybindings set using an earlier version of the extension. + "codeQLQueryResults.nextPathStep": this.navigateResultView.bind( + this, + NavigationDirection.down, + ), + "codeQLQueryResults.previousPathStep": this.navigateResultView.bind( + this, + NavigationDirection.up, + ), + }; + } + async navigateResultView(direction: NavigationDirection): Promise { if (!this.panel?.visible) { return; @@ -295,6 +310,13 @@ export class ResultsView extends AbstractWebview< case "telemetry": telemetryListener?.sendUIInteraction(msg.action); break; + case "unhandledError": + void showAndLogExceptionWithTelemetry( + redactableError( + msg.error, + )`Unhandled error in results view: ${msg.error.message}`, + ); + break; default: assertNever(msg); } diff --git a/extensions/ql-vscode/src/legacy-query-server/legacyRunner.ts b/extensions/ql-vscode/src/legacy-query-server/legacyRunner.ts index 44eba17d9..f12434822 100644 --- a/extensions/ql-vscode/src/legacy-query-server/legacyRunner.ts +++ b/extensions/ql-vscode/src/legacy-query-server/legacyRunner.ts @@ -1,5 +1,5 @@ import { CancellationToken } from "vscode"; -import { ProgressCallback } from "../commandRunner"; +import { ProgressCallback } from "../progress"; import { DatabaseItem } from "../local-databases"; import { Dataset, diff --git a/extensions/ql-vscode/src/legacy-query-server/queryserver-client.ts b/extensions/ql-vscode/src/legacy-query-server/queryserver-client.ts index 36146da31..50c0278db 100644 --- a/extensions/ql-vscode/src/legacy-query-server/queryserver-client.ts +++ b/extensions/ql-vscode/src/legacy-query-server/queryserver-client.ts @@ -1,8 +1,7 @@ -import { dirname } from "path"; import { ensureFile } from "fs-extra"; import { DisposableObject } from "../pure/disposable-object"; -import { CancellationToken, commands } from "vscode"; +import { CancellationToken } from "vscode"; import { createMessageConnection, RequestType } from "vscode-jsonrpc/node"; import * as cli from "../cli"; import { QueryServerConfig } from "../config"; @@ -13,11 +12,10 @@ import { progress, ProgressMessage, WithProgressId, - compileQuery, } from "../pure/legacy-messages"; -import { ProgressCallback, ProgressTask } from "../commandRunner"; -import { findQueryLogFile } from "../run-queries-shared"; +import { ProgressCallback, ProgressTask } from "../progress"; import { ServerProcess } from "../json-rpc-server"; +import { App } from "../common/app"; type WithProgressReporting = ( task: ( @@ -56,20 +54,24 @@ export class QueryServerClient extends DisposableObject { this.queryServerStartListeners.push(e); }; - public activeQueryLogFile: string | undefined; + public activeQueryLogger: Logger; constructor( + app: App, readonly config: QueryServerConfig, readonly cliServer: cli.CodeQLCliServer, readonly opts: ServerOpts, withProgressReporting: WithProgressReporting, ) { super(); + // Since no query is active when we initialize, just point the "active query logger" to the + // default logger. + this.activeQueryLogger = this.logger; // When the query server configuration changes, restart the query server. if (config.onDidChangeConfiguration !== undefined) { this.push( config.onDidChangeConfiguration(() => - commands.executeCommand("codeQL.restartQueryServer"), + app.commands.execute("codeQL.restartQueryServer"), ), ); } @@ -177,9 +179,8 @@ export class QueryServerClient extends DisposableObject { args, this.logger, (data) => - this.logger.log(data.toString(), { + this.activeQueryLogger.log(data.toString(), { trailingNewline: false, - additionalLogLocation: this.activeQueryLogFile, }), undefined, // no listener for stdout progressReporter, @@ -240,8 +241,6 @@ export class QueryServerClient extends DisposableObject { ): Promise { const id = this.nextProgress++; this.progressCallbacks[id] = progress; - - this.updateActiveQuery(type.method, parameter); try { if (this.serverProcess === undefined) { throw new Error("No query server process found."); @@ -255,18 +254,4 @@ export class QueryServerClient extends DisposableObject { delete this.progressCallbacks[id]; } } - - /** - * Updates the active query every time there is a new request to compile. - * The active query is used to specify the side log. - * - * This isn't ideal because in situations where there are queries running - * in parallel, each query's log messages are interleaved. Fixing this - * properly will require a change in the query server. - */ - private updateActiveQuery(method: string, parameter: any): void { - if (method === compileQuery.method) { - this.activeQueryLogFile = findQueryLogFile(dirname(parameter.resultPath)); - } - } } diff --git a/extensions/ql-vscode/src/legacy-query-server/run-queries.ts b/extensions/ql-vscode/src/legacy-query-server/run-queries.ts index 0326d5c24..e2f3fb5f5 100644 --- a/extensions/ql-vscode/src/legacy-query-server/run-queries.ts +++ b/extensions/ql-vscode/src/legacy-query-server/run-queries.ts @@ -13,9 +13,9 @@ import { tryGetQueryMetadata, upgradesTmpDir, } from "../helpers"; -import { ProgressCallback } from "../commandRunner"; +import { ProgressCallback } from "../progress"; import { QueryMetadata } from "../pure/interface-types"; -import { extLogger } from "../common"; +import { extLogger, Logger, TeeLogger } from "../common"; import * as messages from "../pure/legacy-messages"; import { InitialQueryInfo, LocalQueryInfo } from "../query-results"; import * as qsClient from "./queryserver-client"; @@ -66,7 +66,8 @@ export class QueryInProgress { dbItem: DatabaseItem, progress: ProgressCallback, token: CancellationToken, - queryInfo?: LocalQueryInfo, + logger: Logger, + queryInfo: LocalQueryInfo | undefined, ): Promise { if (!dbItem.contents || dbItem.error) { throw new Error("Can't run query on invalid database."); @@ -137,7 +138,7 @@ export class QueryInProgress { await this.queryEvalInfo.addQueryLogs( queryInfo, qs.cliServer, - qs.logger, + logger, ); } else { void showAndLogWarningMessage( @@ -162,6 +163,7 @@ export class QueryInProgress { program: messages.QlProgram, progress: ProgressCallback, token: CancellationToken, + logger: Logger, ): Promise { let compiled: messages.CheckQueryResult | undefined; try { @@ -190,6 +192,11 @@ export class QueryInProgress { target, }; + // Update the active query logger every time there is a new request to compile. + // This isn't ideal because in situations where there are queries running + // in parallel, each query's log messages are interleaved. Fixing this + // properly will require a change in the query server. + qs.activeQueryLogger = logger; compiled = await qs.sendRequest( messages.compileQuery, params, @@ -197,9 +204,7 @@ export class QueryInProgress { progress, ); } finally { - void qs.logger.log(" - - - COMPILATION DONE - - - ", { - additionalLogLocation: this.queryEvalInfo.logPath, - }); + void logger.log(" - - - COMPILATION DONE - - - "); } return (compiled?.messages || []).filter( (msg) => msg.severity === messages.Severity.ERROR, @@ -386,6 +391,8 @@ export async function compileAndRunQueryAgainstDatabase( metadata, templates, ); + const logger = new TeeLogger(qs.logger, query.queryEvalInfo.logPath); + await query.queryEvalInfo.createTimestampFile(); let upgradeDir: tmp.DirectoryResult | undefined; @@ -402,7 +409,7 @@ export async function compileAndRunQueryAgainstDatabase( ); let errors; try { - errors = await query.compile(qs, qlProgram, progress, token); + errors = await query.compile(qs, qlProgram, progress, token, logger); } catch (e) { if ( e instanceof ResponseError && @@ -422,6 +429,7 @@ export async function compileAndRunQueryAgainstDatabase( dbItem, progress, token, + logger, queryInfo, ); if (result.resultType !== messages.QueryResultType.SUCCESS) { @@ -439,18 +447,14 @@ export async function compileAndRunQueryAgainstDatabase( result, successful: result.resultType === messages.QueryResultType.SUCCESS, logFileLocation: result.logFileLocation, - dispose: () => { - qs.logger.removeAdditionalLogLocation(result.logFileLocation); - }, }; } else { // Error dialogs are limited in size and scrollability, // so we include a general description of the problem, // and direct the user to the output window for the detailed compilation messages. // However we don't show quick eval errors there so we need to display them anyway. - void qs.logger.log( + void logger.log( `Failed to compile query ${initialInfo.queryPath} against database scheme ${qlProgram.dbschemePath}:`, - { additionalLogLocation: query.queryEvalInfo.logPath }, ); const formattedMessages: string[] = []; @@ -459,9 +463,7 @@ export async function compileAndRunQueryAgainstDatabase( const message = error.message || "[no error message available]"; const formatted = `ERROR: ${message} (${error.position.fileName}:${error.position.line}:${error.position.column}:${error.position.endLine}:${error.position.endColumn})`; formattedMessages.push(formatted); - void qs.logger.log(formatted, { - additionalLogLocation: query.queryEvalInfo.logPath, - }); + void logger.log(formatted); } if (initialInfo.isQuickEval && formattedMessages.length <= 2) { // If there are more than 2 error messages, they will not be displayed well in a popup @@ -484,9 +486,8 @@ export async function compileAndRunQueryAgainstDatabase( try { await upgradeDir?.cleanup(); } catch (e) { - void qs.logger.log( + void logger.log( `Could not clean up the upgrades dir. Reason: ${getErrorMessage(e)}`, - { additionalLogLocation: query.queryEvalInfo.logPath }, ); } } @@ -535,9 +536,6 @@ function createSyntheticResult( runId: 0, }, successful: false, - dispose: () => { - /**/ - }, }; } diff --git a/extensions/ql-vscode/src/legacy-query-server/upgrades.ts b/extensions/ql-vscode/src/legacy-query-server/upgrades.ts index 0ec182766..6b70bf58b 100644 --- a/extensions/ql-vscode/src/legacy-query-server/upgrades.ts +++ b/extensions/ql-vscode/src/legacy-query-server/upgrades.ts @@ -4,7 +4,7 @@ import { showAndLogExceptionWithTelemetry, tmpDir, } from "../helpers"; -import { ProgressCallback, UserCancellationException } from "../commandRunner"; +import { ProgressCallback, UserCancellationException } from "../progress"; import { extLogger } from "../common"; import * as messages from "../pure/legacy-messages"; import * as qsClient from "./queryserver-client"; diff --git a/extensions/ql-vscode/src/local-databases-ui.ts b/extensions/ql-vscode/src/local-databases-ui.ts index 5d6d8fcc0..f1d88edb6 100644 --- a/extensions/ql-vscode/src/local-databases-ui.ts +++ b/extensions/ql-vscode/src/local-databases-ui.ts @@ -21,11 +21,7 @@ import { DatabaseItem, DatabaseManager, } from "./local-databases"; -import { - commandRunner, - commandRunnerWithProgress, - ProgressCallback, -} from "./commandRunner"; +import { ProgressCallback, withProgress } from "./progress"; import { isLikelyDatabaseRoot, isLikelyDbLanguageFolder, @@ -42,8 +38,8 @@ import { asError, asyncFilter, getErrorMessage } from "./pure/helpers-pure"; import { QueryRunner } from "./queryRunner"; import { isCanary } from "./config"; import { App } from "./common/app"; -import { Credentials } from "./common/authentication"; import { redactableError } from "./pure/errors"; +import { LocalDatabasesCommands } from "./common/commands"; enum SortOrder { NameAsc = "NameAsc", @@ -73,12 +69,12 @@ class DatabaseTreeDataProvider this.push( this.databaseManager.onDidChangeDatabaseItem( - this.handleDidChangeDatabaseItem, + this.handleDidChangeDatabaseItem.bind(this), ), ); this.push( this.databaseManager.onDidChangeCurrentDatabaseItem( - this.handleDidChangeCurrentDatabaseItem, + this.handleDidChangeCurrentDatabaseItem.bind(this), ), ); } @@ -87,18 +83,18 @@ class DatabaseTreeDataProvider return this._onDidChangeTreeData.event; } - private handleDidChangeDatabaseItem = (event: DatabaseChangedEvent): void => { + private handleDidChangeDatabaseItem(event: DatabaseChangedEvent): void { // Note that events from the database manager are instances of DatabaseChangedEvent // and events fired by the UI are instances of DatabaseItem // When event.item is undefined, then the entire tree is refreshed. // When event.item is a db item, then only that item is refreshed. this._onDidChangeTreeData.fire(event.item); - }; + } - private handleDidChangeCurrentDatabaseItem = ( + private handleDidChangeCurrentDatabaseItem( event: DatabaseChangedEvent, - ): void => { + ): void { if (this.currentDatabaseItem) { this._onDidChangeTreeData.fire(this.currentDatabaseItem); } @@ -106,7 +102,7 @@ class DatabaseTreeDataProvider if (this.currentDatabaseItem) { this._onDidChangeTreeData.fire(this.currentDatabaseItem); } - }; + } public getTreeItem(element: DatabaseItem): TreeItem { const item = new TreeItem(element.name); @@ -210,149 +206,53 @@ export class DatabaseUI extends DisposableObject { ); } - init() { - void extLogger.log("Registering database panel commands."); - this.push( - commandRunnerWithProgress( - "codeQL.setCurrentDatabase", - this.handleSetCurrentDatabase, - { - title: "Importing database from archive", - }, - ), - ); - this.push( - commandRunnerWithProgress( - "codeQL.setDefaultTourDatabase", - this.handleSetDefaultTourDatabase, - { - title: "Set Default Database for Codespace CodeQL Tour", - }, - ), - ); - this.push( - commandRunnerWithProgress( - "codeQL.upgradeCurrentDatabase", - this.handleUpgradeCurrentDatabase, - { - title: "Upgrading current database", - cancellable: true, - }, - ), - ); - this.push( - commandRunnerWithProgress("codeQL.clearCache", this.handleClearCache, { - title: "Clearing Cache", - }), - ); - - this.push( - commandRunnerWithProgress( - "codeQLDatabases.chooseDatabaseFolder", - this.handleChooseDatabaseFolder, - { - title: "Adding database from folder", - }, - ), - ); - this.push( - commandRunnerWithProgress( - "codeQLDatabases.chooseDatabaseArchive", - this.handleChooseDatabaseArchive, - { - title: "Adding database from archive", - }, - ), - ); - this.push( - commandRunnerWithProgress( - "codeQLDatabases.chooseDatabaseInternet", - this.handleChooseDatabaseInternet, - { - title: "Adding database from URL", - }, - ), - ); - this.push( - commandRunnerWithProgress( - "codeQLDatabases.chooseDatabaseGithub", - async (progress: ProgressCallback, token: CancellationToken) => { - const credentials = isCanary() ? this.app.credentials : undefined; - await this.handleChooseDatabaseGithub(credentials, progress, token); - }, - { - title: "Adding database from GitHub", - }, - ), - ); - this.push( - commandRunner( - "codeQLDatabases.setCurrentDatabase", - this.handleMakeCurrentDatabase, - ), - ); - this.push( - commandRunner("codeQLDatabases.sortByName", this.handleSortByName), - ); - this.push( - commandRunner( - "codeQLDatabases.sortByDateAdded", - this.handleSortByDateAdded, - ), - ); - this.push( - commandRunnerWithProgress( - "codeQLDatabases.removeDatabase", - this.handleRemoveDatabase, - { - title: "Removing database", - cancellable: false, - }, - ), - ); - this.push( - commandRunnerWithProgress( - "codeQLDatabases.upgradeDatabase", - this.handleUpgradeDatabase, - { - title: "Upgrading database", - cancellable: true, - }, - ), - ); - this.push( - commandRunner( - "codeQLDatabases.renameDatabase", - this.handleRenameDatabase, - ), - ); - this.push( - commandRunner( - "codeQLDatabases.openDatabaseFolder", - this.handleOpenFolder, - ), - ); - this.push( - commandRunner("codeQLDatabases.addDatabaseSource", this.handleAddSource), - ); - this.push( - commandRunner( - "codeQLDatabases.removeOrphanedDatabases", - this.handleRemoveOrphanedDatabases, - ), - ); + public getCommands(): LocalDatabasesCommands { + return { + "codeQL.chooseDatabaseFolder": + this.handleChooseDatabaseFolderFromPalette.bind(this), + "codeQL.chooseDatabaseArchive": + this.handleChooseDatabaseArchiveFromPalette.bind(this), + "codeQL.chooseDatabaseInternet": + this.handleChooseDatabaseInternet.bind(this), + "codeQL.chooseDatabaseGithub": this.handleChooseDatabaseGithub.bind(this), + "codeQL.setCurrentDatabase": this.handleSetCurrentDatabase.bind(this), + "codeQL.setDefaultTourDatabase": + this.handleSetDefaultTourDatabase.bind(this), + "codeQL.upgradeCurrentDatabase": + this.handleUpgradeCurrentDatabase.bind(this), + "codeQL.clearCache": this.handleClearCache.bind(this), + "codeQLDatabases.chooseDatabaseFolder": + this.handleChooseDatabaseFolder.bind(this), + "codeQLDatabases.chooseDatabaseArchive": + this.handleChooseDatabaseArchive.bind(this), + "codeQLDatabases.chooseDatabaseInternet": + this.handleChooseDatabaseInternet.bind(this), + "codeQLDatabases.chooseDatabaseGithub": + this.handleChooseDatabaseGithub.bind(this), + "codeQLDatabases.setCurrentDatabase": + this.handleMakeCurrentDatabase.bind(this), + "codeQLDatabases.sortByName": this.handleSortByName.bind(this), + "codeQLDatabases.sortByDateAdded": this.handleSortByDateAdded.bind(this), + "codeQLDatabases.removeDatabase": this.handleRemoveDatabase.bind(this), + "codeQLDatabases.upgradeDatabase": this.handleUpgradeDatabase.bind(this), + "codeQLDatabases.renameDatabase": this.handleRenameDatabase.bind(this), + "codeQLDatabases.openDatabaseFolder": this.handleOpenFolder.bind(this), + "codeQLDatabases.addDatabaseSource": this.handleAddSource.bind(this), + "codeQLDatabases.removeOrphanedDatabases": + this.handleRemoveOrphanedDatabases.bind(this), + }; } - private handleMakeCurrentDatabase = async ( + private async handleMakeCurrentDatabase( databaseItem: DatabaseItem, - ): Promise => { + ): Promise { await this.databaseManager.setCurrentDatabaseItem(databaseItem); - }; + } - handleChooseDatabaseFolder = async ( + private async chooseDatabaseFolder( progress: ProgressCallback, token: CancellationToken, - ): Promise => { + ): Promise { try { await this.chooseAndSetDatabase(true, progress, token); } catch (e) { @@ -362,47 +262,73 @@ export class DatabaseUI extends DisposableObject { )`Failed to choose and set database: ${getErrorMessage(e)}`, ); } - }; + } - private handleSetDefaultTourDatabase = async ( - progress: ProgressCallback, - token: CancellationToken, - ): Promise => { - try { - if (!workspace.workspaceFolders?.length) { - throw new Error("No workspace folder is open."); - } else { - // This specifically refers to the database folder in - // https://github.com/github/codespaces-codeql - const uri = Uri.parse( - `${workspace.workspaceFolders[0].uri}/.tours/codeql-tutorial-database`, - ); + private async handleChooseDatabaseFolder(): Promise { + return withProgress( + async (progress, token) => { + await this.chooseDatabaseFolder(progress, token); + }, + { + title: "Adding database from folder", + }, + ); + } - let databaseItem = this.databaseManager.findDatabaseItem(uri); - const isTutorialDatabase = true; - if (databaseItem === undefined) { - databaseItem = await this.databaseManager.openDatabase( - progress, - token, - uri, - "CodeQL Tutorial Database", - isTutorialDatabase, + private async handleChooseDatabaseFolderFromPalette(): Promise { + return withProgress( + async (progress, token) => { + await this.chooseDatabaseFolder(progress, token); + }, + { + title: "Choose a Database from a Folder", + }, + ); + } + + private async handleSetDefaultTourDatabase(): Promise { + return withProgress( + async (progress, token) => { + try { + if (!workspace.workspaceFolders?.length) { + throw new Error("No workspace folder is open."); + } else { + // This specifically refers to the database folder in + // https://github.com/github/codespaces-codeql + const uri = Uri.parse( + `${workspace.workspaceFolders[0].uri}/.tours/codeql-tutorial-database`, + ); + + let databaseItem = this.databaseManager.findDatabaseItem(uri); + const isTutorialDatabase = true; + if (databaseItem === undefined) { + databaseItem = await this.databaseManager.openDatabase( + progress, + token, + uri, + "CodeQL Tutorial Database", + isTutorialDatabase, + ); + } + await this.databaseManager.setCurrentDatabaseItem(databaseItem); + await this.handleTourDependencies(); + } + } catch (e) { + // rethrow and let this be handled by default error handling. + throw new Error( + `Could not set the database for the Code Tour. Please make sure you are using the default workspace in your codespace: ${getErrorMessage( + e, + )}`, ); } - await this.databaseManager.setCurrentDatabaseItem(databaseItem); - await this.handleTourDependencies(); - } - } catch (e) { - // rethrow and let this be handled by default error handling. - throw new Error( - `Could not set the database for the Code Tour. Please make sure you are using the default workspace in your codespace: ${getErrorMessage( - e, - )}`, - ); - } - }; + }, + { + title: "Set Default Database for Codespace CodeQL Tour", + }, + ); + } - private handleTourDependencies = async (): Promise => { + private async handleTourDependencies(): Promise { if (!workspace.workspaceFolders?.length) { throw new Error("No workspace folder is open."); } else { @@ -416,9 +342,10 @@ export class DatabaseUI extends DisposableObject { } await cli.packInstall(tutorialQueriesPath); } - }; + } - handleRemoveOrphanedDatabases = async (): Promise => { + // Public because it's used in tests + public async handleRemoveOrphanedDatabases(): Promise { void extLogger.log("Removing orphaned databases from workspace storage."); let dbDirs = undefined; @@ -481,12 +408,12 @@ export class DatabaseUI extends DisposableObject { )}).\nTo delete unused databases, please remove them manually from the storage folder ${dirname}.`, ); } - }; + } - handleChooseDatabaseArchive = async ( + private async chooseDatabaseArchive( progress: ProgressCallback, token: CancellationToken, - ): Promise => { + ): Promise { try { await this.chooseAndSetDatabase(false, progress, token); } catch (e: unknown) { @@ -496,81 +423,132 @@ export class DatabaseUI extends DisposableObject { )`Failed to choose and set database: ${getErrorMessage(e)}`, ); } - }; - - handleChooseDatabaseInternet = async ( - progress: ProgressCallback, - token: CancellationToken, - ): Promise => { - return await promptImportInternetDatabase( - this.databaseManager, - this.storagePath, - progress, - token, - this.queryServer?.cliServer, - ); - }; - - handleChooseDatabaseGithub = async ( - credentials: Credentials | undefined, - progress: ProgressCallback, - token: CancellationToken, - ): Promise => { - return await promptImportGithubDatabase( - this.databaseManager, - this.storagePath, - credentials, - progress, - token, - this.queryServer?.cliServer, - ); - }; - - async tryUpgradeCurrentDatabase( - progress: ProgressCallback, - token: CancellationToken, - ) { - await this.handleUpgradeCurrentDatabase(progress, token); } - private handleSortByName = async () => { + private async handleChooseDatabaseArchive(): Promise { + return withProgress( + async (progress, token) => { + await this.chooseDatabaseArchive(progress, token); + }, + { + title: "Adding database from archive", + }, + ); + } + + private async handleChooseDatabaseArchiveFromPalette(): Promise { + return withProgress( + async (progress, token) => { + await this.chooseDatabaseArchive(progress, token); + }, + { + title: "Choose a Database from an Archive", + }, + ); + } + + private async handleChooseDatabaseInternet(): Promise { + return withProgress( + async (progress, token) => { + await promptImportInternetDatabase( + this.app.commands, + this.databaseManager, + this.storagePath, + progress, + token, + this.queryServer?.cliServer, + ); + }, + { + title: "Adding database from URL", + }, + ); + } + + private async handleChooseDatabaseGithub(): Promise { + return withProgress( + async (progress, token) => { + const credentials = isCanary() ? this.app.credentials : undefined; + + await promptImportGithubDatabase( + this.app.commands, + this.databaseManager, + this.storagePath, + credentials, + progress, + token, + this.queryServer?.cliServer, + ); + }, + { + title: "Adding database from GitHub", + }, + ); + } + + private async handleSortByName() { if (this.treeDataProvider.sortOrder === SortOrder.NameAsc) { this.treeDataProvider.sortOrder = SortOrder.NameDesc; } else { this.treeDataProvider.sortOrder = SortOrder.NameAsc; } - }; + } - private handleSortByDateAdded = async () => { + private async handleSortByDateAdded() { if (this.treeDataProvider.sortOrder === SortOrder.DateAddedAsc) { this.treeDataProvider.sortOrder = SortOrder.DateAddedDesc; } else { this.treeDataProvider.sortOrder = SortOrder.DateAddedAsc; } - }; + } - private handleUpgradeCurrentDatabase = async ( - progress: ProgressCallback, - token: CancellationToken, - ): Promise => { - await this.handleUpgradeDatabase( - progress, - token, - this.databaseManager.currentDatabaseItem, - [], + private async handleUpgradeCurrentDatabase(): Promise { + return withProgress( + async (progress, token) => { + await this.handleUpgradeDatabaseInternal( + progress, + token, + this.databaseManager.currentDatabaseItem, + [], + ); + }, + { + title: "Upgrading current database", + cancellable: true, + }, ); - }; + } - private handleUpgradeDatabase = async ( + private async handleUpgradeDatabase( + databaseItem: DatabaseItem | undefined, + multiSelect: DatabaseItem[] | undefined, + ): Promise { + return withProgress( + async (progress, token) => { + return await this.handleUpgradeDatabaseInternal( + progress, + token, + databaseItem, + multiSelect, + ); + }, + { + title: "Upgrading database", + cancellable: true, + }, + ); + } + + private async handleUpgradeDatabaseInternal( progress: ProgressCallback, token: CancellationToken, databaseItem: DatabaseItem | undefined, multiSelect: DatabaseItem[] | undefined, - ): Promise => { + ): Promise { if (multiSelect?.length) { await Promise.all( multiSelect.map((dbItem) => - this.handleUpgradeDatabase(progress, token, dbItem, []), + this.handleUpgradeDatabaseInternal(progress, token, dbItem, []), ), ); } @@ -602,78 +580,92 @@ export class DatabaseUI extends DisposableObject { progress, token, ); - }; + } - private handleClearCache = async ( - progress: ProgressCallback, - token: CancellationToken, - ): Promise => { - if ( - this.queryServer !== undefined && - this.databaseManager.currentDatabaseItem !== undefined - ) { - await this.queryServer.clearCacheInDatabase( - this.databaseManager.currentDatabaseItem, - progress, - token, - ); - } - }; + private async handleClearCache(): Promise { + return withProgress( + async (progress, token) => { + if ( + this.queryServer !== undefined && + this.databaseManager.currentDatabaseItem !== undefined + ) { + await this.queryServer.clearCacheInDatabase( + this.databaseManager.currentDatabaseItem, + progress, + token, + ); + } + }, + { + title: "Clearing cache", + }, + ); + } - private handleSetCurrentDatabase = async ( - progress: ProgressCallback, - token: CancellationToken, - uri: Uri, - ): Promise => { - try { - // Assume user has selected an archive if the file has a .zip extension - if (uri.path.endsWith(".zip")) { - await importArchiveDatabase( - uri.toString(true), - this.databaseManager, - this.storagePath, - progress, - token, - this.queryServer?.cliServer, - ); - } else { - await this.setCurrentDatabase(progress, token, uri); - } - } catch (e) { - // rethrow and let this be handled by default error handling. - throw new Error( - `Could not set database to ${basename( - uri.fsPath, - )}. Reason: ${getErrorMessage(e)}`, - ); - } - }; + private async handleSetCurrentDatabase(uri: Uri): Promise { + return withProgress( + async (progress, token) => { + try { + // Assume user has selected an archive if the file has a .zip extension + if (uri.path.endsWith(".zip")) { + await importArchiveDatabase( + this.app.commands, + uri.toString(true), + this.databaseManager, + this.storagePath, + progress, + token, + this.queryServer?.cliServer, + ); + } else { + await this.setCurrentDatabase(progress, token, uri); + } + } catch (e) { + // rethrow and let this be handled by default error handling. + throw new Error( + `Could not set database to ${basename( + uri.fsPath, + )}. Reason: ${getErrorMessage(e)}`, + ); + } + }, + { + title: "Importing database from archive", + }, + ); + } - private handleRemoveDatabase = async ( - progress: ProgressCallback, - token: CancellationToken, + private async handleRemoveDatabase( databaseItem: DatabaseItem, multiSelect: DatabaseItem[] | undefined, - ): Promise => { - if (multiSelect?.length) { - await Promise.all( - multiSelect.map((dbItem) => - this.databaseManager.removeDatabaseItem(progress, token, dbItem), - ), - ); - } else { - await this.databaseManager.removeDatabaseItem( - progress, - token, - databaseItem, - ); - } - }; + ): Promise { + return withProgress( + async (progress, token) => { + if (multiSelect?.length) { + await Promise.all( + multiSelect.map((dbItem) => + this.databaseManager.removeDatabaseItem(progress, token, dbItem), + ), + ); + } else { + await this.databaseManager.removeDatabaseItem( + progress, + token, + databaseItem, + ); + } + }, + { + title: "Removing database", + cancellable: false, + }, + ); + } - private handleRenameDatabase = async ( + private async handleRenameDatabase( databaseItem: DatabaseItem, multiSelect: DatabaseItem[] | undefined, - ): Promise => { + ): Promise { this.assertSingleDatabase(multiSelect); const newName = await window.showInputBox({ @@ -684,12 +676,12 @@ export class DatabaseUI extends DisposableObject { if (newName) { await this.databaseManager.renameDatabaseItem(databaseItem, newName); } - }; + } - private handleOpenFolder = async ( + private async handleOpenFolder( databaseItem: DatabaseItem, multiSelect: DatabaseItem[] | undefined, - ): Promise => { + ): Promise { if (multiSelect?.length) { await Promise.all( multiSelect.map((dbItem) => env.openExternal(dbItem.databaseUri)), @@ -697,17 +689,17 @@ export class DatabaseUI extends DisposableObject { } else { await env.openExternal(databaseItem.databaseUri); } - }; + } /** * Adds the source folder of a CodeQL database to the workspace. * When a database is first added in the "Databases" view, its source folder is added to the workspace. * If the source folder is removed from the workspace for some reason, we want to be able to re-add it if need be. */ - private handleAddSource = async ( + private async handleAddSource( databaseItem: DatabaseItem, multiSelect: DatabaseItem[] | undefined, - ): Promise => { + ): Promise { if (multiSelect?.length) { for (const dbItem of multiSelect) { await this.databaseManager.addDatabaseSourceArchiveFolder(dbItem); @@ -715,7 +707,7 @@ export class DatabaseUI extends DisposableObject { } else { await this.databaseManager.addDatabaseSourceArchiveFolder(databaseItem); } - }; + } /** * Return the current database directory. If we don't already have a @@ -773,6 +765,7 @@ export class DatabaseUI extends DisposableObject { // we are selecting a database archive. Must unzip into a workspace-controlled area // before importing. return await importArchiveDatabase( + this.app.commands, uri.toString(true), this.databaseManager, this.storagePath, diff --git a/extensions/ql-vscode/src/local-databases.ts b/extensions/ql-vscode/src/local-databases.ts index 8ba756df7..918ddd5fc 100644 --- a/extensions/ql-vscode/src/local-databases.ts +++ b/extensions/ql-vscode/src/local-databases.ts @@ -12,7 +12,7 @@ import { isFolderAlreadyInWorkspace, showBinaryChoiceDialog, } from "./helpers"; -import { ProgressCallback, withProgress } from "./commandRunner"; +import { ProgressCallback, withProgress } from "./progress"; import { zipArchiveScheme, encodeArchiveBasePath, @@ -28,6 +28,7 @@ import { redactableError } from "./pure/errors"; import { isCodespacesTemplate } from "./config"; import { QlPackGenerator } from "./qlpack-generator"; import { QueryLanguage } from "./common/query-language"; +import { App } from "./common/app"; /** * databases.ts @@ -593,6 +594,7 @@ export class DatabaseManager extends DisposableObject { constructor( private readonly ctx: ExtensionContext, + private readonly app: App, private readonly qs: QueryRunner, private readonly cli: cli.CodeQLCliServer, public logger: Logger, @@ -794,74 +796,66 @@ export class DatabaseManager extends DisposableObject { } public async loadPersistedState(): Promise { - return withProgress( - { - location: vscode.ProgressLocation.Notification, - }, - async (progress, token) => { - const currentDatabaseUri = - this.ctx.workspaceState.get(CURRENT_DB); - const databases = this.ctx.workspaceState.get( - DB_LIST, - [], + return withProgress(async (progress, token) => { + const currentDatabaseUri = + this.ctx.workspaceState.get(CURRENT_DB); + const databases = this.ctx.workspaceState.get( + DB_LIST, + [], + ); + let step = 0; + progress({ + maxStep: databases.length, + message: "Loading persisted databases", + step, + }); + try { + void this.logger.log( + `Found ${databases.length} persisted databases: ${databases + .map((db) => db.uri) + .join(", ")}`, ); - let step = 0; - progress({ - maxStep: databases.length, - message: "Loading persisted databases", - step, - }); - try { - void this.logger.log( - `Found ${databases.length} persisted databases: ${databases - .map((db) => db.uri) - .join(", ")}`, - ); - for (const database of databases) { - progress({ - maxStep: databases.length, - message: `Loading ${ - database.options?.displayName || "databases" - }`, - step: ++step, - }); + for (const database of databases) { + progress({ + maxStep: databases.length, + message: `Loading ${database.options?.displayName || "databases"}`, + step: ++step, + }); - const databaseItem = - await this.createDatabaseItemFromPersistedState( - progress, - token, - database, - ); - try { - await databaseItem.refresh(); - await this.registerDatabase(progress, token, databaseItem); - if (currentDatabaseUri === database.uri) { - await this.setCurrentDatabaseItem(databaseItem, true); - } - void this.logger.log( - `Loaded database ${databaseItem.name} at URI ${database.uri}.`, - ); - } catch (e) { - // When loading from persisted state, leave invalid databases in the list. They will be - // marked as invalid, and cannot be set as the current database. - void this.logger.log( - `Error loading database ${database.uri}: ${e}.`, - ); + const databaseItem = await this.createDatabaseItemFromPersistedState( + progress, + token, + database, + ); + try { + await databaseItem.refresh(); + await this.registerDatabase(progress, token, databaseItem); + if (currentDatabaseUri === database.uri) { + await this.setCurrentDatabaseItem(databaseItem, true); } + void this.logger.log( + `Loaded database ${databaseItem.name} at URI ${database.uri}.`, + ); + } catch (e) { + // When loading from persisted state, leave invalid databases in the list. They will be + // marked as invalid, and cannot be set as the current database. + void this.logger.log( + `Error loading database ${database.uri}: ${e}.`, + ); } - await this.updatePersistedDatabaseList(); - } catch (e) { - // database list had an unexpected type - nothing to be done? - void showAndLogExceptionWithTelemetry( - redactableError( - asError(e), - )`Database list loading failed: ${getErrorMessage(e)}`, - ); } + await this.updatePersistedDatabaseList(); + } catch (e) { + // database list had an unexpected type - nothing to be done? + void showAndLogExceptionWithTelemetry( + redactableError( + asError(e), + )`Database list loading failed: ${getErrorMessage(e)}`, + ); + } - void this.logger.log("Finished loading persisted databases."); - }, - ); + void this.logger.log("Finished loading persisted databases."); + }); } public get databaseItems(): readonly DatabaseItem[] { @@ -883,7 +877,7 @@ export class DatabaseManager extends DisposableObject { this._currentDatabaseItem = item; this.updatePersistedCurrentDatabaseItem(); - await vscode.commands.executeCommand( + await this.app.commands.execute( "setContext", "codeQL.currentDatabaseItem", item?.name, diff --git a/extensions/ql-vscode/src/local-queries.ts b/extensions/ql-vscode/src/local-queries.ts new file mode 100644 index 000000000..380c4d21d --- /dev/null +++ b/extensions/ql-vscode/src/local-queries.ts @@ -0,0 +1,390 @@ +import { ProgressCallback, ProgressUpdate, withProgress } from "./progress"; +import { + CancellationToken, + CancellationTokenSource, + QuickPickItem, + Range, + Uri, + window, +} from "vscode"; +import { extLogger } from "./common"; +import { MAX_QUERIES } from "./config"; +import { gatherQlFiles } from "./pure/files"; +import { basename } from "path"; +import { + findLanguage, + showAndLogErrorMessage, + showAndLogWarningMessage, + showBinaryChoiceDialog, +} from "./helpers"; +import { displayQuickQuery } from "./quick-query"; +import { QueryRunner } from "./queryRunner"; +import { QueryHistoryManager } from "./query-history/query-history-manager"; +import { DatabaseUI } from "./local-databases-ui"; +import { ResultsView } from "./interface"; +import { DatabaseItem, DatabaseManager } from "./local-databases"; +import { createInitialQueryInfo } from "./run-queries-shared"; +import { CompletedLocalQueryInfo, LocalQueryInfo } from "./query-results"; +import { WebviewReveal } from "./interface-utils"; +import { asError, getErrorMessage } from "./pure/helpers-pure"; +import { CodeQLCliServer } from "./cli"; +import { LocalQueryCommands } from "./common/commands"; +import { App } from "./common/app"; + +type LocalQueryOptions = { + app: App; + queryRunner: QueryRunner; + queryHistoryManager: QueryHistoryManager; + databaseManager: DatabaseManager; + cliServer: CodeQLCliServer; + databaseUI: DatabaseUI; + localQueryResultsView: ResultsView; + queryStorageDir: string; +}; + +export function getLocalQueryCommands({ + app, + queryRunner, + queryHistoryManager, + databaseManager, + cliServer, + databaseUI, + localQueryResultsView, + queryStorageDir, +}: LocalQueryOptions): LocalQueryCommands { + const runQuery = async (uri: Uri | undefined) => + withProgress( + async (progress, token) => { + await compileAndRunQuery( + queryRunner, + queryHistoryManager, + databaseUI, + localQueryResultsView, + queryStorageDir, + false, + uri, + progress, + token, + undefined, + ); + }, + { + title: "Running query", + cancellable: true, + }, + ); + + const runQueryOnMultipleDatabases = async (uri: Uri | undefined) => + withProgress( + async (progress, token) => + await compileAndRunQueryOnMultipleDatabases( + cliServer, + queryRunner, + queryHistoryManager, + databaseManager, + databaseUI, + localQueryResultsView, + queryStorageDir, + progress, + token, + uri, + ), + { + title: "Running query on selected databases", + cancellable: true, + }, + ); + + const runQueries = async (_: Uri | undefined, multi: Uri[]) => + withProgress( + async (progress, token) => { + const maxQueryCount = MAX_QUERIES.getValue() as number; + const [files, dirFound] = await gatherQlFiles( + multi.map((uri) => uri.fsPath), + ); + if (files.length > maxQueryCount) { + throw new Error( + `You tried to run ${files.length} queries, but the maximum is ${maxQueryCount}. Try selecting fewer queries or changing the 'codeQL.runningQueries.maxQueries' setting.`, + ); + } + // warn user and display selected files when a directory is selected because some ql + // files may be hidden from the user. + if (dirFound) { + const fileString = files.map((file) => basename(file)).join(", "); + const res = await showBinaryChoiceDialog( + `You are about to run ${files.length} queries: ${fileString} Do you want to continue?`, + ); + if (!res) { + return; + } + } + const queryUris = files.map((path) => Uri.parse(`file:${path}`, true)); + + // Use a wrapped progress so that messages appear with the queries remaining in it. + let queriesRemaining = queryUris.length; + + function wrappedProgress(update: ProgressUpdate) { + const message = + queriesRemaining > 1 + ? `${queriesRemaining} remaining. ${update.message}` + : update.message; + progress({ + ...update, + message, + }); + } + + wrappedProgress({ + maxStep: queryUris.length, + step: queryUris.length - queriesRemaining, + message: "", + }); + + await Promise.all( + queryUris.map(async (uri) => + compileAndRunQuery( + queryRunner, + queryHistoryManager, + databaseUI, + localQueryResultsView, + queryStorageDir, + false, + uri, + wrappedProgress, + token, + undefined, + ).then(() => queriesRemaining--), + ), + ); + }, + { + title: "Running queries", + cancellable: true, + }, + ); + + const quickEval = async (uri: Uri) => + withProgress( + async (progress, token) => { + await compileAndRunQuery( + queryRunner, + queryHistoryManager, + databaseUI, + localQueryResultsView, + queryStorageDir, + true, + uri, + progress, + token, + undefined, + ); + }, + { + title: "Running query", + cancellable: true, + }, + ); + + const codeLensQuickEval = async (uri: Uri, range: Range) => + withProgress( + async (progress, token) => + await compileAndRunQuery( + queryRunner, + queryHistoryManager, + databaseUI, + localQueryResultsView, + queryStorageDir, + true, + uri, + progress, + token, + undefined, + range, + ), + { + title: "Running query", + cancellable: true, + }, + ); + + const quickQuery = async () => + withProgress( + async (progress, token) => + displayQuickQuery(app, cliServer, databaseUI, progress, token), + { + title: "Run Quick Query", + }, + ); + + return { + "codeQL.runQuery": runQuery, + "codeQL.runQueryContextEditor": runQuery, + "codeQL.runQueryOnMultipleDatabases": runQueryOnMultipleDatabases, + "codeQL.runQueryOnMultipleDatabasesContextEditor": + runQueryOnMultipleDatabases, + "codeQL.runQueries": runQueries, + "codeQL.quickEval": quickEval, + "codeQL.quickEvalContextEditor": quickEval, + "codeQL.codeLensQuickEval": codeLensQuickEval, + "codeQL.quickQuery": quickQuery, + }; +} + +export async function compileAndRunQuery( + qs: QueryRunner, + qhm: QueryHistoryManager, + databaseUI: DatabaseUI, + localQueryResultsView: ResultsView, + queryStorageDir: string, + quickEval: boolean, + selectedQuery: Uri | undefined, + progress: ProgressCallback, + token: CancellationToken, + databaseItem: DatabaseItem | undefined, + range?: Range, +): Promise { + if (qs !== undefined) { + // If no databaseItem is specified, use the database currently selected in the Databases UI + databaseItem = + databaseItem || (await databaseUI.getDatabaseItem(progress, token)); + if (databaseItem === undefined) { + throw new Error("Can't run query without a selected database"); + } + const databaseInfo = { + name: databaseItem.name, + databaseUri: databaseItem.databaseUri.toString(), + }; + + // handle cancellation from the history view. + const source = new CancellationTokenSource(); + token.onCancellationRequested(() => source.cancel()); + + const initialInfo = await createInitialQueryInfo( + selectedQuery, + databaseInfo, + quickEval, + range, + ); + const item = new LocalQueryInfo(initialInfo, source); + qhm.addQuery(item); + try { + const completedQueryInfo = await qs.compileAndRunQueryAgainstDatabase( + databaseItem, + initialInfo, + queryStorageDir, + progress, + source.token, + undefined, + item, + ); + qhm.completeQuery(item, completedQueryInfo); + await showResultsForCompletedQuery( + localQueryResultsView, + item as CompletedLocalQueryInfo, + WebviewReveal.Forced, + ); + // Note we must update the query history view after showing results as the + // display and sorting might depend on the number of results + } catch (e) { + const err = asError(e); + err.message = `Error running query: ${err.message}`; + item.failureReason = err.message; + throw e; + } finally { + await qhm.refreshTreeView(); + source.dispose(); + } + } +} + +interface DatabaseQuickPickItem extends QuickPickItem { + databaseItem: DatabaseItem; +} + +async function compileAndRunQueryOnMultipleDatabases( + cliServer: CodeQLCliServer, + qs: QueryRunner, + qhm: QueryHistoryManager, + dbm: DatabaseManager, + databaseUI: DatabaseUI, + localQueryResultsView: ResultsView, + queryStorageDir: string, + progress: ProgressCallback, + token: CancellationToken, + uri: Uri | undefined, +): Promise { + let filteredDBs = dbm.databaseItems; + if (filteredDBs.length === 0) { + void showAndLogErrorMessage( + "No databases found. Please add a suitable database to your workspace.", + ); + return; + } + // If possible, only show databases with the right language (otherwise show all databases). + const queryLanguage = await findLanguage(cliServer, uri); + if (queryLanguage) { + filteredDBs = dbm.databaseItems.filter( + (db) => db.language === queryLanguage, + ); + if (filteredDBs.length === 0) { + void showAndLogErrorMessage( + `No databases found for language ${queryLanguage}. Please add a suitable database to your workspace.`, + ); + return; + } + } + const quickPickItems = filteredDBs.map((dbItem) => ({ + databaseItem: dbItem, + label: dbItem.name, + description: dbItem.language, + })); + /** + * Databases that were selected in the quick pick menu. + */ + const quickpick = await window.showQuickPick( + quickPickItems, + { canPickMany: true, ignoreFocusOut: true }, + ); + if (quickpick !== undefined) { + // Collect all skipped databases and display them at the end (instead of popping up individual errors) + const skippedDatabases = []; + const errors = []; + for (const item of quickpick) { + try { + await compileAndRunQuery( + qs, + qhm, + databaseUI, + localQueryResultsView, + queryStorageDir, + false, + uri, + progress, + token, + item.databaseItem, + ); + } catch (e) { + skippedDatabases.push(item.label); + errors.push(getErrorMessage(e)); + } + } + if (skippedDatabases.length > 0) { + void extLogger.log(`Errors:\n${errors.join("\n")}`); + void showAndLogWarningMessage( + `The following databases were skipped:\n${skippedDatabases.join( + "\n", + )}.\nFor details about the errors, see the logs.`, + ); + } + } else { + void showAndLogErrorMessage("No databases selected."); + } +} + +export async function showResultsForCompletedQuery( + localQueryResultsView: ResultsView, + query: CompletedLocalQueryInfo, + forceReveal: WebviewReveal, +): Promise { + await localQueryResultsView.showResults(query, forceReveal, false); +} diff --git a/extensions/ql-vscode/src/log-insights/summary-language-support.ts b/extensions/ql-vscode/src/log-insights/summary-language-support.ts index c7968ba97..b6920fa30 100644 --- a/extensions/ql-vscode/src/log-insights/summary-language-support.ts +++ b/extensions/ql-vscode/src/log-insights/summary-language-support.ts @@ -13,9 +13,9 @@ import { workspace, } from "vscode"; import { DisposableObject } from "../pure/disposable-object"; -import { commandRunner } from "../commandRunner"; import { extLogger } from "../common"; import { getErrorMessage } from "../pure/helpers-pure"; +import { SummaryLanguageSupportCommands } from "../common/commands"; /** A `Position` within a specified file on disk. */ interface PositionInFile { @@ -73,8 +73,12 @@ export class SummaryLanguageSupport extends DisposableObject { this.handleDidCloseTextDocument.bind(this), ), ); + } - this.push(commandRunner("codeQL.gotoQL", this.handleGotoQL.bind(this))); + public getCommands(): SummaryLanguageSupportCommands { + return { + "codeQL.gotoQL": this.handleGotoQL.bind(this), + }; } /** diff --git a/extensions/ql-vscode/src/mocks/vscode-mock-gh-api-server.ts b/extensions/ql-vscode/src/mocks/vscode-mock-gh-api-server.ts index fbe43c2c9..50c821b56 100644 --- a/extensions/ql-vscode/src/mocks/vscode-mock-gh-api-server.ts +++ b/extensions/ql-vscode/src/mocks/vscode-mock-gh-api-server.ts @@ -15,6 +15,7 @@ import { } from "../config"; import { DisposableObject } from "../pure/disposable-object"; import { MockGitHubApiServer } from "./mock-gh-api-server"; +import { MockGitHubApiServerCommands } from "../common/commands"; /** * "Interface" to the mock GitHub API server which implements VSCode interactions, such as @@ -34,6 +35,19 @@ export class VSCodeMockGitHubApiServer extends DisposableObject { this.setupConfigListener(); } + public getCommands(): MockGitHubApiServerCommands { + return { + "codeQL.mockGitHubApiServer.startRecording": + this.startRecording.bind(this), + "codeQL.mockGitHubApiServer.saveScenario": this.saveScenario.bind(this), + "codeQL.mockGitHubApiServer.cancelRecording": + this.cancelRecording.bind(this), + "codeQL.mockGitHubApiServer.loadScenario": this.loadScenario.bind(this), + "codeQL.mockGitHubApiServer.unloadScenario": + this.unloadScenario.bind(this), + }; + } + public async startServer(): Promise { this.server.startServer(); } diff --git a/extensions/ql-vscode/src/packages/commands/CommandManager.ts b/extensions/ql-vscode/src/packages/commands/CommandManager.ts index c0b1d38f2..6583840a7 100644 --- a/extensions/ql-vscode/src/packages/commands/CommandManager.ts +++ b/extensions/ql-vscode/src/packages/commands/CommandManager.ts @@ -1 +1,70 @@ -export class CommandManager {} +/** + * Contains a generic implementation of typed commands. + * + * This allows different parts of the extension to register commands with a certain type, + * and then allow other parts to call those commands in a well-typed manner. + */ + +import { Disposable } from "./Disposable"; + +/** + * A command function is a completely untyped command. + */ +export type CommandFunction = (...args: any[]) => Promise; + +/** + * The command manager basically takes a single input, the type + * of all the known commands. The second parameter is provided by + * default (and should not be needed by the caller) it is a + * technicality to allow the type system to look up commands. + */ +export class CommandManager< + Commands extends Record, + CommandName extends keyof Commands & string = keyof Commands & string, +> implements Disposable +{ + // TODO: should this be a map? + // TODO: handle multiple command names + private commands: Disposable[] = []; + + constructor( + private readonly commandRegister: ( + commandName: T, + fn: NonNullable, + ) => Disposable, + private readonly commandExecute: ( + commandName: T, + ...args: Parameters + ) => Promise>>, + ) {} + + /** + * Register a command with the specified name and implementation. + */ + register( + commandName: T, + definition: NonNullable, + ): void { + this.commands.push(this.commandRegister(commandName, definition)); + } + + /** + * Execute a command with the specified name and the provided arguments. + */ + execute( + commandName: T, + ...args: Parameters + ): Promise>> { + return this.commandExecute(commandName, ...args); + } + + /** + * Dispose the manager, disposing all the registered commands. + */ + dispose(): void { + this.commands.forEach((cmd) => { + cmd.dispose(); + }); + this.commands = []; + } +} diff --git a/extensions/ql-vscode/src/packages/commands/Disposable.ts b/extensions/ql-vscode/src/packages/commands/Disposable.ts new file mode 100644 index 000000000..3a6dd5c17 --- /dev/null +++ b/extensions/ql-vscode/src/packages/commands/Disposable.ts @@ -0,0 +1,7 @@ +/** + * This interface mirrors the vscode.Disaposable class, so that + * the command manager does not depend on vscode directly. + */ +export interface Disposable { + dispose(): void; +} diff --git a/extensions/ql-vscode/src/packaging.ts b/extensions/ql-vscode/src/packaging.ts index c17621c09..64196d48c 100644 --- a/extensions/ql-vscode/src/packaging.ts +++ b/extensions/ql-vscode/src/packaging.ts @@ -5,11 +5,43 @@ import { showAndLogInformationMessage, } from "./helpers"; import { QuickPickItem, window } from "vscode"; -import { ProgressCallback, UserCancellationException } from "./commandRunner"; +import { + ProgressCallback, + UserCancellationException, + withProgress, +} from "./progress"; import { extLogger } from "./common"; import { asError, getErrorStack } from "./pure/helpers-pure"; import { redactableError } from "./pure/errors"; import { PACKS_BY_QUERY_LANGUAGE } from "./common/query-language"; +import { PackagingCommands } from "./common/commands"; + +type PackagingOptions = { + cliServer: CodeQLCliServer; +}; + +export function getPackagingCommands({ + cliServer, +}: PackagingOptions): PackagingCommands { + return { + "codeQL.installPackDependencies": async () => + withProgress( + async (progress: ProgressCallback) => + await handleInstallPackDependencies(cliServer, progress), + { + title: "Installing pack dependencies", + }, + ), + "codeQL.downloadPacks": async () => + withProgress( + async (progress: ProgressCallback) => + await handleDownloadPacks(cliServer, progress), + { + title: "Downloading packs", + }, + ), + }; +} /** * Prompts user to choose packs to download, and downloads them. diff --git a/extensions/ql-vscode/src/progress.ts b/extensions/ql-vscode/src/progress.ts new file mode 100644 index 000000000..45758e39f --- /dev/null +++ b/extensions/ql-vscode/src/progress.ts @@ -0,0 +1,128 @@ +import { + CancellationToken, + ProgressLocation, + ProgressOptions as VSCodeProgressOptions, + window as Window, +} from "vscode"; + +export class UserCancellationException extends Error { + /** + * @param message The error message + * @param silent If silent is true, then this exception will avoid showing a warning message to the user. + */ + constructor(message?: string, public readonly silent = false) { + super(message); + } +} + +export interface ProgressUpdate { + /** + * The current step + */ + step: number; + /** + * The maximum step. This *should* be constant for a single job. + */ + maxStep: number; + /** + * The current progress message + */ + message: string; +} + +export type ProgressCallback = (p: ProgressUpdate) => void; + +// Make certain properties within a type optional +type Optional = Pick, K> & Omit; + +export type ProgressOptions = Optional; + +/** + * A task that reports progress. + * + * @param progress a progress handler function. Call this + * function with a `ProgressUpdate` instance in order to + * denote some progress being achieved on this task. + * @param token a cancellation token + */ +export type ProgressTask = ( + progress: ProgressCallback, + token: CancellationToken, +) => Thenable; + +/** + * This mediates between the kind of progress callbacks we want to + * write (where we *set* current progress position and give + * `maxSteps`) and the kind vscode progress api expects us to write + * (which increment progress by a certain amount out of 100%). + */ +export function withProgress( + task: ProgressTask, + { + location = ProgressLocation.Notification, + title, + cancellable, + }: ProgressOptions = {}, +): Thenable { + let progressAchieved = 0; + return Window.withProgress( + { + location, + title, + cancellable, + }, + (progress, token) => { + return task((p) => { + const { message, step, maxStep } = p; + const increment = (100 * (step - progressAchieved)) / maxStep; + progressAchieved = step; + progress.report({ message, increment }); + }, token); + }, + ); +} + +/** + * Displays a progress monitor that indicates how much progess has been made + * reading from a stream. + * + * @param readable The stream to read progress from + * @param messagePrefix A prefix for displaying the message + * @param totalNumBytes Total number of bytes in this stream + * @param progress The progress callback used to set messages + */ +export function reportStreamProgress( + readable: NodeJS.ReadableStream, + messagePrefix: string, + totalNumBytes?: number, + progress?: ProgressCallback, +) { + if (progress && totalNumBytes) { + let numBytesDownloaded = 0; + const bytesToDisplayMB = (numBytes: number): string => + `${(numBytes / (1024 * 1024)).toFixed(1)} MB`; + const updateProgress = () => { + progress({ + step: numBytesDownloaded, + maxStep: totalNumBytes, + message: `${messagePrefix} [${bytesToDisplayMB( + numBytesDownloaded, + )} of ${bytesToDisplayMB(totalNumBytes)}]`, + }); + }; + + // Display the progress straight away rather than waiting for the first chunk. + updateProgress(); + + readable.on("data", (data) => { + numBytesDownloaded += data.length; + updateProgress(); + }); + } else if (progress) { + progress({ + step: 1, + maxStep: 2, + message: `${messagePrefix} (Size unknown)`, + }); + } +} diff --git a/extensions/ql-vscode/src/pure/errors.ts b/extensions/ql-vscode/src/pure/errors.ts index dd42ebd74..28300d1fa 100644 --- a/extensions/ql-vscode/src/pure/errors.ts +++ b/extensions/ql-vscode/src/pure/errors.ts @@ -1,6 +1,6 @@ export class RedactableError extends Error { constructor( - cause: Error | undefined, + cause: ErrorLike | undefined, private readonly strings: TemplateStringsArray, private readonly values: unknown[], ) { @@ -54,19 +54,34 @@ export function redactableError( ...values: unknown[] ): RedactableError; export function redactableError( - error: Error, + error: ErrorLike, ): (strings: TemplateStringsArray, ...values: unknown[]) => RedactableError; export function redactableError( - errorOrStrings: Error | TemplateStringsArray, + errorOrStrings: ErrorLike | TemplateStringsArray, ...values: unknown[] ): | ((strings: TemplateStringsArray, ...values: unknown[]) => RedactableError) | RedactableError { - if (errorOrStrings instanceof Error) { + if (isErrorLike(errorOrStrings)) { return (strings: TemplateStringsArray, ...values: unknown[]) => new RedactableError(errorOrStrings, strings, values); } else { return new RedactableError(undefined, errorOrStrings, values); } } + +export interface ErrorLike { + message: string; + stack?: string; +} + +function isErrorLike(error: any): error is ErrorLike { + if ( + typeof error.message === "string" && + (error.stack === undefined || typeof error.stack === "string") + ) { + return true; + } + return false; +} diff --git a/extensions/ql-vscode/src/pure/interface-types.ts b/extensions/ql-vscode/src/pure/interface-types.ts index bbc4b0903..2d68b39d2 100644 --- a/extensions/ql-vscode/src/pure/interface-types.ts +++ b/extensions/ql-vscode/src/pure/interface-types.ts @@ -12,6 +12,8 @@ import { VariantAnalysisScannedRepositoryState, } from "../variant-analysis/shared/variant-analysis"; import { RepositoriesFilterSortStateWithIds } from "./variant-analysis-filter-sort"; +import { ErrorLike } from "./errors"; +import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths"; /** * This module contains types and code that are shared between @@ -182,14 +184,13 @@ export type IntoResultsViewMsg = * A message sent from the results view. */ export type FromResultsViewMsg = + | CommonFromViewMessages | ViewSourceFileMsg | ToggleDiagnostics | ChangeRawResultsSortMsg | ChangeInterpretedResultsSortMsg - | ViewLoadedMsg | ChangePage - | OpenFileMsg - | TelemetryMessage; + | OpenFileMsg; /** * Message from the results view to open a database source @@ -231,6 +232,21 @@ interface ViewLoadedMsg { viewName: string; } +interface TelemetryMessage { + t: "telemetry"; + action: string; +} + +interface UnhandledErrorMessage { + t: "unhandledError"; + error: ErrorLike; +} + +type CommonFromViewMessages = + | ViewLoadedMsg + | TelemetryMessage + | UnhandledErrorMessage; + /** * Message from the results view to signal a request to change the * page. @@ -287,11 +303,10 @@ interface ChangeInterpretedResultsSortMsg { * Message from the compare view to the extension. */ export type FromCompareViewMessage = - | ViewLoadedMsg + | CommonFromViewMessages | ChangeCompareMessage | ViewSourceFileMsg - | OpenQueryMessage - | TelemetryMessage; + | OpenQueryMessage; /** * Message from the compare view to request opening a query. @@ -434,9 +449,9 @@ export interface CancelVariantAnalysisMessage { t: "cancelVariantAnalysis"; } -export interface TelemetryMessage { - t: "telemetry"; - action: string; +export interface ShowDataFlowPathsMessage { + t: "showDataFlowPaths"; + dataFlowPaths: DataFlowPaths; } export type ToVariantAnalysisMessage = @@ -445,7 +460,7 @@ export type ToVariantAnalysisMessage = | SetRepoStatesMessage; export type FromVariantAnalysisMessage = - | ViewLoadedMsg + | CommonFromViewMessages | RequestRepositoryResultsMessage | OpenQueryFileMessage | OpenQueryTextMessage @@ -453,4 +468,13 @@ export type FromVariantAnalysisMessage = | ExportResultsMessage | OpenLogsMessage | CancelVariantAnalysisMessage - | TelemetryMessage; + | ShowDataFlowPathsMessage; + +export interface SetDataFlowPathsMessage { + t: "setDataFlowPaths"; + dataFlowPaths: DataFlowPaths; +} + +export type ToDataFlowPathsMessage = SetDataFlowPathsMessage; + +export type FromDataFlowPathsMessage = CommonFromViewMessages; diff --git a/extensions/ql-vscode/src/pure/ql.ts b/extensions/ql-vscode/src/pure/ql.ts index 62c2f006a..6bb08d674 100644 --- a/extensions/ql-vscode/src/pure/ql.ts +++ b/extensions/ql-vscode/src/pure/ql.ts @@ -2,6 +2,10 @@ import { join } from "path"; import { pathExists } from "fs-extra"; export const QLPACK_FILENAMES = ["qlpack.yml", "codeql-pack.yml"]; +export const QLPACK_LOCK_FILENAMES = [ + "qlpack.lock.yml", + "codeql-pack.lock.yml", +]; export const FALLBACK_QLPACK_FILENAME = QLPACK_FILENAMES[0]; export async function getQlPackPath( diff --git a/extensions/ql-vscode/src/query-editor.ts b/extensions/ql-vscode/src/query-editor.ts new file mode 100644 index 000000000..5821dc781 --- /dev/null +++ b/extensions/ql-vscode/src/query-editor.ts @@ -0,0 +1,88 @@ +import { Uri, window } from "vscode"; +import { CodeQLCliServer } from "./cli"; +import { QueryRunner } from "./queryRunner"; +import { basename, join } from "path"; +import { getErrorMessage } from "./pure/helpers-pure"; +import { redactableError } from "./pure/errors"; +import { showAndLogExceptionWithTelemetry } from "./helpers"; +import { AppCommandManager, QueryEditorCommands } from "./common/commands"; + +type QueryEditorOptions = { + commandManager: AppCommandManager; + + queryRunner: QueryRunner; + cliServer: CodeQLCliServer; + + qhelpTmpDir: string; +}; + +export function getQueryEditorCommands({ + commandManager, + queryRunner, + cliServer, + qhelpTmpDir, +}: QueryEditorOptions): QueryEditorCommands { + const openReferencedFileCommand = async (selectedQuery: Uri) => + await openReferencedFile(queryRunner, cliServer, selectedQuery); + + return { + "codeQL.openReferencedFile": openReferencedFileCommand, + // Since we are tracking extension usage through commands, this command mirrors the "codeQL.openReferencedFile" command + "codeQL.openReferencedFileContextEditor": openReferencedFileCommand, + // Since we are tracking extension usage through commands, this command mirrors the "codeQL.openReferencedFile" command + "codeQL.openReferencedFileContextExplorer": openReferencedFileCommand, + "codeQL.previewQueryHelp": async (selectedQuery: Uri) => + await previewQueryHelp( + commandManager, + cliServer, + qhelpTmpDir, + selectedQuery, + ), + }; +} + +async function previewQueryHelp( + commandManager: AppCommandManager, + cliServer: CodeQLCliServer, + qhelpTmpDir: string, + selectedQuery: Uri, +): Promise { + // selectedQuery is unpopulated when executing through the command palette + const pathToQhelp = selectedQuery + ? selectedQuery.fsPath + : window.activeTextEditor?.document.uri.fsPath; + if (pathToQhelp) { + // Create temporary directory + const relativePathToMd = `${basename(pathToQhelp, ".qhelp")}.md`; + const absolutePathToMd = join(qhelpTmpDir, relativePathToMd); + const uri = Uri.file(absolutePathToMd); + try { + await cliServer.generateQueryHelp(pathToQhelp, absolutePathToMd); + await commandManager.execute("markdown.showPreviewToSide", uri); + } catch (e) { + const errorMessage = getErrorMessage(e).includes( + "Generating qhelp in markdown", + ) + ? redactableError`Could not generate markdown from ${pathToQhelp}: Bad formatting in .qhelp file.` + : redactableError`Could not open a preview of the generated file (${absolutePathToMd}).`; + void showAndLogExceptionWithTelemetry(errorMessage, { + fullMessage: `${errorMessage}\n${getErrorMessage(e)}`, + }); + } + } +} + +async function openReferencedFile( + qs: QueryRunner, + cliServer: CodeQLCliServer, + selectedQuery: Uri, +): Promise { + // If no file is selected, the path of the file in the editor is selected + const path = + selectedQuery?.fsPath || window.activeTextEditor?.document.uri.fsPath; + if (qs !== undefined && path) { + const resolved = await cliServer.resolveQlref(path); + const uri = Uri.file(resolved.resolvedPath); + await window.showTextDocument(uri, { preview: false }); + } +} diff --git a/extensions/ql-vscode/src/query-history/query-history-manager.ts b/extensions/ql-vscode/src/query-history/query-history-manager.ts index daffbb9d5..ca7ab3fd9 100644 --- a/extensions/ql-vscode/src/query-history/query-history-manager.ts +++ b/extensions/ql-vscode/src/query-history/query-history-manager.ts @@ -1,6 +1,5 @@ import { join, dirname } from "path"; import { - commands, Disposable, env, EventEmitter, @@ -25,14 +24,8 @@ import { import { extLogger } from "../common"; import { URLSearchParams } from "url"; import { DisposableObject } from "../pure/disposable-object"; -import { commandRunner } from "../commandRunner"; import { ONE_HOUR_IN_MS, TWO_HOURS_IN_MS } from "../pure/time"; -import { - asError, - assertNever, - getErrorMessage, - getErrorStack, -} from "../pure/helpers-pure"; +import { asError, assertNever, getErrorMessage } from "../pure/helpers-pure"; import { CompletedLocalQueryInfo, LocalQueryInfo } from "../query-results"; import { getActionsWorkflowRunUrl, @@ -49,7 +42,7 @@ import { import { deserializeQueryHistory, serializeQueryHistory, -} from "../query-serialization"; +} from "./store/query-history-store"; import { pathExists } from "fs-extra"; import { CliVersionConstraint } from "../cli"; import { HistoryItemLabelProvider } from "./history-item-label-provider"; @@ -66,6 +59,9 @@ import { getTotalResultCount } from "../variant-analysis/shared/variant-analysis import { HistoryTreeDataProvider } from "./history-tree-data-provider"; import { redactableError } from "../pure/errors"; import { QueryHistoryDirs } from "./query-history-dirs"; +import { QueryHistoryCommands } from "../common/commands"; +import { App } from "../common/app"; +import { tryOpenExternalFile } from "../vscode-utils/external-files"; /** * query-history-manager.ts @@ -135,6 +131,7 @@ export class QueryHistoryManager extends DisposableObject { readonly onDidCompleteQuery = this._onDidCompleteQuery.event; constructor( + private readonly app: App, private readonly qs: QueryRunner, private readonly dbm: DatabaseManager, private readonly localQueriesResultsView: ResultsView, @@ -201,141 +198,6 @@ export class QueryHistoryManager extends DisposableObject { }), ); - void extLogger.log("Registering query history panel commands."); - this.push( - commandRunner( - "codeQLQueryHistory.openQuery", - this.handleOpenQuery.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.removeHistoryItem", - this.handleRemoveHistoryItem.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.sortByName", - this.handleSortByName.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.sortByDate", - this.handleSortByDate.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.sortByCount", - this.handleSortByCount.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.renameItem", - this.handleRenameItem.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.compareWith", - this.handleCompareWith.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.showQueryLog", - this.handleShowQueryLog.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.openQueryDirectory", - this.handleOpenQueryDirectory.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.showEvalLog", - this.handleShowEvalLog.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.showEvalLogSummary", - this.handleShowEvalLogSummary.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.showEvalLogViewer", - this.handleShowEvalLogViewer.bind(this), - ), - ); - this.push( - commandRunner("codeQLQueryHistory.cancel", this.handleCancel.bind(this)), - ); - this.push( - commandRunner( - "codeQLQueryHistory.showQueryText", - this.handleShowQueryText.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.exportResults", - this.handleExportResults.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.viewCsvResults", - this.handleViewCsvResults.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.viewCsvAlerts", - this.handleViewCsvAlerts.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.viewSarifAlerts", - this.handleViewSarifAlerts.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.viewDil", - this.handleViewDil.bind(this), - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.itemClicked", - async (item: LocalQueryInfo) => { - return this.handleItemClicked(item, [item]); - }, - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.openOnGithub", - async (item: LocalQueryInfo) => { - return this.handleOpenOnGithub(item, [item]); - }, - ), - ); - this.push( - commandRunner( - "codeQLQueryHistory.copyRepoList", - this.handleCopyRepoList.bind(this), - ), - ); - // There are two configuration items that affect the query history: // 1. The ttl for query history items. // 2. The default label for query history items. @@ -370,6 +232,48 @@ export class QueryHistoryManager extends DisposableObject { this.registerToVariantAnalysisEvents(); } + public getCommands(): QueryHistoryCommands { + return { + "codeQLQueryHistory.sortByName": this.handleSortByName.bind(this), + "codeQLQueryHistory.sortByDate": this.handleSortByDate.bind(this), + "codeQLQueryHistory.sortByCount": this.handleSortByCount.bind(this), + + "codeQLQueryHistory.openQueryTitleMenu": this.handleOpenQuery.bind(this), + "codeQLQueryHistory.openQueryContextMenu": + this.handleOpenQuery.bind(this), + "codeQLQueryHistory.removeHistoryItemTitleMenu": + this.handleRemoveHistoryItem.bind(this), + "codeQLQueryHistory.removeHistoryItemContextMenu": + this.handleRemoveHistoryItem.bind(this), + "codeQLQueryHistory.removeHistoryItemContextInline": + this.handleRemoveHistoryItem.bind(this), + "codeQLQueryHistory.renameItem": this.handleRenameItem.bind(this), + "codeQLQueryHistory.compareWith": this.handleCompareWith.bind(this), + "codeQLQueryHistory.showEvalLog": this.handleShowEvalLog.bind(this), + "codeQLQueryHistory.showEvalLogSummary": + this.handleShowEvalLogSummary.bind(this), + "codeQLQueryHistory.showEvalLogViewer": + this.handleShowEvalLogViewer.bind(this), + "codeQLQueryHistory.showQueryLog": this.handleShowQueryLog.bind(this), + "codeQLQueryHistory.showQueryText": this.handleShowQueryText.bind(this), + "codeQLQueryHistory.openQueryDirectory": + this.handleOpenQueryDirectory.bind(this), + "codeQLQueryHistory.cancel": this.handleCancel.bind(this), + "codeQLQueryHistory.exportResults": this.handleExportResults.bind(this), + "codeQLQueryHistory.viewCsvResults": this.handleViewCsvResults.bind(this), + "codeQLQueryHistory.viewCsvAlerts": this.handleViewCsvAlerts.bind(this), + "codeQLQueryHistory.viewSarifAlerts": + this.handleViewSarifAlerts.bind(this), + "codeQLQueryHistory.viewDil": this.handleViewDil.bind(this), + "codeQLQueryHistory.itemClicked": this.handleItemClicked.bind(this), + "codeQLQueryHistory.openOnGithub": this.handleOpenOnGithub.bind(this), + "codeQLQueryHistory.copyRepoList": this.handleCopyRepoList.bind(this), + + "codeQL.exportSelectedVariantAnalysisResults": + this.exportSelectedVariantAnalysisResults.bind(this), + }; + } + public completeQuery(info: LocalQueryInfo, results: QueryWithResults): void { info.completeThisQuery(results); this._onDidCompleteQuery.fire(info); @@ -510,8 +414,7 @@ export class QueryHistoryManager extends DisposableObject { } if (finalSingleItem.t === "variant-analysis") { - await commands.executeCommand( - "codeQL.openVariantAnalysisQueryFile", + await this.variantAnalysisManager.openQueryFile( finalSingleItem.variantAnalysis.id, ); return; @@ -556,7 +459,6 @@ export class QueryHistoryManager extends DisposableObject { !(await pathExists(item.completedQuery?.query.querySaveDir)) ) { this.treeDataProvider.remove(item); - item.completedQuery?.dispose(); } }), ); @@ -577,7 +479,6 @@ export class QueryHistoryManager extends DisposableObject { // Removing in progress local queries is not supported. They must be cancelled first. if (item.status !== QueryStatus.InProgress) { this.treeDataProvider.remove(item); - item.completedQuery?.dispose(); // User has explicitly asked for this query to be removed. // We need to delete it from disk as well. @@ -779,7 +680,10 @@ export class QueryHistoryManager extends DisposableObject { } if (singleItem.completedQuery.logFileLocation) { - await this.tryOpenExternalFile(singleItem.completedQuery.logFileLocation); + await tryOpenExternalFile( + this.app.commands, + singleItem.completedQuery.logFileLocation, + ); } else { void showAndLogWarningMessage("No log file available"); } @@ -847,7 +751,7 @@ export class QueryHistoryManager extends DisposableObject { } } try { - await commands.executeCommand( + await this.app.commands.execute( "revealFileInOS", Uri.file(externalFilePath), ); @@ -896,7 +800,10 @@ export class QueryHistoryManager extends DisposableObject { } if (finalSingleItem.evalLogLocation) { - await this.tryOpenExternalFile(finalSingleItem.evalLogLocation); + await tryOpenExternalFile( + this.app.commands, + finalSingleItem.evalLogLocation, + ); } else { this.warnNoEvalLogs(); } @@ -921,7 +828,10 @@ export class QueryHistoryManager extends DisposableObject { } if (finalSingleItem.evalLogSummaryLocation) { - await this.tryOpenExternalFile(finalSingleItem.evalLogSummaryLocation); + await tryOpenExternalFile( + this.app.commands, + finalSingleItem.evalLogSummaryLocation, + ); return; } @@ -993,8 +903,7 @@ export class QueryHistoryManager extends DisposableObject { if (item.t === "local") { item.cancel(); } else if (item.t === "variant-analysis") { - await commands.executeCommand( - "codeQL.cancelVariantAnalysis", + await this.variantAnalysisManager.cancelVariantAnalysis( item.variantAnalysis.id, ); } else { @@ -1020,8 +929,7 @@ export class QueryHistoryManager extends DisposableObject { } if (finalSingleItem.t === "variant-analysis") { - await commands.executeCommand( - "codeQL.openVariantAnalysisQueryText", + await this.variantAnalysisManager.openQueryText( finalSingleItem.variantAnalysis.id, ); return; @@ -1066,7 +974,10 @@ export class QueryHistoryManager extends DisposableObject { const query = finalSingleItem.completedQuery.query; const hasInterpretedResults = query.canHaveInterpretedResults(); if (hasInterpretedResults) { - await this.tryOpenExternalFile(query.resultsPaths.interpretedResultsPath); + await tryOpenExternalFile( + this.app.commands, + query.resultsPaths.interpretedResultsPath, + ); } else { const label = this.labelProvider.getLabel(finalSingleItem); void showAndLogInformationMessage( @@ -1095,11 +1006,11 @@ export class QueryHistoryManager extends DisposableObject { } const query = finalSingleItem.completedQuery.query; if (await query.hasCsv()) { - void this.tryOpenExternalFile(query.csvPath); + void tryOpenExternalFile(this.app.commands, query.csvPath); return; } if (await query.exportCsvResults(this.qs.cliServer, query.csvPath)) { - void this.tryOpenExternalFile(query.csvPath); + void tryOpenExternalFile(this.app.commands, query.csvPath); } } @@ -1122,7 +1033,8 @@ export class QueryHistoryManager extends DisposableObject { return; } - await this.tryOpenExternalFile( + await tryOpenExternalFile( + this.app.commands, await finalSingleItem.completedQuery.query.ensureCsvAlerts( this.qs.cliServer, this.dbm, @@ -1149,7 +1061,8 @@ export class QueryHistoryManager extends DisposableObject { return; } - await this.tryOpenExternalFile( + await tryOpenExternalFile( + this.app.commands, await finalSingleItem.completedQuery.query.ensureDilPath( this.qs.cliServer, ), @@ -1175,7 +1088,7 @@ export class QueryHistoryManager extends DisposableObject { const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(finalSingleItem); - await commands.executeCommand( + await this.app.commands.execute( "vscode.open", Uri.parse(actionsWorkflowRunUrl), ); @@ -1199,7 +1112,7 @@ export class QueryHistoryManager extends DisposableObject { return; } - await commands.executeCommand( + await this.app.commands.execute( "codeQL.copyVariantAnalysisRepoList", finalSingleItem.variantAnalysis.id, ); @@ -1223,12 +1136,27 @@ export class QueryHistoryManager extends DisposableObject { return; } - await commands.executeCommand( - "codeQL.exportVariantAnalysisResults", + await this.variantAnalysisManager.exportResults( finalSingleItem.variantAnalysis.id, ); } + /** + * Exports the results of the currently-selected variant analysis. + */ + async exportSelectedVariantAnalysisResults(): Promise { + const queryHistoryItem = this.getCurrentQueryHistoryItem(); + if (!queryHistoryItem || queryHistoryItem.t !== "variant-analysis") { + throw new Error( + "No variant analysis results currently open. To open results, click an item in the query history view.", + ); + } + + await this.variantAnalysisManager.exportResults( + queryHistoryItem.variantAnalysis.id, + ); + } + addQuery(item: QueryHistoryInfo) { this.treeDataProvider.pushQuery(item); this.updateTreeViewSelectionIfVisible(); @@ -1254,47 +1182,6 @@ export class QueryHistoryManager extends DisposableObject { } } - private async tryOpenExternalFile(fileLocation: string) { - const uri = Uri.file(fileLocation); - try { - await window.showTextDocument(uri, { preview: false }); - } catch (e) { - const msg = getErrorMessage(e); - if ( - msg.includes( - "Files above 50MB cannot be synchronized with extensions", - ) || - msg.includes("too large to open") - ) { - const res = await showBinaryChoiceDialog( - `VS Code does not allow extensions to open files >50MB. This file -exceeds that limit. Do you want to open it outside of VS Code? - -You can also try manually opening it inside VS Code by selecting -the file in the file explorer and dragging it into the workspace.`, - ); - if (res) { - try { - await commands.executeCommand("revealFileInOS", uri); - } catch (e) { - void showAndLogExceptionWithTelemetry( - redactableError( - asError(e), - )`Failed to reveal file in OS: ${getErrorMessage(e)}`, - ); - } - } - } else { - void showAndLogExceptionWithTelemetry( - redactableError(asError(e))`Could not open file ${fileLocation}`, - { - fullMessage: `${getErrorMessage(e)}\n${getErrorStack(e)}`, - }, - ); - } - } - } - private async findOtherQueryToCompare( singleItem: QueryHistoryInfo, multiSelect: QueryHistoryInfo[], diff --git a/extensions/ql-vscode/src/query-serialization.ts b/extensions/ql-vscode/src/query-history/store/query-history-store.ts similarity index 89% rename from extensions/ql-vscode/src/query-serialization.ts rename to extensions/ql-vscode/src/query-history/store/query-history-store.ts index c2611a06b..bad4a4677 100644 --- a/extensions/ql-vscode/src/query-serialization.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-store.ts @@ -1,18 +1,18 @@ import { pathExists, readFile, remove, mkdir, writeFile } from "fs-extra"; import { dirname } from "path"; -import { showAndLogExceptionWithTelemetry } from "./helpers"; +import { showAndLogExceptionWithTelemetry } from "../../helpers"; import { asError, asyncFilter, getErrorMessage, getErrorStack, -} from "./pure/helpers-pure"; -import { CompletedQueryInfo, LocalQueryInfo } from "./query-results"; -import { QueryHistoryInfo } from "./query-history/query-history-info"; -import { QueryEvaluationInfo } from "./run-queries-shared"; -import { QueryResultType } from "./pure/legacy-messages"; -import { redactableError } from "./pure/errors"; +} from "../../pure/helpers-pure"; +import { CompletedQueryInfo, LocalQueryInfo } from "../../query-results"; +import { QueryHistoryInfo } from "../query-history-info"; +import { QueryEvaluationInfo } from "../../run-queries-shared"; +import { QueryResultType } from "../../pure/legacy-messages"; +import { redactableError } from "../../pure/errors"; export async function deserializeQueryHistory( fsPath: string, @@ -55,10 +55,6 @@ export async function deserializeQueryHistory( q.completedQuery.query, QueryEvaluationInfo.prototype, ); - // deserialized queries do not need to be disposed - q.completedQuery.dispose = () => { - /**/ - }; // Previously, there was a typo in the completedQuery type. There was a field // `sucessful` and it was renamed to `successful`. We need to handle this case. diff --git a/extensions/ql-vscode/src/query-results.ts b/extensions/ql-vscode/src/query-results.ts index c9f3f2d93..33100bde8 100644 --- a/extensions/ql-vscode/src/query-results.ts +++ b/extensions/ql-vscode/src/query-results.ts @@ -55,11 +55,6 @@ export class CompletedQueryInfo implements QueryWithResults { readonly logFileLocation?: string; resultCount: number; - /** - * This dispose method is called when the query is removed from the history view. - */ - dispose: () => void; - /** * Map from result set name to SortedResultSetInfo. */ @@ -85,10 +80,6 @@ export class CompletedQueryInfo implements QueryWithResults { this.message = evaluation.message; this.successful = evaluation.successful; - // Use the dispose method from the evaluation. - // The dispose will clean up any additional log locations that this - // query may have created. - this.dispose = evaluation.dispose; this.sortedResultsInfo = {}; this.resultCount = 0; diff --git a/extensions/ql-vscode/src/query-server/query-runner.ts b/extensions/ql-vscode/src/query-server/query-runner.ts index d80a07cbc..ebb90907b 100644 --- a/extensions/ql-vscode/src/query-server/query-runner.ts +++ b/extensions/ql-vscode/src/query-server/query-runner.ts @@ -1,5 +1,5 @@ import { CancellationToken } from "vscode"; -import { ProgressCallback, UserCancellationException } from "../commandRunner"; +import { ProgressCallback, UserCancellationException } from "../progress"; import { DatabaseItem } from "../local-databases"; import { clearCache, diff --git a/extensions/ql-vscode/src/query-server/queryserver-client.ts b/extensions/ql-vscode/src/query-server/queryserver-client.ts index 824e3bae8..428526299 100644 --- a/extensions/ql-vscode/src/query-server/queryserver-client.ts +++ b/extensions/ql-vscode/src/query-server/queryserver-client.ts @@ -1,4 +1,3 @@ -import { dirname } from "path"; import { ensureFile } from "fs-extra"; import { DisposableObject } from "../pure/disposable-object"; @@ -12,9 +11,7 @@ import { ProgressMessage, WithProgressId, } from "../pure/new-messages"; -import * as messages from "../pure/new-messages"; -import { ProgressCallback, ProgressTask } from "../commandRunner"; -import { findQueryLogFile } from "../run-queries-shared"; +import { ProgressCallback, ProgressTask } from "../progress"; import { ServerProcess } from "../json-rpc-server"; type ServerOpts = { @@ -53,7 +50,7 @@ export class QueryServerClient extends DisposableObject { this.queryServerStartListeners.push(e); }; - public activeQueryLogFile: string | undefined; + public activeQueryLogger: Logger; constructor( readonly config: QueryServerConfig, @@ -62,6 +59,9 @@ export class QueryServerClient extends DisposableObject { withProgressReporting: WithProgressReporting, ) { super(); + // Since no query is active when we initialize, just point the "active query logger" to the + // default logger. + this.activeQueryLogger = this.logger; // When the query server configuration changes, restart the query server. if (config.onDidChangeConfiguration !== undefined) { this.push( @@ -167,9 +167,8 @@ export class QueryServerClient extends DisposableObject { args, this.logger, (data) => - this.logger.log(data.toString(), { + this.activeQueryLogger.log(data.toString(), { trailingNewline: false, - additionalLogLocation: this.activeQueryLogFile, }), undefined, // no listener for stdout progressReporter, @@ -210,7 +209,6 @@ export class QueryServerClient extends DisposableObject { const id = this.nextProgress++; this.progressCallbacks[id] = progress; - this.updateActiveQuery(type.method, parameter); try { if (this.serverProcess === undefined) { throw new Error("No query server process found."); @@ -224,20 +222,4 @@ export class QueryServerClient extends DisposableObject { delete this.progressCallbacks[id]; } } - - /** - * Updates the active query every time there is a new request to compile. - * The active query is used to specify the side log. - * - * This isn't ideal because in situations where there are queries running - * in parallel, each query's log messages are interleaved. Fixing this - * properly will require a change in the query server. - */ - private updateActiveQuery(method: string, parameter: any): void { - if (method === messages.runQuery.method) { - this.activeQueryLogFile = findQueryLogFile( - dirname(dirname((parameter as messages.RunQueryParams).outputPath)), - ); - } - } } diff --git a/extensions/ql-vscode/src/query-server/run-queries.ts b/extensions/ql-vscode/src/query-server/run-queries.ts index f9ee0a6d7..a08680cc4 100644 --- a/extensions/ql-vscode/src/query-server/run-queries.ts +++ b/extensions/ql-vscode/src/query-server/run-queries.ts @@ -1,7 +1,7 @@ import { join } from "path"; import { CancellationToken } from "vscode"; import * as cli from "../cli"; -import { ProgressCallback } from "../commandRunner"; +import { ProgressCallback } from "../progress"; import { DatabaseItem } from "../local-databases"; import { getOnDiskWorkspaceFolders, @@ -9,7 +9,7 @@ import { showAndLogWarningMessage, tryGetQueryMetadata, } from "../helpers"; -import { extLogger } from "../common"; +import { extLogger, TeeLogger } from "../common"; import * as messages from "../pure/new-messages"; import { QueryResultType } from "../pure/legacy-messages"; import { InitialQueryInfo, LocalQueryInfo } from "../query-results"; @@ -88,9 +88,15 @@ export async function compileAndRunQueryAgainstDatabase( target, extensionPacks, }; + const logger = new TeeLogger(qs.logger, query.logPath); await query.createTimestampFile(); let result: messages.RunQueryResult | undefined; try { + // Update the active query logger every time there is a new request to compile. + // This isn't ideal because in situations where there are queries running + // in parallel, each query's log messages are interleaved. Fixing this + // properly will require a change in the query server. + qs.activeQueryLogger = logger; result = await qs.sendRequest( messages.runQuery, queryToRun, @@ -105,7 +111,7 @@ export async function compileAndRunQueryAgainstDatabase( } finally { if (queryInfo) { if (await query.hasEvalLog()) { - await query.addQueryLogs(queryInfo, qs.cliServer, qs.logger); + await query.addQueryLogs(queryInfo, qs.cliServer, logger); } else { void showAndLogWarningMessage( `Failed to write structured evaluator log to ${query.evalLogPath}.`, @@ -160,8 +166,5 @@ export async function compileAndRunQueryAgainstDatabase( }, message, successful, - dispose: () => { - qs.logger.removeAdditionalLogLocation(undefined); - }, }; } diff --git a/extensions/ql-vscode/src/queryRunner.ts b/extensions/ql-vscode/src/queryRunner.ts index a2e92ee66..0ef8058e5 100644 --- a/extensions/ql-vscode/src/queryRunner.ts +++ b/extensions/ql-vscode/src/queryRunner.ts @@ -1,6 +1,6 @@ import { CancellationToken } from "vscode"; import { CodeQLCliServer } from "./cli"; -import { ProgressCallback } from "./commandRunner"; +import { ProgressCallback } from "./progress"; import { DatabaseItem } from "./local-databases"; import { InitialQueryInfo, LocalQueryInfo } from "./query-results"; import { QueryWithResults } from "./run-queries-shared"; diff --git a/extensions/ql-vscode/src/quick-query.ts b/extensions/ql-vscode/src/quick-query.ts index 7829896d7..47c3ca3a9 100644 --- a/extensions/ql-vscode/src/quick-query.ts +++ b/extensions/ql-vscode/src/quick-query.ts @@ -1,13 +1,7 @@ import { ensureDir, writeFile, pathExists, readFile } from "fs-extra"; import { dump, load } from "js-yaml"; import { basename, join } from "path"; -import { - CancellationToken, - ExtensionContext, - window as Window, - workspace, - Uri, -} from "vscode"; +import { CancellationToken, window as Window, workspace, Uri } from "vscode"; import { LSPErrorCodes, ResponseError } from "vscode-languageclient"; import { CodeQLCliServer } from "./cli"; import { DatabaseUI } from "./local-databases-ui"; @@ -17,9 +11,10 @@ import { getQlPackForDbscheme, showBinaryChoiceDialog, } from "./helpers"; -import { ProgressCallback, UserCancellationException } from "./commandRunner"; +import { ProgressCallback, UserCancellationException } from "./progress"; import { getErrorMessage } from "./pure/helpers-pure"; import { FALLBACK_QLPACK_FILENAME, getQlPackPath } from "./pure/ql"; +import { App } from "./common/app"; const QUICK_QUERIES_DIR_NAME = "quick-queries"; const QUICK_QUERY_QUERY_NAME = "quick-query.ql"; @@ -30,8 +25,8 @@ export function isQuickQueryPath(queryPath: string): boolean { return basename(queryPath) === QUICK_QUERY_QUERY_NAME; } -async function getQuickQueriesDir(ctx: ExtensionContext): Promise { - const storagePath = ctx.storagePath; +async function getQuickQueriesDir(app: App): Promise { + const storagePath = app.workspaceStoragePath; if (storagePath === undefined) { throw new Error("Workspace storage path is undefined"); } @@ -57,7 +52,7 @@ function findExistingQuickQueryEditor() { * Show a buffer the user can enter a simple query into. */ export async function displayQuickQuery( - ctx: ExtensionContext, + app: App, cliServer: CodeQLCliServer, databaseUI: DatabaseUI, progress: ProgressCallback, @@ -73,7 +68,7 @@ export async function displayQuickQuery( } const workspaceFolders = workspace.workspaceFolders || []; - const queriesDir = await getQuickQueriesDir(ctx); + const queriesDir = await getQuickQueriesDir(app); // We need to have a multi-root workspace to make quick query work // at all. Changing the workspace from single-root to multi-root @@ -143,7 +138,7 @@ export async function displayQuickQuery( if (shouldRewrite) { await cliServer.clearCache(); - await cliServer.packInstall(queriesDir, true); + await cliServer.packInstall(queriesDir, { forceUpdate: true }); } await Window.showTextDocument(await workspace.openTextDocument(qlFile)); diff --git a/extensions/ql-vscode/src/run-queries-shared.ts b/extensions/ql-vscode/src/run-queries-shared.ts index 66f85e7e1..da4b7e86c 100644 --- a/extensions/ql-vscode/src/run-queries-shared.ts +++ b/extensions/ql-vscode/src/run-queries-shared.ts @@ -12,7 +12,7 @@ import { window, } from "vscode"; import { isCanary, AUTOSAVE_SETTING } from "./config"; -import { UserCancellationException } from "./commandRunner"; +import { UserCancellationException } from "./progress"; import { pathExists, readFile, @@ -30,7 +30,7 @@ import { nanoid } from "nanoid"; import { CodeQLCliServer } from "./cli"; import { SELECT_QUERY_NAME } from "./contextual/locationFinder"; import { DatabaseManager } from "./local-databases"; -import { DecodedBqrsChunk } from "./pure/bqrs-cli-types"; +import { DecodedBqrsChunk, EntityValue } from "./pure/bqrs-cli-types"; import { extLogger, Logger } from "./common"; import { generateSummarySymbolsFile } from "./log-insights/summary-parser"; import { getErrorMessage } from "./pure/helpers-pure"; @@ -298,12 +298,8 @@ export class QueryEvaluationInfo { this.evalLogEndSummaryPath, "utf-8", ); - void logger.log(" --- Evaluator Log Summary --- ", { - additionalLogLocation: this.logPath, - }); - void logger.log(endSummaryContent, { - additionalLogLocation: this.logPath, - }); + void logger.log(" --- Evaluator Log Summary --- "); + void logger.log(endSummaryContent); } catch (e) { void showAndLogWarningMessage( `Could not read structured evaluator log end of summary file at ${this.evalLogEndSummaryPath}.`, @@ -355,11 +351,17 @@ export class QueryEvaluationInfo { chunk.tuples.forEach((tuple) => { out.write( `${tuple - .map((v, i) => - chunk.columns[i].kind === "String" - ? `"${typeof v === "string" ? v.replaceAll('"', '""') : v}"` - : v, - ) + .map((v, i) => { + if (chunk.columns[i].kind === "String") { + return `"${ + typeof v === "string" ? v.replaceAll('"', '""') : v + }"`; + } else if (chunk.columns[i].kind === "Entity") { + return (v as EntityValue).label; + } else { + return v; + } + }) .join(",")}\n`, ); }); @@ -436,7 +438,6 @@ export class QueryEvaluationInfo { export interface QueryWithResults { readonly query: QueryEvaluationInfo; readonly logFileLocation?: string; - readonly dispose: () => void; readonly successful?: boolean; readonly message?: string; readonly result: legacyMessages.EvaluationResult; diff --git a/extensions/ql-vscode/src/stories/common/CodePaths.stories.tsx b/extensions/ql-vscode/src/stories/common/CodePaths.stories.tsx index 5b68f1c2d..793c66099 100644 --- a/extensions/ql-vscode/src/stories/common/CodePaths.stories.tsx +++ b/extensions/ql-vscode/src/stories/common/CodePaths.stories.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import { ComponentStory, ComponentMeta } from "@storybook/react"; -import { ThemeProvider } from "@primer/react"; import { CodePaths } from "../../view/common"; import type { CodeFlow } from "../../variant-analysis/shared/analysis-result"; @@ -9,13 +8,7 @@ import type { CodeFlow } from "../../variant-analysis/shared/analysis-result"; export default { title: "Code Paths", component: CodePaths, - decorators: [ - (Story) => ( - - - - ), - ], + decorators: [(Story) => ], } as ComponentMeta; const Template: ComponentStory = (args) => ( diff --git a/extensions/ql-vscode/src/stories/data-flow-paths/DataFlowPaths.stories.tsx b/extensions/ql-vscode/src/stories/data-flow-paths/DataFlowPaths.stories.tsx new file mode 100644 index 000000000..dd1598360 --- /dev/null +++ b/extensions/ql-vscode/src/stories/data-flow-paths/DataFlowPaths.stories.tsx @@ -0,0 +1,19 @@ +import * as React from "react"; + +import { ComponentMeta, ComponentStory } from "@storybook/react"; + +import { DataFlowPaths as DataFlowPathsComponent } from "../../view/data-flow-paths/DataFlowPaths"; +import { createMockDataFlowPaths } from "../../../test/factories/variant-analysis/shared/data-flow-paths"; +export default { + title: "Data Flow Paths/Data Flow Paths", + component: DataFlowPathsComponent, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +export const PowerShell = Template.bind({}); +PowerShell.args = { + dataFlowPaths: createMockDataFlowPaths(), +}; diff --git a/extensions/ql-vscode/src/telemetry.ts b/extensions/ql-vscode/src/telemetry.ts index 4c4cd79d2..6062cb345 100644 --- a/extensions/ql-vscode/src/telemetry.ts +++ b/extensions/ql-vscode/src/telemetry.ts @@ -16,7 +16,7 @@ import { } from "./config"; import * as appInsights from "applicationinsights"; import { extLogger } from "./common"; -import { UserCancellationException } from "./commandRunner"; +import { UserCancellationException } from "./progress"; import { showBinaryChoiceWithUrlDialog } from "./helpers"; import { RedactableError } from "./pure/errors"; diff --git a/extensions/ql-vscode/src/test-ui.ts b/extensions/ql-vscode/src/test-ui.ts index f0a642576..45b8f41a7 100644 --- a/extensions/ql-vscode/src/test-ui.ts +++ b/extensions/ql-vscode/src/test-ui.ts @@ -1,6 +1,6 @@ import { lstat, copy, pathExists, createFile } from "fs-extra"; import { basename } from "path"; -import { Uri, TextDocumentShowOptions, commands, window } from "vscode"; +import { Uri, TextDocumentShowOptions, window } from "vscode"; import { TestHub, TestController, @@ -14,9 +14,9 @@ import { import { showAndLogWarningMessage } from "./helpers"; import { TestTreeNode } from "./test-tree-node"; import { DisposableObject } from "./pure/disposable-object"; -import { UIService } from "./vscode-utils/ui-service"; import { QLTestAdapter, getExpectedFile, getActualFile } from "./test-adapter"; -import { extLogger } from "./common"; +import { TestUICommands } from "./common/commands"; +import { App } from "./common/app"; type VSCodeTestEvent = | TestRunStartedEvent @@ -42,22 +42,23 @@ class QLTestListener extends DisposableObject { /** * Service that implements all UI and commands for QL tests. */ -export class TestUIService extends UIService implements TestController { +export class TestUIService extends DisposableObject implements TestController { private readonly listeners: Map = new Map(); - constructor(private readonly testHub: TestHub) { + constructor(private readonly app: App, private readonly testHub: TestHub) { super(); - void extLogger.log("Registering CodeQL test panel commands."); - this.registerCommand( - "codeQLTests.showOutputDifferences", - this.showOutputDifferences, - ); - this.registerCommand("codeQLTests.acceptOutput", this.acceptOutput); - testHub.registerTestController(this); } + public getCommands(): TestUICommands { + return { + "codeQLTests.showOutputDifferences": + this.showOutputDifferences.bind(this), + "codeQLTests.acceptOutput": this.acceptOutput.bind(this), + }; + } + public dispose(): void { this.testHub.unregisterTestController(this); @@ -105,7 +106,7 @@ export class TestUIService extends UIService implements TestController { if (await pathExists(actualPath)) { const actualUri = Uri.file(actualPath); - await commands.executeCommand( + await this.app.commands.execute( "vscode.diff", expectedUri, actualUri, diff --git a/extensions/ql-vscode/src/variant-analysis/data-flow-paths-view.ts b/extensions/ql-vscode/src/variant-analysis/data-flow-paths-view.ts new file mode 100644 index 000000000..cfaf568db --- /dev/null +++ b/extensions/ql-vscode/src/variant-analysis/data-flow-paths-view.ts @@ -0,0 +1,68 @@ +import { ExtensionContext, ViewColumn } from "vscode"; +import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview"; +import { assertNever } from "../pure/helpers-pure"; +import { telemetryListener } from "../telemetry"; +import { + FromDataFlowPathsMessage, + ToDataFlowPathsMessage, +} from "../pure/interface-types"; +import { DataFlowPaths } from "./shared/data-flow-paths"; +import { showAndLogExceptionWithTelemetry } from "../helpers"; +import { redactableError } from "../pure/errors"; + +export class DataFlowPathsView extends AbstractWebview< + ToDataFlowPathsMessage, + FromDataFlowPathsMessage +> { + public static readonly viewType = "codeQL.dataFlowPaths"; + + public constructor(ctx: ExtensionContext) { + super(ctx); + } + + public async showDataFlows(dataFlowPaths: DataFlowPaths) { + const panel = await this.getPanel(); + panel.reveal(undefined, true); + + await this.waitForPanelLoaded(); + + await this.postMessage({ + t: "setDataFlowPaths", + dataFlowPaths, + }); + } + + protected async getPanelConfig(): Promise { + return { + viewId: DataFlowPathsView.viewType, + title: "Data Flow Paths", + viewColumn: ViewColumn.Active, + preserveFocus: true, + view: "data-flow-paths", + }; + } + + protected onPanelDispose(): void { + // Nothing to dispose + } + + protected async onMessage(msg: FromDataFlowPathsMessage): Promise { + switch (msg.t) { + case "viewLoaded": + this.onWebViewLoaded(); + break; + case "telemetry": + telemetryListener?.sendUIInteraction(msg.action); + break; + case "unhandledError": + void showAndLogExceptionWithTelemetry( + redactableError( + msg.error, + )`Unhandled error in data flow paths view: ${msg.error.message}`, + ); + break; + default: + assertNever(msg); + } + } +} diff --git a/extensions/ql-vscode/src/variant-analysis/export-results.ts b/extensions/ql-vscode/src/variant-analysis/export-results.ts index faf3f0ee5..a866436b7 100644 --- a/extensions/ql-vscode/src/variant-analysis/export-results.ts +++ b/extensions/ql-vscode/src/variant-analysis/export-results.ts @@ -9,10 +9,13 @@ import { window, workspace, } from "vscode"; -import { ProgressCallback, UserCancellationException } from "../commandRunner"; +import { + ProgressCallback, + UserCancellationException, + withProgress, +} from "../progress"; import { showInformationMessageWithAction } from "../helpers"; import { extLogger } from "../common"; -import { QueryHistoryManager } from "../query-history/query-history-manager"; import { createGist } from "./gh-api/gh-api-client"; import { generateVariantAnalysisMarkdown, @@ -33,25 +36,6 @@ import { } from "../pure/variant-analysis-filter-sort"; import { Credentials } from "../common/authentication"; -/** - * Exports the results of the currently-selected variant analysis. - */ -export async function exportSelectedVariantAnalysisResults( - queryHistoryManager: QueryHistoryManager, -): Promise { - const queryHistoryItem = queryHistoryManager.getCurrentQueryHistoryItem(); - if (!queryHistoryItem || queryHistoryItem.t !== "variant-analysis") { - throw new Error( - "No variant analysis results currently open. To open results, click an item in the query history view.", - ); - } - - return commands.executeCommand( - "codeQL.exportVariantAnalysisResults", - queryHistoryItem.variantAnalysis.id, - ); -} - const MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS = 2; /** @@ -63,108 +47,117 @@ export async function exportVariantAnalysisResults( variantAnalysisId: number, filterSort: RepositoriesFilterSortStateWithIds | undefined, credentials: Credentials, - progress: ProgressCallback, - token: CancellationToken, ): Promise { - const variantAnalysis = await variantAnalysisManager.getVariantAnalysis( - variantAnalysisId, - ); - if (!variantAnalysis) { - void extLogger.log( - `Could not find variant analysis with id ${variantAnalysisId}`, - ); - throw new Error( - "There was an error when trying to retrieve variant analysis information", - ); - } + await withProgress( + async (progress: ProgressCallback, token: CancellationToken) => { + const variantAnalysis = await variantAnalysisManager.getVariantAnalysis( + variantAnalysisId, + ); + if (!variantAnalysis) { + void extLogger.log( + `Could not find variant analysis with id ${variantAnalysisId}`, + ); + throw new Error( + "There was an error when trying to retrieve variant analysis information", + ); + } - if (token.isCancellationRequested) { - throw new UserCancellationException("Cancelled"); - } + if (token.isCancellationRequested) { + throw new UserCancellationException("Cancelled"); + } - const repoStates = await variantAnalysisManager.getRepoStates( - variantAnalysisId, - ); - - void extLogger.log( - `Exporting variant analysis results for variant analysis with id ${variantAnalysis.id}`, - ); - - progress({ - maxStep: MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS, - step: 0, - message: "Determining export format", - }); - - const exportFormat = await determineExportFormat(); - if (!exportFormat) { - return; - } - - if (token.isCancellationRequested) { - throw new UserCancellationException("Cancelled"); - } - - const repositories = filterAndSortRepositoriesWithResults( - variantAnalysis.scannedRepos, - filterSort, - )?.filter( - (repo) => - repo.resultCount && - repoStates.find((r) => r.repositoryId === repo.repository.id) - ?.downloadStatus === - VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, - ); - - async function* getAnalysesResults(): AsyncGenerator< - [VariantAnalysisScannedRepository, VariantAnalysisScannedRepositoryResult] - > { - if (!variantAnalysis) { - return; - } - - if (!repositories) { - return; - } - - for (const repo of repositories) { - const result = await variantAnalysisManager.loadResults( - variantAnalysis.id, - repo.repository.fullName, - { - skipCacheStore: true, - }, + const repoStates = await variantAnalysisManager.getRepoStates( + variantAnalysisId, ); - yield [repo, result]; - } - } + void extLogger.log( + `Exporting variant analysis results for variant analysis with id ${variantAnalysis.id}`, + ); - const exportDirectory = - variantAnalysisManager.getVariantAnalysisStorageLocation( - variantAnalysis.id, - ); + progress({ + maxStep: MAX_VARIANT_ANALYSIS_EXPORT_PROGRESS_STEPS, + step: 0, + message: "Determining export format", + }); - // The date will be formatted like the following: 20221115T123456Z. The time is in UTC. - const formattedDate = new Date() - .toISOString() - .replace(/[-:]/g, "") - .replace(/\.\d+Z$/, "Z"); - const exportedResultsDirectory = join( - exportDirectory, - "exported-results", - `results_${formattedDate}`, - ); + const exportFormat = await determineExportFormat(); + if (!exportFormat) { + return; + } - await exportVariantAnalysisAnalysisResults( - exportedResultsDirectory, - variantAnalysis, - getAnalysesResults(), - repositories?.length ?? 0, - exportFormat, - credentials, - progress, - token, + if (token.isCancellationRequested) { + throw new UserCancellationException("Cancelled"); + } + + const repositories = filterAndSortRepositoriesWithResults( + variantAnalysis.scannedRepos, + filterSort, + )?.filter( + (repo) => + repo.resultCount && + repoStates.find((r) => r.repositoryId === repo.repository.id) + ?.downloadStatus === + VariantAnalysisScannedRepositoryDownloadStatus.Succeeded, + ); + + async function* getAnalysesResults(): AsyncGenerator< + [ + VariantAnalysisScannedRepository, + VariantAnalysisScannedRepositoryResult, + ] + > { + if (!variantAnalysis) { + return; + } + + if (!repositories) { + return; + } + + for (const repo of repositories) { + const result = await variantAnalysisManager.loadResults( + variantAnalysis.id, + repo.repository.fullName, + { + skipCacheStore: true, + }, + ); + + yield [repo, result]; + } + } + + const exportDirectory = + variantAnalysisManager.getVariantAnalysisStorageLocation( + variantAnalysis.id, + ); + + // The date will be formatted like the following: 20221115T123456Z. The time is in UTC. + const formattedDate = new Date() + .toISOString() + .replace(/[-:]/g, "") + .replace(/\.\d+Z$/, "Z"); + const exportedResultsDirectory = join( + exportDirectory, + "exported-results", + `results_${formattedDate}`, + ); + + await exportVariantAnalysisAnalysisResults( + exportedResultsDirectory, + variantAnalysis, + getAnalysesResults(), + repositories?.length ?? 0, + exportFormat, + credentials, + progress, + token, + ); + }, + { + title: "Exporting variant analysis results", + cancellable: true, + }, ); } diff --git a/extensions/ql-vscode/src/variant-analysis/repository-selection.ts b/extensions/ql-vscode/src/variant-analysis/repository-selection.ts index 59f0b7b7b..b8ddcdb51 100644 --- a/extensions/ql-vscode/src/variant-analysis/repository-selection.ts +++ b/extensions/ql-vscode/src/variant-analysis/repository-selection.ts @@ -1,4 +1,4 @@ -import { UserCancellationException } from "../commandRunner"; +import { UserCancellationException } from "../progress"; import { DbManager } from "../databases/db-manager"; import { DbItemKind } from "../databases/db-item"; 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 8b64aa8c7..6b9565a99 100644 --- a/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts +++ b/extensions/ql-vscode/src/variant-analysis/run-remote-query.ts @@ -18,7 +18,7 @@ import { getRemoteControllerRepo, setRemoteControllerRepo, } from "../config"; -import { ProgressCallback, UserCancellationException } from "../commandRunner"; +import { ProgressCallback, UserCancellationException } from "../progress"; import { RequestError } from "@octokit/types/dist-types"; import { QueryMetadata } from "../pure/interface-types"; import { getErrorMessage, REPO_REGEX } from "../pure/helpers-pure"; @@ -34,6 +34,7 @@ import { getQlPackPath, FALLBACK_QLPACK_FILENAME, QLPACK_FILENAMES, + QLPACK_LOCK_FILENAMES, } from "../pure/ql"; export interface QlPack { @@ -70,42 +71,23 @@ async function generateQueryPack( const originalPackRoot = await findPackRoot(queryFile); const packRelativePath = relative(originalPackRoot, queryFile); const targetQueryFileName = join(queryPackDir, packRelativePath); + const workspaceFolders = getOnDiskWorkspaceFolders(); let language: string | undefined; + + // Check if the query is already in a query pack. + // If so, copy the entire query pack to the temporary directory. + // Otherwise, copy only the query file to the temporary directory + // and generate a synthetic query pack. if (await getQlPackPath(originalPackRoot)) { // don't include ql files. We only want the queryFile to be copied. - const toCopy = await cliServer.packPacklist(originalPackRoot, false); - - // also copy the lock file (either new name or old name) and the query file itself. These are not included in the packlist. - [ - join(originalPackRoot, "qlpack.lock.yml"), - join(originalPackRoot, "codeql-pack.lock.yml"), + await copyExistingQueryPack( + cliServer, + originalPackRoot, queryFile, - ].forEach((absolutePath) => { - if (absolutePath) { - toCopy.push(absolutePath); - } - }); - - let copiedCount = 0; - await copy(originalPackRoot, queryPackDir, { - 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) => { - // Normalized paths ensure that Windows drive letters are capitalized consistently. - const normalizedPath = Uri.file(f).fsPath; - const matches = - normalizedPath === file || normalizedPath.startsWith(file + sep); - if (matches) { - copiedCount++; - } - return matches; - }), - }); - - void extLogger.log(`Copied ${copiedCount} files to ${queryPackDir}`); - - await fixPackFile(queryPackDir, packRelativePath); + queryPackDir, + packRelativePath, + ); language = await findLanguage(cliServer, Uri.file(targetQueryFileName)); } else { @@ -114,20 +96,12 @@ async function generateQueryPack( // copy only the query file to the query pack directory // and generate a synthetic query pack - void extLogger.log(`Copying ${queryFile} to ${queryPackDir}`); - 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`]: "*", - }, - defaultSuite: generateDefaultSuite(packRelativePath), - }; - await writeFile( - join(queryPackDir, FALLBACK_QLPACK_FILENAME), - dump(syntheticQueryPack), + await createNewQueryPack( + queryFile, + queryPackDir, + targetQueryFileName, + language, + packRelativePath, ); } if (!language) { @@ -149,12 +123,21 @@ async function generateQueryPack( precompilationOpts = ["--no-precompile"]; } + if (await cliServer.useExtensionPacks()) { + await injectExtensionPacks(cliServer, queryPackDir, workspaceFolders); + } + + await cliServer.packInstall(queryPackDir, { + workspaceFolders, + }); + + // Clear the CLI cache so that the most recent qlpack lock file is used. + await cliServer.clearCache(); + const bundlePath = await getPackedBundlePath(queryPackDir); void extLogger.log( `Compiling and bundling query pack from ${queryPackDir} to ${bundlePath}. (This may take a while.)`, ); - await cliServer.packInstall(queryPackDir); - const workspaceFolders = getOnDiskWorkspaceFolders(); await cliServer.packBundle( queryPackDir, workspaceFolders, @@ -168,6 +151,70 @@ async function generateQueryPack( }; } +async function createNewQueryPack( + queryFile: string, + queryPackDir: string, + targetQueryFileName: string, + language: string | undefined, + packRelativePath: string, +) { + void extLogger.log(`Copying ${queryFile} to ${queryPackDir}`); + 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`]: "*", + }, + defaultSuite: generateDefaultSuite(packRelativePath), + }; + await writeFile( + join(queryPackDir, FALLBACK_QLPACK_FILENAME), + dump(syntheticQueryPack), + ); +} + +async function copyExistingQueryPack( + cliServer: cli.CodeQLCliServer, + originalPackRoot: string, + queryFile: string, + queryPackDir: string, + packRelativePath: string, +) { + const toCopy = await cliServer.packPacklist(originalPackRoot, false); + + [ + // 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, + ].forEach((absolutePath) => { + if (absolutePath) { + toCopy.push(absolutePath); + } + }); + + let copiedCount = 0; + await copy(originalPackRoot, queryPackDir, { + 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) => { + // Normalized paths ensure that Windows drive letters are capitalized consistently. + const normalizedPath = Uri.file(f).fsPath; + const matches = + normalizedPath === file || normalizedPath.startsWith(file + sep); + if (matches) { + copiedCount++; + } + return matches; + }), + }); + + void extLogger.log(`Copied ${copiedCount} files to ${queryPackDir}`); + + 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); @@ -329,19 +376,54 @@ async function fixPackFile( } const qlpack = load(await readFile(packPath, "utf8")) as QlPack; - // update pack name qlpack.name = QUERY_PACK_NAME; - - // update default suite - delete qlpack.defaultSuiteFile; - qlpack.defaultSuite = generateDefaultSuite(packRelativePath); - - // remove any ${workspace} version references + updateDefaultSuite(qlpack, packRelativePath); removeWorkspaceRefs(qlpack); await writeFile(packPath, dump(qlpack)); } +async function injectExtensionPacks( + cliServer: cli.CodeQLCliServer, + queryPackDir: string, + workspaceFolders: string[], +) { + const qlpackFile = await getQlPackPath(queryPackDir); + if (!qlpackFile) { + throw new Error( + `Could not find ${QLPACK_FILENAMES.join( + " or ", + )} file in '${queryPackDir}'`, + ); + } + const syntheticQueryPack = load(await readFile(qlpackFile, "utf8")) as QlPack; + + const extensionPacks = await cliServer.resolveQlpacks(workspaceFolders, true); + Object.entries(extensionPacks).forEach(([name, paths]) => { + // We are guaranteed that there is at least one path found for each extension pack. + // If there are multiple paths, then we have a problem. This means that there is + // ambiguity in which path to use. This is an error. + if (paths.length > 1) { + throw new Error( + `Multiple versions of extension pack '${name}' found: ${paths.join( + ", ", + )}`, + ); + } + // Add this extension pack as a dependency. It doesn't matter which + // version we specify, since we are guaranteed that the extension pack + // is resolved from source at the given path. + syntheticQueryPack.dependencies[name] = "*"; + }); + await writeFile(qlpackFile, dump(syntheticQueryPack)); + await cliServer.clearCache(); +} + +function updateDefaultSuite(qlpack: QlPack, packRelativePath: string) { + delete qlpack.defaultSuiteFile; + qlpack.defaultSuite = generateDefaultSuite(packRelativePath); +} + function generateDefaultSuite(packRelativePath: string) { return [ { diff --git a/extensions/ql-vscode/src/variant-analysis/shared/data-flow-paths.ts b/extensions/ql-vscode/src/variant-analysis/shared/data-flow-paths.ts new file mode 100644 index 000000000..7ed20b69f --- /dev/null +++ b/extensions/ql-vscode/src/variant-analysis/shared/data-flow-paths.ts @@ -0,0 +1,8 @@ +import { AnalysisMessage, CodeFlow, ResultSeverity } from "./analysis-result"; + +export interface DataFlowPaths { + codeFlows: CodeFlow[]; + ruleDescription: string; + message: AnalysisMessage; + severity: ResultSeverity; +} 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 af42da85c..276a3af36 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts @@ -51,7 +51,11 @@ import { import { readFile, readJson, remove, pathExists, outputJson } from "fs-extra"; import { EOL } from "os"; import { cancelVariantAnalysis } from "./gh-api/gh-actions-api-client"; -import { ProgressCallback, UserCancellationException } from "../commandRunner"; +import { + ProgressCallback, + UserCancellationException, + withProgress, +} from "../progress"; import { CodeQLCliServer } from "../cli"; import { defaultFilterSortState, @@ -62,6 +66,8 @@ import { URLSearchParams } from "url"; import { DbManager } from "../databases/db-manager"; import { App } from "../common/app"; import { redactableError } from "../pure/errors"; +import { AppCommandManager, VariantAnalysisCommands } from "../common/commands"; +import { exportVariantAnalysisResults } from "./export-results"; export class VariantAnalysisManager extends DisposableObject @@ -123,11 +129,54 @@ export class VariantAnalysisManager ); } + getCommands(): VariantAnalysisCommands { + return { + "codeQL.autoDownloadVariantAnalysisResult": + this.enqueueDownload.bind(this), + "codeQL.copyVariantAnalysisRepoList": + this.copyRepoListToClipboard.bind(this), + "codeQL.loadVariantAnalysisRepoResults": this.loadResults.bind(this), + "codeQL.monitorVariantAnalysis": this.monitorVariantAnalysis.bind(this), + "codeQL.openVariantAnalysisLogs": this.openVariantAnalysisLogs.bind(this), + "codeQL.openVariantAnalysisView": this.showView.bind(this), + "codeQL.runVariantAnalysis": + this.runVariantAnalysisFromCommand.bind(this), + // Since we are tracking extension usage through commands, this command mirrors the "codeQL.runVariantAnalysis" command + "codeQL.runVariantAnalysisContextEditor": + this.runVariantAnalysisFromCommand.bind(this), + }; + } + + get commandManager(): AppCommandManager { + return this.app.commands; + } + + private async runVariantAnalysisFromCommand(uri?: Uri) { + return withProgress( + async (progress, token) => + this.runVariantAnalysis( + uri || Window.activeTextEditor?.document.uri, + progress, + token, + ), + { + title: "Run Variant Analysis", + cancellable: true, + }, + ); + } + public async runVariantAnalysis( uri: Uri | undefined, progress: ProgressCallback, token: CancellationToken, ): Promise { + progress({ + maxStep: 5, + step: 0, + message: "Getting credentials", + }); + const { actionBranch, base64Pack, @@ -452,19 +501,16 @@ export class VariantAnalysisManager public async monitorVariantAnalysis( variantAnalysis: VariantAnalysis, - cancellationToken: CancellationToken, ): Promise { await this.variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, this.app.credentials, - cancellationToken, ); } public async autoDownloadVariantAnalysisResult( scannedRepo: VariantAnalysisScannedRepository, variantAnalysis: VariantAnalysis, - cancellationToken: CancellationToken, ): Promise { if ( this.repoStates.get(variantAnalysis.id)?.[scannedRepo.repository.id] @@ -481,13 +527,6 @@ export class VariantAnalysisManager await this.onRepoStateUpdated(variantAnalysis.id, repoState); - if (cancellationToken && cancellationToken.isCancellationRequested) { - repoState.downloadStatus = - VariantAnalysisScannedRepositoryDownloadStatus.Failed; - await this.onRepoStateUpdated(variantAnalysis.id, repoState); - return; - } - let repoTask: VariantAnalysisRepositoryTask; try { const repoTaskResponse = await getVariantAnalysisRepo( @@ -562,14 +601,9 @@ export class VariantAnalysisManager public async enqueueDownload( scannedRepo: VariantAnalysisScannedRepository, variantAnalysis: VariantAnalysis, - token: CancellationToken, ): Promise { await this.queue.add(() => - this.autoDownloadVariantAnalysisResult( - scannedRepo, - variantAnalysis, - token, - ), + this.autoDownloadVariantAnalysisResult(scannedRepo, variantAnalysis), ); } @@ -647,6 +681,18 @@ export class VariantAnalysisManager await env.clipboard.writeText(text.join(EOL)); } + public async exportResults( + variantAnalysisId: number, + filterSort?: RepositoriesFilterSortStateWithIds, + ) { + await exportVariantAnalysisResults( + this, + variantAnalysisId, + filterSort, + this.app.credentials, + ); + } + private getRepoStatesStoragePath(variantAnalysisId: number): string { return join( this.getVariantAnalysisStorageLocation(variantAnalysisId), diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-monitor.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-monitor.ts index f7683fb4b..7fa8f3b14 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-monitor.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-monitor.ts @@ -1,4 +1,4 @@ -import { CancellationToken, commands, EventEmitter } from "vscode"; +import { commands, EventEmitter } from "vscode"; import { getVariantAnalysis } from "./gh-api/gh-api-client"; import { @@ -37,7 +37,6 @@ export class VariantAnalysisMonitor extends DisposableObject { public async monitorVariantAnalysis( variantAnalysis: VariantAnalysis, credentials: Credentials, - cancellationToken: CancellationToken, ): Promise { let attemptCount = 0; const scannedReposDownloaded: number[] = []; @@ -45,10 +44,6 @@ export class VariantAnalysisMonitor extends DisposableObject { while (attemptCount <= VariantAnalysisMonitor.maxAttemptCount) { await sleep(VariantAnalysisMonitor.sleepTime); - if (cancellationToken && cancellationToken.isCancellationRequested) { - return; - } - if (await this.shouldCancelMonitor(variantAnalysis.id)) { return; } diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view-manager.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view-manager.ts index 7f307c42d..4db740683 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view-manager.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view-manager.ts @@ -2,6 +2,8 @@ import { VariantAnalysis, VariantAnalysisScannedRepositoryState, } from "./shared/variant-analysis"; +import { AppCommandManager } from "../common/commands"; +import { RepositoriesFilterSortStateWithIds } from "../pure/variant-analysis-filter-sort"; export interface VariantAnalysisViewInterface { variantAnalysisId: number; @@ -11,6 +13,8 @@ export interface VariantAnalysisViewInterface { export interface VariantAnalysisViewManager< T extends VariantAnalysisViewInterface, > { + commandManager: AppCommandManager; + registerView(view: T): void; unregisterView(view: T): void; getView(variantAnalysisId: number): T | undefined; @@ -21,4 +25,11 @@ export interface VariantAnalysisViewManager< getRepoStates( variantAnalysisId: number, ): Promise; + openQueryFile(variantAnalysisId: number): Promise; + openQueryText(variantAnalysisId: number): Promise; + cancelVariantAnalysis(variantAnalysisId: number): Promise; + exportResults( + variantAnalysisId: number, + filterSort?: RepositoriesFilterSortStateWithIds, + ): Promise; } 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 e41a85205..ffd07c146 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-view.ts @@ -15,14 +15,21 @@ import { VariantAnalysisViewInterface, VariantAnalysisViewManager, } from "./variant-analysis-view-manager"; -import { showAndLogWarningMessage } from "../helpers"; +import { + showAndLogExceptionWithTelemetry, + showAndLogWarningMessage, +} from "../helpers"; import { telemetryListener } from "../telemetry"; +import { redactableError } from "../pure/errors"; +import { DataFlowPathsView } from "./data-flow-paths-view"; +import { DataFlowPaths } from "./shared/data-flow-paths"; export class VariantAnalysisView extends AbstractWebview implements VariantAnalysisViewInterface { public static readonly viewType = "codeQL.variantAnalysis"; + private readonly dataFlowPathsView: DataFlowPathsView; public constructor( ctx: ExtensionContext, @@ -32,6 +39,8 @@ export class VariantAnalysisView super(ctx); manager.registerView(this); + + this.dataFlowPathsView = new DataFlowPathsView(ctx); } public async openView() { @@ -106,10 +115,7 @@ export class VariantAnalysisView break; case "cancelVariantAnalysis": - void commands.executeCommand( - "codeQL.cancelVariantAnalysis", - this.variantAnalysisId, - ); + await this.manager.cancelVariantAnalysis(this.variantAnalysisId); break; case "requestRepositoryResults": void commands.executeCommand( @@ -119,16 +125,10 @@ export class VariantAnalysisView ); break; case "openQueryFile": - void commands.executeCommand( - "codeQL.openVariantAnalysisQueryFile", - this.variantAnalysisId, - ); + await this.manager.openQueryFile(this.variantAnalysisId); break; case "openQueryText": - void commands.executeCommand( - "codeQL.openVariantAnalysisQueryText", - this.variantAnalysisId, - ); + await this.manager.openQueryText(this.variantAnalysisId); break; case "copyRepositoryList": void commands.executeCommand( @@ -138,21 +138,30 @@ export class VariantAnalysisView ); break; case "exportResults": - void commands.executeCommand( - "codeQL.exportVariantAnalysisResults", + await this.manager.exportResults( this.variantAnalysisId, msg.filterSort, ); break; case "openLogs": - await commands.executeCommand( + await this.manager.commandManager.execute( "codeQL.openVariantAnalysisLogs", this.variantAnalysisId, ); break; + case "showDataFlowPaths": + await this.showDataFlows(msg.dataFlowPaths); + break; case "telemetry": telemetryListener?.sendUIInteraction(msg.action); break; + case "unhandledError": + void showAndLogExceptionWithTelemetry( + redactableError( + msg.error, + )`Unhandled error in variant analysis results view: ${msg.error.message}`, + ); + break; default: assertNever(msg); } @@ -193,4 +202,8 @@ export class VariantAnalysisView ? `${variantAnalysis.query.name} - Variant Analysis Results` : `Variant Analysis ${this.variantAnalysisId} - Results`; } + + private async showDataFlows(dataFlows: DataFlowPaths): Promise { + await this.dataFlowPathsView.showDataFlows(dataFlows); + } } diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx index 90bd3b44e..e7bb09f5b 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx @@ -1,17 +1,13 @@ import * as React from "react"; -import { useRef, useState } from "react"; import styled from "styled-components"; import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; -import { Overlay, ThemeProvider } from "@primer/react"; - import { AnalysisMessage, CodeFlow, ResultSeverity, } from "../../../variant-analysis/shared/analysis-result"; -import { CodePathsOverlay } from "./CodePathsOverlay"; -import { useTelemetryOnChange } from "../telemetry"; +import { vscode } from "../../vscode-api"; const ShowPathsLink = styled(VSCodeLink)` cursor: pointer; @@ -24,46 +20,27 @@ export type CodePathsProps = { severity: ResultSeverity; }; -const filterIsOpenTelemetry = (v: boolean) => v; - export const CodePaths = ({ codeFlows, ruleDescription, message, severity, }: CodePathsProps) => { - const [isOpen, setIsOpen] = useState(false); - useTelemetryOnChange(isOpen, "code-path-is-open", { - filterTelemetryOnValue: filterIsOpenTelemetry, - }); - - const linkRef = useRef(null); - - const closeOverlay = () => setIsOpen(false); + const onShowPathsClick = () => { + vscode.postMessage({ + t: "showDataFlowPaths", + dataFlowPaths: { + codeFlows, + ruleDescription, + message, + severity, + }, + }); + }; return ( <> - setIsOpen(true)} ref={linkRef}> - Show paths - - {isOpen && ( - - - - - - )} + Show paths ); }; diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx deleted file mode 100644 index 7ba11898a..000000000 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import * as React from "react"; -import { useState } from "react"; -import styled from "styled-components"; - -import { - AnalysisMessage, - CodeFlow, - ResultSeverity, -} from "../../../variant-analysis/shared/analysis-result"; -import { useTelemetryOnChange } from "../telemetry"; -import { SectionTitle } from "../SectionTitle"; -import { VerticalSpace } from "../VerticalSpace"; -import { CodeFlowsDropdown } from "./CodeFlowsDropdown"; -import { CodePath } from "./CodePath"; - -const StyledCloseButton = styled.button` - position: absolute; - top: 1em; - right: 4em; - background-color: var(--vscode-editor-background); - color: var(--vscode-editor-foreground); - border: none; - cursor: pointer; - - &:focus-visible { - outline: none; - } -`; - -const OverlayContainer = styled.div` - height: 100%; - width: 100%; - padding: 2em; - position: fixed; - top: 0; - left: 0; - background-color: var(--vscode-editor-background); - color: var(--vscode-editor-foreground); - overflow-y: scroll; -`; - -const CloseButton = ({ onClick }: { onClick: () => void }) => ( - - - -); - -const PathsContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; -`; - -const PathDetailsContainer = styled.div` - padding: 0; - border: 0; -`; - -const PathDropdownContainer = styled.div` - flex-grow: 1; - padding: 0 0 0 0.2em; - border: none; -`; - -type CodePathsOverlayProps = { - codeFlows: CodeFlow[]; - ruleDescription: string; - message: AnalysisMessage; - severity: ResultSeverity; - onClose: () => void; -}; - -export const CodePathsOverlay = ({ - codeFlows, - ruleDescription, - message, - severity, - onClose, -}: CodePathsOverlayProps) => { - const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]); - useTelemetryOnChange(selectedCodeFlow, "code-flow-selected"); - - return ( - - - - {ruleDescription} - - - - - {codeFlows.length} paths available:{" "} - {selectedCodeFlow.threadFlows.length} steps in - - - - - - - - - - - ); -}; diff --git a/extensions/ql-vscode/src/view/common/CodePaths/__tests__/CodePaths.spec.tsx b/extensions/ql-vscode/src/view/common/CodePaths/__tests__/CodePaths.spec.tsx index a45ed417b..1ff6135e1 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/__tests__/CodePaths.spec.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/__tests__/CodePaths.spec.tsx @@ -18,20 +18,25 @@ describe(CodePaths.name, () => { />, ); - it("renders correctly when unexpanded", () => { + it("renders 'show paths' link", () => { render(); expect(screen.getByText("Show paths")).toBeInTheDocument(); - expect(screen.queryByText("Code snippet text")).not.toBeInTheDocument(); - expect(screen.queryByText("Rule description")).not.toBeInTheDocument(); }); - it("renders correctly when expanded", async () => { + it("posts extension message when 'show paths' link clicked", async () => { render(); await userEvent.click(screen.getByText("Show paths")); - expect(screen.getByText("Code snippet text")).toBeInTheDocument(); - expect(screen.getByText("Rule description")).toBeInTheDocument(); + expect((window as any).vsCodeApi.postMessage).toHaveBeenCalledWith({ + t: "showDataFlowPaths", + dataFlowPaths: { + codeFlows: createMockCodeFlows(), + ruleDescription: "Rule description", + message: createMockAnalysisMessage(), + severity: "Recommendation", + }, + }); }); }); diff --git a/extensions/ql-vscode/src/view/common/errors.ts b/extensions/ql-vscode/src/view/common/errors.ts new file mode 100644 index 000000000..e6a443de1 --- /dev/null +++ b/extensions/ql-vscode/src/view/common/errors.ts @@ -0,0 +1,54 @@ +import { getErrorMessage, getErrorStack } from "../../pure/helpers-pure"; +import { vscode } from "../vscode-api"; + +// Keep track of previous errors that have happened. +// The listeners for uncaught errors and rejections can get triggered +// twice for each error. This is believed to be an effect caused +// by React's error boundaries. Adding an error boundary stops +// this duplicate reporting for errors that happen during component +// rendering, but unfortunately errors from event handlers and +// timeouts are still duplicated and there does not appear to be +// a way around this. +const previousErrors: Set = new Set(); + +function shouldReportError(error: Error): boolean { + const seenBefore = previousErrors.has(error); + previousErrors.add(error); + setTimeout(() => { + previousErrors.delete(error); + }, 1000); + return !seenBefore; +} + +const unhandledErrorListener = (event: ErrorEvent) => { + if (shouldReportError(event.error)) { + vscode.postMessage({ + t: "unhandledError", + error: { + message: getErrorMessage(event.error), + stack: getErrorStack(event.error), + }, + }); + } +}; + +const unhandledRejectionListener = (event: PromiseRejectionEvent) => { + if (shouldReportError(event.reason)) { + vscode.postMessage({ + t: "unhandledError", + error: { + message: getErrorMessage(event.reason), + stack: getErrorStack(event.reason), + }, + }); + } +}; + +/** + * Adds listeners for unhandled errors / rejected promises. + * When an error is detected a "unhandledError" message is posted to the view. + */ +export function registerUnhandledErrorListener() { + window.addEventListener("error", unhandledErrorListener); + window.addEventListener("unhandledrejection", unhandledRejectionListener); +} diff --git a/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPaths.tsx b/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPaths.tsx new file mode 100644 index 000000000..7bccc0bed --- /dev/null +++ b/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPaths.tsx @@ -0,0 +1,71 @@ +import * as React from "react"; +import styled from "styled-components"; +import { useState } from "react"; + +import { useTelemetryOnChange } from "../common/telemetry"; +import { CodeFlowsDropdown } from "../common/CodePaths/CodeFlowsDropdown"; +import { SectionTitle, VerticalSpace } from "../common"; +import { CodePath } from "../common/CodePaths/CodePath"; +import { DataFlowPaths as DataFlowPathsDomainModel } from "../../variant-analysis/shared/data-flow-paths"; + +const PathsContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +const PathDetailsContainer = styled.div` + padding: 0; + border: 0; +`; + +const PathDropdownContainer = styled.div` + flex-grow: 1; + padding: 0 0 0 0.2em; + border: none; +`; + +export type DataFlowPathsProps = { + dataFlowPaths: DataFlowPathsDomainModel; +}; + +export const DataFlowPaths = ({ + dataFlowPaths, +}: { + dataFlowPaths: DataFlowPathsDomainModel; +}): JSX.Element => { + const [selectedCodeFlow, setSelectedCodeFlow] = useState( + dataFlowPaths.codeFlows[0], + ); + useTelemetryOnChange(selectedCodeFlow, "code-flow-selected"); + + const { codeFlows, ruleDescription, message, severity } = dataFlowPaths; + + return ( + <> + + {ruleDescription} + + + + + {codeFlows.length} paths available:{" "} + {selectedCodeFlow?.threadFlows.length} steps in + + + + + + + + + + ); +}; diff --git a/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPathsView.tsx b/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPathsView.tsx new file mode 100644 index 000000000..de3d490a7 --- /dev/null +++ b/extensions/ql-vscode/src/view/data-flow-paths/DataFlowPathsView.tsx @@ -0,0 +1,47 @@ +import * as React from "react"; +import { useEffect, useState } from "react"; +import { ToDataFlowPathsMessage } from "../../pure/interface-types"; +import { DataFlowPaths as DataFlowPathsDomainModel } from "../../variant-analysis/shared/data-flow-paths"; +import { DataFlowPaths } from "./DataFlowPaths"; + +export type DataFlowPathsViewProps = { + dataFlowPaths?: DataFlowPathsDomainModel; +}; + +export function DataFlowPathsView({ + dataFlowPaths: initialDataFlowPaths, +}: DataFlowPathsViewProps): JSX.Element { + const [dataFlowPaths, setDataFlowPaths] = useState< + DataFlowPathsDomainModel | undefined + >(initialDataFlowPaths); + + useEffect(() => { + const listener = (evt: MessageEvent) => { + if (evt.origin === window.origin) { + const msg: ToDataFlowPathsMessage = evt.data; + if (msg.t === "setDataFlowPaths") { + setDataFlowPaths(msg.dataFlowPaths); + + // Scroll to the top of the page when we're rendering + // new data flow paths. + window.scrollTo(0, 0); + } + } else { + // sanitize origin + const origin = evt.origin.replace(/\n|\r/g, ""); + console.error(`Invalid event origin ${origin}`); + } + }; + window.addEventListener("message", listener); + + return () => { + window.removeEventListener("message", listener); + }; + }, []); + + if (!dataFlowPaths) { + return <>Loading data flow paths; + } + + return ; +} diff --git a/extensions/ql-vscode/src/view/data-flow-paths/__tests__/DataFlowPaths.spec.tsx b/extensions/ql-vscode/src/view/data-flow-paths/__tests__/DataFlowPaths.spec.tsx new file mode 100644 index 000000000..02efd2105 --- /dev/null +++ b/extensions/ql-vscode/src/view/data-flow-paths/__tests__/DataFlowPaths.spec.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import { render as reactRender, screen } from "@testing-library/react"; +import { DataFlowPaths, DataFlowPathsProps } from "../DataFlowPaths"; +import { createMockDataFlowPaths } from "../../../../test/factories/variant-analysis/shared/data-flow-paths"; + +describe(DataFlowPaths.name, () => { + const render = (props: DataFlowPathsProps) => + reactRender(); + + it("renders data flow paths", () => { + const dataFlowPaths = createMockDataFlowPaths(); + + render({ dataFlowPaths }); + + expect(screen.getByText(dataFlowPaths.ruleDescription)).toBeInTheDocument(); + expect( + screen.getByText("1 paths available", { exact: false }), + ).toBeInTheDocument(); + expect( + screen.getByText("3 steps in", { + exact: false, + }), + ).toBeInTheDocument(); + + expect( + screen.getByText("This zip file may have a dangerous path", { + exact: false, + }), + ).toBeInTheDocument(); + }); +}); diff --git a/extensions/ql-vscode/src/view/data-flow-paths/__tests__/DataFlowPathsView.spec.tsx b/extensions/ql-vscode/src/view/data-flow-paths/__tests__/DataFlowPathsView.spec.tsx new file mode 100644 index 000000000..7baefc06c --- /dev/null +++ b/extensions/ql-vscode/src/view/data-flow-paths/__tests__/DataFlowPathsView.spec.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import { render as reactRender, screen } from "@testing-library/react"; +import { + DataFlowPathsView, + DataFlowPathsViewProps, +} from "../DataFlowPathsView"; +import { createMockCodeFlows } from "../../../../test/factories/variant-analysis/shared/CodeFlow"; +import { createMockDataFlowPaths } from "../../../../test/factories/variant-analysis/shared/data-flow-paths"; + +describe(DataFlowPathsView.name, () => { + const render = (props: Partial) => + reactRender(); + + it("renders a loading data flow paths view", () => { + render({}); + + expect(screen.getByText("Loading data flow paths")).toBeInTheDocument(); + }); + + it("renders a data flow paths view", () => { + const dataFlowPaths = createMockDataFlowPaths({ + ruleDescription: "Rule description", + codeFlows: createMockCodeFlows(), + }); + + render({ dataFlowPaths }); + + expect(screen.queryByText("Code snippet text")).toBeInTheDocument(); + expect(screen.getByText("Rule description")).toBeInTheDocument(); + }); +}); diff --git a/extensions/ql-vscode/src/view/data-flow-paths/index.tsx b/extensions/ql-vscode/src/view/data-flow-paths/index.tsx new file mode 100644 index 000000000..5fd13430b --- /dev/null +++ b/extensions/ql-vscode/src/view/data-flow-paths/index.tsx @@ -0,0 +1,9 @@ +import * as React from "react"; +import { WebviewDefinition } from "../webview-definition"; +import { DataFlowPathsView } from "./DataFlowPathsView"; + +const definition: WebviewDefinition = { + component: , +}; + +export default definition; diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx index cf4a0df25..83d473563 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx @@ -13,7 +13,7 @@ import { VariantAnalysisLoading } from "./VariantAnalysisLoading"; import { ToVariantAnalysisMessage } from "../../pure/interface-types"; import { vscode } from "../vscode-api"; import { defaultFilterSortState } from "../../pure/variant-analysis-filter-sort"; -import { useTelemetryOnChange } from "../common/telemetry"; +import { sendTelemetry, useTelemetryOnChange } from "../common/telemetry"; export type VariantAnalysisProps = { variantAnalysis?: VariantAnalysisDomainModel; @@ -25,18 +25,21 @@ const openQueryFile = () => { vscode.postMessage({ t: "openQueryFile", }); + sendTelemetry("variant-analysis-open-query-file"); }; const openQueryText = () => { vscode.postMessage({ t: "openQueryText", }); + sendTelemetry("variant-analysis-open-query-text"); }; const stopQuery = () => { vscode.postMessage({ t: "cancelVariantAnalysis", }); + sendTelemetry("variant-analysis-cancel"); }; const openLogs = () => { @@ -136,6 +139,7 @@ export function VariantAnalysis({ repositoryIds: selectedRepositoryIds, }, }); + sendTelemetry("variant-analysis-export-results"); }, [filterSortState, selectedRepositoryIds]); if ( diff --git a/extensions/ql-vscode/src/view/webview.tsx b/extensions/ql-vscode/src/view/webview.tsx index 4698a33d8..9207975d9 100644 --- a/extensions/ql-vscode/src/view/webview.tsx +++ b/extensions/ql-vscode/src/view/webview.tsx @@ -7,8 +7,11 @@ import { WebviewDefinition } from "./webview-definition"; // Allow all views to use Codicons import "@vscode/codicons/dist/codicon.css"; +import { registerUnhandledErrorListener } from "./common/errors"; const render = () => { + registerUnhandledErrorListener(); + const element = document.getElementById("root"); if (!element) { @@ -27,9 +30,9 @@ const render = () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const view: WebviewDefinition = require(`./${viewName}/index.tsx`).default; - createRoot(element).render( + const root = createRoot(element); + root.render( - {/* Post a message to the extension when fully loaded. See https://github.com/reactwg/react-18/discussions/5 ("What about the render callback?")*/}
vscode.postMessage({ t: "viewLoaded", viewName })}> {view.component}
diff --git a/extensions/ql-vscode/src/vscode-utils/external-files.ts b/extensions/ql-vscode/src/vscode-utils/external-files.ts new file mode 100644 index 000000000..b355199ef --- /dev/null +++ b/extensions/ql-vscode/src/vscode-utils/external-files.ts @@ -0,0 +1,50 @@ +import { Uri, window } from "vscode"; +import { AppCommandManager } from "../common/commands"; +import { + showAndLogExceptionWithTelemetry, + showBinaryChoiceDialog, +} from "../helpers"; +import { redactableError } from "../pure/errors"; +import { asError, getErrorMessage, getErrorStack } from "../pure/helpers-pure"; + +export async function tryOpenExternalFile( + commandManager: AppCommandManager, + fileLocation: string, +) { + const uri = Uri.file(fileLocation); + try { + await window.showTextDocument(uri, { preview: false }); + } catch (e) { + const msg = getErrorMessage(e); + if ( + msg.includes("Files above 50MB cannot be synchronized with extensions") || + msg.includes("too large to open") + ) { + const res = await showBinaryChoiceDialog( + `VS Code does not allow extensions to open files >50MB. This file +exceeds that limit. Do you want to open it outside of VS Code? + +You can also try manually opening it inside VS Code by selecting +the file in the file explorer and dragging it into the workspace.`, + ); + if (res) { + try { + await commandManager.execute("revealFileInOS", uri); + } catch (e) { + void showAndLogExceptionWithTelemetry( + redactableError( + asError(e), + )`Failed to reveal file in OS: ${getErrorMessage(e)}`, + ); + } + } + } else { + void showAndLogExceptionWithTelemetry( + redactableError(asError(e))`Could not open file ${fileLocation}`, + { + fullMessage: `${getErrorMessage(e)}\n${getErrorStack(e)}`, + }, + ); + } + } +} diff --git a/extensions/ql-vscode/src/vscode-utils/ui-service.ts b/extensions/ql-vscode/src/vscode-utils/ui-service.ts deleted file mode 100644 index c2ffef0c8..000000000 --- a/extensions/ql-vscode/src/vscode-utils/ui-service.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { TreeDataProvider, window } from "vscode"; -import { DisposableObject } from "../pure/disposable-object"; -import { commandRunner } from "../commandRunner"; - -/** - * A VS Code service that interacts with the UI, including handling commands. - */ -export class UIService extends DisposableObject { - protected constructor() { - super(); - } - - /** - * Registers a command handler with Visual Studio Code. - * @param command The ID of the command to register. - * @param callback Callback function to implement the command. - * @remarks The command handler is automatically unregistered when the service is disposed. - */ - protected registerCommand( - command: string, - callback: (...args: any[]) => any, - ): void { - this.push(commandRunner(command, callback.bind(this))); - } - - protected registerTreeDataProvider( - viewId: string, - treeDataProvider: TreeDataProvider, - ): void { - this.push(window.registerTreeDataProvider(viewId, treeDataProvider)); - } -} diff --git a/extensions/ql-vscode/supported_cli_versions.json b/extensions/ql-vscode/supported_cli_versions.json index 58b5b931d..124e33c2b 100644 --- a/extensions/ql-vscode/supported_cli_versions.json +++ b/extensions/ql-vscode/supported_cli_versions.json @@ -1,5 +1,5 @@ [ - "v2.12.4", + "v2.12.5", "v2.11.6", "v2.7.6", "v2.8.5", diff --git a/extensions/ql-vscode/test/__mocks__/appMock.ts b/extensions/ql-vscode/test/__mocks__/appMock.ts index e5f74be29..1b6d9da9d 100644 --- a/extensions/ql-vscode/test/__mocks__/appMock.ts +++ b/extensions/ql-vscode/test/__mocks__/appMock.ts @@ -6,23 +6,25 @@ import { createMockLogger } from "./loggerMock"; import { createMockMemento } from "../mock-memento"; import { testCredentialsWithStub } from "../factories/authentication"; import { Credentials } from "../../src/common/authentication"; +import { AppCommandManager } from "../../src/common/commands"; +import { createMockCommandManager } from "./commandsMock"; export function createMockApp({ extensionPath = "/mock/extension/path", workspaceStoragePath = "/mock/workspace/storage/path", globalStoragePath = "/mock/global/storage/path", createEventEmitter = () => new MockAppEventEmitter(), - executeCommand = jest.fn(() => Promise.resolve()), workspaceState = createMockMemento(), credentials = testCredentialsWithStub(), + commands = createMockCommandManager(), }: { extensionPath?: string; workspaceStoragePath?: string; globalStoragePath?: string; createEventEmitter?: () => AppEventEmitter; - executeCommand?: () => Promise; workspaceState?: Memento; credentials?: Credentials; + commands?: AppCommandManager; }): App { return { mode: AppMode.Test, @@ -33,8 +35,8 @@ export function createMockApp({ globalStoragePath, workspaceState, createEventEmitter, - executeCommand, credentials, + commands, }; } diff --git a/extensions/ql-vscode/test/__mocks__/commandsMock.ts b/extensions/ql-vscode/test/__mocks__/commandsMock.ts new file mode 100644 index 000000000..16e92606b --- /dev/null +++ b/extensions/ql-vscode/test/__mocks__/commandsMock.ts @@ -0,0 +1,13 @@ +import { AppCommandManager } from "../../src/common/commands"; +import { CommandFunction, CommandManager } from "../../src/packages/commands"; +import { Disposable } from "../../src/packages/commands/Disposable"; + +export function createMockCommandManager({ + registerCommand = jest.fn(), + executeCommand = jest.fn(), +}: { + registerCommand?: (commandName: string, fn: CommandFunction) => Disposable; + executeCommand?: (commandName: string, ...args: any[]) => Promise; +} = {}): AppCommandManager { + return new CommandManager(registerCommand, executeCommand); +} diff --git a/extensions/ql-vscode/test/__mocks__/loggerMock.ts b/extensions/ql-vscode/test/__mocks__/loggerMock.ts index 665720357..ff69b0f6e 100644 --- a/extensions/ql-vscode/test/__mocks__/loggerMock.ts +++ b/extensions/ql-vscode/test/__mocks__/loggerMock.ts @@ -4,6 +4,5 @@ export function createMockLogger(): Logger { return { log: jest.fn(() => Promise.resolve()), show: jest.fn(), - removeAdditionalLogLocation: jest.fn(), }; } diff --git a/extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts b/extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts index a15bea5aa..b05ebdb17 100644 --- a/extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts +++ b/extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts @@ -1,7 +1,7 @@ import { readdirSync, readFileSync } from "fs-extra"; import { join } from "path"; import * as tmp from "tmp"; -import { OutputChannelLogger } from "../../../src/common"; +import { Logger, OutputChannelLogger, TeeLogger } from "../../../src/common"; jest.setTimeout(999999); @@ -58,16 +58,19 @@ describe("OutputChannelLogger tests", function () { expect(mockOutputChannel.appendLine).not.toBeCalledWith("yyy"); expect(mockOutputChannel.append).toBeCalledWith("yyy"); - await logger.log("zzz", createLogOptions("hucairz")); + const hucairz = createSideLogger(logger, "hucairz"); + await hucairz.log("zzz"); // should have created 1 side log expect(readdirSync(tempFolders.storagePath.name)).toEqual(["hucairz"]); }); it("should create a side log", async () => { - await logger.log("xxx", createLogOptions("first")); - await logger.log("yyy", createLogOptions("second")); - await logger.log("zzz", createLogOptions("first", false)); + const first = createSideLogger(logger, "first"); + await first.log("xxx"); + const second = createSideLogger(logger, "second"); + await second.log("yyy"); + await first.log("zzz", { trailingNewline: false }); await logger.log("aaa"); // expect 2 side logs @@ -82,16 +85,13 @@ describe("OutputChannelLogger tests", function () { ).toBe("yyy\n"); }); - function createLogOptions( + function createSideLogger( + logger: Logger, additionalLogLocation: string, - trailingNewline?: boolean, - ) { - return { - additionalLogLocation: join( - tempFolders.storagePath.name, - additionalLogLocation, - ), - trailingNewline, - }; + ): Logger { + return new TeeLogger( + logger, + join(tempFolders.storagePath.name, additionalLogLocation), + ); } }); diff --git a/extensions/ql-vscode/test/factories/extension-context.ts b/extensions/ql-vscode/test/factories/extension-context.ts index d871e77d6..8c90b330c 100644 --- a/extensions/ql-vscode/test/factories/extension-context.ts +++ b/extensions/ql-vscode/test/factories/extension-context.ts @@ -19,5 +19,6 @@ export function createMockExtensionContext({ globalStorageUri: vscode.Uri.file(globalStoragePath), storageUri: vscode.Uri.file(workspaceStoragePath), workspaceState: createMockMemento(), + subscriptions: [], } as any as vscode.ExtensionContext; } diff --git a/extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts b/extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts index 157f60f3b..a67de64b0 100644 --- a/extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts +++ b/extensions/ql-vscode/test/factories/query-history/local-query-history-item.ts @@ -74,7 +74,6 @@ export function createMockQueryWithResults({ hasInterpretedResults?: boolean; hasMetadata?: boolean; }): QueryWithResults { - const dispose = jest.fn(); const deleteQuery = jest.fn(); const metadata = hasMetadata ? ({ name: "query-name" } as QueryMetadata) @@ -87,7 +86,6 @@ export function createMockQueryWithResults({ metadata, } as unknown as QueryEvaluationInfo, successful: didRunSuccessfully, - dispose, result: { evaluationTime: 1, queryId: 0, diff --git a/extensions/ql-vscode/test/factories/variant-analysis/shared/data-flow-paths.ts b/extensions/ql-vscode/test/factories/variant-analysis/shared/data-flow-paths.ts new file mode 100644 index 000000000..9f0ae98ac --- /dev/null +++ b/extensions/ql-vscode/test/factories/variant-analysis/shared/data-flow-paths.ts @@ -0,0 +1,122 @@ +import { + AnalysisMessage, + CodeFlow, + ResultSeverity, +} from "../../../../src/variant-analysis/shared/analysis-result"; +import { DataFlowPaths } from "../../../../src/variant-analysis/shared/data-flow-paths"; + +const defaultCodeFlows: CodeFlow[] = [ + { + threadFlows: [ + { + fileLink: { + fileLinkPrefix: + "https://github.com/PowerShell/PowerShell/blob/450d884668ca477c6581ce597958f021fac30bff", + filePath: + "src/System.Management.Automation/help/UpdatableHelpSystem.cs", + }, + codeSnippet: { + startLine: 1260, + endLine: 1260, + text: " string extractPath = Path.Combine(destination, entry.FullName);", + }, + highlightedRegion: { + startLine: 1260, + startColumn: 72, + endLine: 1260, + endColumn: 86, + }, + message: { + tokens: [ + { + t: "text", + text: "access to property FullName : String", + }, + ], + }, + }, + { + fileLink: { + fileLinkPrefix: + "https://github.com/PowerShell/PowerShell/blob/450d884668ca477c6581ce597958f021fac30bff", + filePath: + "src/System.Management.Automation/help/UpdatableHelpSystem.cs", + }, + codeSnippet: { + startLine: 1260, + endLine: 1260, + text: " string extractPath = Path.Combine(destination, entry.FullName);", + }, + highlightedRegion: { + startLine: 1260, + startColumn: 46, + endLine: 1260, + endColumn: 87, + }, + message: { + tokens: [ + { + t: "text", + text: "call to method Combine : String", + }, + ], + }, + }, + { + fileLink: { + fileLinkPrefix: + "https://github.com/PowerShell/PowerShell/blob/450d884668ca477c6581ce597958f021fac30bff", + filePath: + "src/System.Management.Automation/help/UpdatableHelpSystem.cs", + }, + codeSnippet: { + startLine: 1261, + endLine: 1261, + text: " entry.ExtractToFile(extractPath);", + }, + highlightedRegion: { + startLine: 1261, + startColumn: 45, + endLine: 1261, + endColumn: 56, + }, + message: { + tokens: [ + { + t: "text", + text: "access to local variable extractPath", + }, + ], + }, + }, + ], + }, +]; + +const defaultMessage: AnalysisMessage = { + tokens: [ + { + t: "text", + text: "This zip file may have a dangerous path", + }, + ], +}; + +export function createMockDataFlowPaths({ + codeFlows = defaultCodeFlows, + ruleDescription = "ZipSlip vulnerability", + message = defaultMessage, + severity = "Warning", +}: { + codeFlows?: CodeFlow[]; + ruleDescription?: string; + message?: AnalysisMessage; + severity?: ResultSeverity; +} = {}): DataFlowPaths { + return { + codeFlows, + ruleDescription, + message, + severity, + }; +} diff --git a/extensions/ql-vscode/test/unit-tests/databases/config/db-config-store.test.ts b/extensions/ql-vscode/test/unit-tests/databases/config/db-config-store.test.ts index 7e0af0d86..ca4b7ead6 100644 --- a/extensions/ql-vscode/test/unit-tests/databases/config/db-config-store.test.ts +++ b/extensions/ql-vscode/test/unit-tests/databases/config/db-config-store.test.ts @@ -19,6 +19,7 @@ import { createRemoteUserDefinedListDbItem, } from "../../../factories/db-item-factories"; import { createMockApp } from "../../../__mocks__/appMock"; +import { createMockCommandManager } from "../../../__mocks__/commandsMock"; describe("db config store", () => { const extensionPath = join(__dirname, "../../../.."); @@ -136,14 +137,16 @@ describe("db config store", () => { it("should set codeQLVariantAnalysisRepositories.configError to true when config has error", async () => { const testDataStoragePathInvalid = join(__dirname, "data", "invalid"); + const executeCommand = jest.fn(); const app = createMockApp({ extensionPath, workspaceStoragePath: testDataStoragePathInvalid, + commands: createMockCommandManager({ executeCommand }), }); const configStore = new DbConfigStore(app, false); await configStore.initialize(); - expect(app.executeCommand).toBeCalledWith( + expect(executeCommand).toBeCalledWith( "setContext", "codeQLVariantAnalysisRepositories.configError", true, @@ -152,14 +155,16 @@ describe("db config store", () => { }); it("should set codeQLVariantAnalysisRepositories.configError to false when config is valid", async () => { + const executeCommand = jest.fn(); const app = createMockApp({ extensionPath, workspaceStoragePath: testDataStoragePath, + commands: createMockCommandManager({ executeCommand }), }); const configStore = new DbConfigStore(app, false); await configStore.initialize(); - expect(app.executeCommand).toBeCalledWith( + expect(executeCommand).toBeCalledWith( "setContext", "codeQLVariantAnalysisRepositories.configError", false, diff --git a/extensions/ql-vscode/test/unit-tests/packages/commands/CommandManager.test.ts b/extensions/ql-vscode/test/unit-tests/packages/commands/CommandManager.test.ts index 40b78d932..96bede0b6 100644 --- a/extensions/ql-vscode/test/unit-tests/packages/commands/CommandManager.test.ts +++ b/extensions/ql-vscode/test/unit-tests/packages/commands/CommandManager.test.ts @@ -1,8 +1,111 @@ -import { CommandManager } from "../../../../src/packages/commands"; +import { + CommandFunction, + CommandManager, +} from "../../../../src/packages/commands"; -describe(CommandManager.name, () => { - it("can create a command manager", () => { - const commandManager = new CommandManager(); - expect(commandManager).not.toBeUndefined(); +describe("CommandManager", () => { + it("can register a command", () => { + const commandRegister = jest.fn(); + const commandManager = new CommandManager>( + commandRegister, + jest.fn(), + ); + const myCommand = jest.fn(); + commandManager.register("abc", myCommand); + expect(commandRegister).toHaveBeenCalledTimes(1); + expect(commandRegister).toHaveBeenCalledWith("abc", myCommand); + }); + + it("can register typed commands", async () => { + const commands = { + "codeQL.openVariantAnalysisLogs": async (variantAnalysisId: number) => { + return variantAnalysisId * 10; + }, + }; + const commandManager = new CommandManager( + jest.fn(), + jest.fn(), + ); + + // @ts-expect-error wrong command name should give a type error + commandManager.register("abc", jest.fn()); + + commandManager.register( + "codeQL.openVariantAnalysisLogs", + // @ts-expect-error wrong function parameter type should give a type error + async (variantAnalysisId: string): Promise => 10, + ); + + commandManager.register( + "codeQL.openVariantAnalysisLogs", + // @ts-expect-error wrong function return type should give a type error + async (variantAnalysisId: number): Promise => "hello", + ); + + // Working types + commandManager.register( + "codeQL.openVariantAnalysisLogs", + async (variantAnalysisId: number): Promise => + variantAnalysisId * 10, + ); + }); + + it("can dispose of its commands", () => { + const dispose1 = jest.fn(); + const dispose2 = jest.fn(); + const commandRegister = jest + .fn() + .mockReturnValueOnce({ dispose: dispose1 }) + .mockReturnValueOnce({ dispose: dispose2 }); + const commandManager = new CommandManager>( + commandRegister, + jest.fn(), + ); + commandManager.register("abc", jest.fn()); + commandManager.register("def", jest.fn()); + expect(dispose1).not.toHaveBeenCalled(); + expect(dispose2).not.toHaveBeenCalled(); + commandManager.dispose(); + expect(dispose1).toHaveBeenCalledTimes(1); + expect(dispose2).toHaveBeenCalledTimes(1); + }); + + it("can execute a command", async () => { + const commandExecute = jest.fn().mockReturnValue(7); + const commandManager = new CommandManager>( + jest.fn(), + commandExecute, + ); + const result = await commandManager.execute("abc", "hello", true); + expect(result).toEqual(7); + expect(commandExecute).toHaveBeenCalledTimes(1); + expect(commandExecute).toHaveBeenCalledWith("abc", "hello", true); + }); + + it("can execute typed commands", async () => { + const commands = { + "codeQL.openVariantAnalysisLogs": async (variantAnalysisId: number) => { + return variantAnalysisId * 10; + }, + }; + const commandManager = new CommandManager( + jest.fn(), + jest.fn(), + ); + + // @ts-expect-error wrong command name should give a type error + await commandManager.execute("abc", 4); + + await commandManager.execute( + "codeQL.openVariantAnalysisLogs", + // @ts-expect-error wrong argument type should give a type error + "xyz", + ); + + // @ts-expect-error wrong number of arguments should give a type error + await commandManager.execute("codeQL.openVariantAnalysisLogs", 2, 3); + + // Working types + await commandManager.execute("codeQL.openVariantAnalysisLogs", 7); }); }); diff --git a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-manager.test.ts index 024a1bc82..05e9b5ed6 100644 --- a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-manager.test.ts @@ -1,5 +1,4 @@ import { - CancellationTokenSource, commands, env, extensions, @@ -54,7 +53,6 @@ jest.setTimeout(3 * 60 * 1000); describe("Variant Analysis Manager", () => { let app: App; - let cancellationTokenSource: CancellationTokenSource; let variantAnalysisManager: VariantAnalysisManager; let variantAnalysisResultsManager: VariantAnalysisResultsManager; let variantAnalysis: VariantAnalysis; @@ -63,8 +61,6 @@ describe("Variant Analysis Manager", () => { beforeEach(async () => { jest.spyOn(extLogger, "log").mockResolvedValue(undefined); - cancellationTokenSource = new CancellationTokenSource(); - scannedRepos = createMockScannedRepos(); variantAnalysis = createMockVariantAnalysis({ status: VariantAnalysisStatus.InProgress, @@ -203,7 +199,6 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ); expect(getVariantAnalysisRepoResultStub).not.toHaveBeenCalled(); @@ -228,23 +223,10 @@ describe("Variant Analysis Manager", () => { getVariantAnalysisRepoResultStub.mockResolvedValue(response); }); - it("should return early if variant analysis is cancelled", async () => { - cancellationTokenSource.cancel(); - - await variantAnalysisManager.autoDownloadVariantAnalysisResult( - scannedRepos[0], - variantAnalysis, - cancellationTokenSource.token, - ); - - expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled(); - }); - it("should fetch a repo task", async () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ); expect(getVariantAnalysisRepoStub).toHaveBeenCalled(); @@ -254,7 +236,6 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ); expect(getVariantAnalysisRepoResultStub).toHaveBeenCalled(); @@ -265,7 +246,6 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ); getVariantAnalysisRepoStub.mockClear(); @@ -273,7 +253,6 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ); expect(getVariantAnalysisRepoStub).not.toHaveBeenCalled(); @@ -283,7 +262,6 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ); await expect(fs.readJson(repoStatesPath)).resolves.toEqual({ @@ -304,7 +282,6 @@ describe("Variant Analysis Manager", () => { variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ), ).rejects.toThrow(); @@ -320,7 +297,6 @@ describe("Variant Analysis Manager", () => { variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ), ).rejects.toThrow(); @@ -329,7 +305,6 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[1], variantAnalysis, - cancellationTokenSource.token, ); await expect(fs.readJson(repoStatesPath)).resolves.toEqual({ @@ -355,7 +330,6 @@ describe("Variant Analysis Manager", () => { variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ), ).rejects.toThrow(); @@ -364,7 +338,6 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[1], variantAnalysis, - cancellationTokenSource.token, ); await expect(fs.readJson(repoStatesPath)).resolves.toEqual({ @@ -400,7 +373,6 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.autoDownloadVariantAnalysisResult( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ); await expect(fs.readJson(repoStatesPath)).resolves.toEqual({ @@ -439,17 +411,14 @@ describe("Variant Analysis Manager", () => { await variantAnalysisManager.enqueueDownload( scannedRepos[0], variantAnalysis, - cancellationTokenSource.token, ); await variantAnalysisManager.enqueueDownload( scannedRepos[1], variantAnalysis, - cancellationTokenSource.token, ); await variantAnalysisManager.enqueueDownload( scannedRepos[2], variantAnalysis, - cancellationTokenSource.token, ); expect(variantAnalysisManager.downloadsQueueSize()).toBe(0); diff --git a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-monitor.test.ts b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-monitor.test.ts index 0c9082c91..329e85685 100644 --- a/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-monitor.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/activated-extension/variant-analysis/variant-analysis-monitor.test.ts @@ -1,4 +1,4 @@ -import { CancellationTokenSource, commands, extensions } from "vscode"; +import { commands, extensions } from "vscode"; import { CodeQLExtensionInterface } from "../../../../src/extension"; import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client"; @@ -33,7 +33,6 @@ describe("Variant Analysis Monitor", () => { let mockGetVariantAnalysis: jest.SpiedFunction< typeof ghApiClient.getVariantAnalysis >; - let cancellationTokenSource: CancellationTokenSource; let variantAnalysisMonitor: VariantAnalysisMonitor; let shouldCancelMonitor: jest.Mock, [number]>; let variantAnalysis: VariantAnalysis; @@ -45,8 +44,6 @@ describe("Variant Analysis Monitor", () => { const onVariantAnalysisChangeSpy = jest.fn(); beforeEach(async () => { - cancellationTokenSource = new CancellationTokenSource(); - variantAnalysis = createMockVariantAnalysis({}); shouldCancelMonitor = jest.fn(); @@ -71,25 +68,12 @@ describe("Variant Analysis Monitor", () => { limitNumberOfAttemptsToMonitor(); }); - it("should return early if variant analysis is cancelled", async () => { - cancellationTokenSource.cancel(); - - await variantAnalysisMonitor.monitorVariantAnalysis( - variantAnalysis, - testCredentialsWithStub(), - cancellationTokenSource.token, - ); - - expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled(); - }); - it("should return early if variant analysis should be cancelled", async () => { shouldCancelMonitor.mockResolvedValue(true); await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, testCredentialsWithStub(), - cancellationTokenSource.token, ); expect(onVariantAnalysisChangeSpy).not.toHaveBeenCalled(); @@ -107,7 +91,6 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, testCredentialsWithStub(), - cancellationTokenSource.token, ); expect(mockGetVariantAnalysis).toHaveBeenCalledTimes(1); @@ -157,7 +140,6 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, testCredentialsWithStub(), - cancellationTokenSource.token, ); expect(commandSpy).toBeCalledTimes(succeededRepos.length); @@ -176,7 +158,6 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, testCredentialsWithStub(), - cancellationTokenSource.token, ); expect(mockGetDownloadResult).toBeCalledTimes(succeededRepos.length); @@ -186,7 +167,6 @@ describe("Variant Analysis Monitor", () => { index + 1, processScannedRepository(succeededRepo), processUpdatedVariantAnalysis(variantAnalysis, mockApiResponse), - undefined, ); }); }); @@ -209,7 +189,6 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, testCredentialsWithStub(), - cancellationTokenSource.token, ); expect(commandSpy).not.toHaveBeenCalled(); @@ -219,7 +198,6 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, testCredentialsWithStub(), - cancellationTokenSource.token, ); expect(mockGetDownloadResult).not.toBeCalled(); @@ -278,7 +256,6 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, testCredentialsWithStub(), - cancellationTokenSource.token, ); expect(mockGetVariantAnalysis).toBeCalledTimes(4); @@ -297,7 +274,6 @@ describe("Variant Analysis Monitor", () => { await variantAnalysisMonitor.monitorVariantAnalysis( variantAnalysis, testCredentialsWithStub(), - cancellationTokenSource.token, ); expect(mockGetDownloadResult).not.toBeCalled(); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/data-extension-pack/extension-file.yml b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-extension-pack/extension-file.yml new file mode 100644 index 000000000..e2c26ddff --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-extension-pack/extension-file.yml @@ -0,0 +1,12 @@ +extensions: + - addsTo: + pack: codeql/javascript-all + extensible: sourceModel + data: + - [ "@example/read-write-user-data", "Member[readUserData].ReturnValue.Awaited", "remote" ] + + - addsTo: + pack: codeql/javascript-all + extensible: sinkModel + data: + - [ "@example/read-write-user-data", "Member[writeUserData].Argument[0]", "command-line-injection" ] diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/data-extension-pack/qlpack.yml b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-extension-pack/qlpack.yml new file mode 100644 index 000000000..b834c739b --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/data-extension-pack/qlpack.yml @@ -0,0 +1,8 @@ +name: github/extension-pack-for-testing +version: 0.0.0 +library: true +extensionTargets: + github/remote-query-pack: "*" + +dataExtensions: + - extension-file.yml diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts index 407c22176..3ba6d5806 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts @@ -9,6 +9,7 @@ import { promptImportInternetDatabase, } from "../../../src/databaseFetcher"; import { cleanDatabases, dbLoc, DB_URL, storagePath } from "../global.helper"; +import { createMockCommandManager } from "../../__mocks__/commandsMock"; jest.setTimeout(60_000); @@ -53,6 +54,7 @@ describe("DatabaseFetcher", () => { it("should add a database from a folder", async () => { const uri = Uri.file(dbLoc); let dbItem = await importArchiveDatabase( + createMockCommandManager(), uri.toString(true), databaseManager, storagePath, @@ -75,6 +77,7 @@ describe("DatabaseFetcher", () => { inputBoxStub.mockResolvedValue(DB_URL); let dbItem = await promptImportInternetDatabase( + createMockCommandManager(), databaseManager, storagePath, progressCallback, diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts index 6c014b609..faacb6db2 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts @@ -12,6 +12,7 @@ import { CodeQLExtensionInterface } from "../../../src/extension"; import { describeWithCodeQL } from "../cli"; import { QueryServerClient } from "../../../src/legacy-query-server/queryserver-client"; import { extLogger, ProgressReporter } from "../../../src/common"; +import { createMockApp } from "../../__mocks__/appMock"; const baseDir = join(__dirname, "../../../test/data"); @@ -121,6 +122,7 @@ describeWithCodeQL()("using the legacy query server", () => { cliServer.quiet = true; qs = new QueryServerClient( + createMockApp({}), { codeQlPath: (await extension.distributionManager.getCodeQlPathWithoutVersionCheck()) || diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts index a7d8f9d4b..4d47f3b02 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts @@ -13,6 +13,7 @@ import { extLogger, ProgressReporter } from "../../../src/common"; import { QueryResultType } from "../../../src/pure/new-messages"; import { cleanDatabases, dbLoc, storagePath } from "../global.helper"; import { importArchiveDatabase } from "../../../src/databaseFetcher"; +import { createMockCommandManager } from "../../__mocks__/commandsMock"; const baseDir = join(__dirname, "../../../test/data"); @@ -147,6 +148,7 @@ describeWithCodeQL()("using the new query server", () => { await cleanDatabases(extension.databaseManager); const uri = Uri.file(dbLoc); const maybeDbItem = await importArchiveDatabase( + createMockCommandManager(), uri.toString(true), extension.databaseManager, storagePath, diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts index 65b304235..b750bbae9 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts @@ -26,6 +26,7 @@ import { createInitialQueryInfo } from "../../../src/run-queries-shared"; import { QueryRunner } from "../../../src/queryRunner"; import { CompletedQueryInfo } from "../../../src/query-results"; import { SELECT_QUERY_NAME } from "../../../src/contextual/locationFinder"; +import { createMockCommandManager } from "../../__mocks__/commandsMock"; jest.setTimeout(20_000); @@ -78,6 +79,7 @@ describeWithCodeQL()("Queries", () => { await cleanDatabases(databaseManager); const uri = Uri.file(dbLoc); const maybeDbItem = await importArchiveDatabase( + createMockCommandManager(), uri.toString(true), databaseManager, storagePath, 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 e0a81a209..dd02364a4 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 @@ -12,7 +12,7 @@ import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-cli import { join } from "path"; import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant-analysis-manager"; -import { CodeQLCliServer } from "../../../../src/cli"; +import { CliVersionConstraint, CodeQLCliServer } from "../../../../src/cli"; import { fixWorkspaceReferences, restoreWorkspaceReferences, @@ -25,7 +25,7 @@ import { } from "../../../../src/variant-analysis/shared/variant-analysis"; import { VariantAnalysis as VariantAnalysisApiResponse } from "../../../../src/variant-analysis/gh-api/variant-analysis"; import { createMockApiResponse } from "../../../factories/variant-analysis/gh-api/variant-analysis-api-response"; -import { UserCancellationException } from "../../../../src/commandRunner"; +import { UserCancellationException } from "../../../../src/progress"; import { Repository } from "../../../../src/variant-analysis/gh-api/repository"; import { DbManager } from "../../../../src/databases/db-manager"; import { ExtensionApp } from "../../../../src/common/vscode/vscode-app"; @@ -255,6 +255,30 @@ describe("Variant Analysis Manager", () => { qlxFilesThatExist: ["subfolder/in-pack.qlx"], }); }); + + it("should run a remote query with extension packs inside a qlpack", async () => { + if (!(await cli.cliConstraints.supportsQlpacksKind())) { + console.log( + `Skipping test because qlpacks kind is only suppported in CLI version ${CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND} or later.`, + ); + return; + } + await cli.setUseExtensionPacks(true); + await doVariantAnalysisTest({ + queryPath: "data-remote-qlpack-nested/subfolder/in-pack.ql", + filesThatExist: [ + "subfolder/in-pack.ql", + "otherfolder/lib.qll", + ".codeql/libraries/semmle/targets-extension/0.0.0/ext/extension.yml", + ], + filesThatDoNotExist: ["subfolder/not-in-pack.ql"], + qlxFilesThatExist: ["subfolder/in-pack.qlx"], + dependenciesToCheck: [ + "codeql/javascript-all", + "semmle/targets-extension", + ], + }); + }); }); async function doVariantAnalysisTest({ @@ -262,11 +286,13 @@ describe("Variant Analysis Manager", () => { filesThatExist, qlxFilesThatExist, filesThatDoNotExist, + dependenciesToCheck = ["codeql/javascript-all"], }: { queryPath: string; filesThatExist: string[]; qlxFilesThatExist: string[]; filesThatDoNotExist: string[]; + dependenciesToCheck?: string[]; }) { const fileUri = getFile(queryPath); await variantAnalysisManager.runVariantAnalysis( @@ -328,8 +354,10 @@ describe("Variant Analysis Manager", () => { const actualLockKeys = Object.keys(qlpackLockContents.dependencies); - // The lock file should contain at least codeql/javascript-all. - expect(actualLockKeys).toContain("codeql/javascript-all"); + // The lock file should contain at least the specified dependencies. + dependenciesToCheck.forEach((dep) => + expect(actualLockKeys).toContain(dep), + ); } function getFile(file: string): Uri { diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts index 61c68b9c2..c106110e8 100644 --- a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts @@ -13,7 +13,7 @@ import { FullDatabaseOptions, } from "../../../src/local-databases"; import { Logger } from "../../../src/common"; -import { ProgressCallback } from "../../../src/commandRunner"; +import { ProgressCallback } from "../../../src/progress"; import { CodeQLCliServer, DbInfo } from "../../../src/cli"; import { encodeArchiveBasePath, @@ -25,6 +25,7 @@ import * as helpers from "../../../src/helpers"; import { Setting } from "../../../src/config"; import { QlPackGenerator } from "../../../src/qlpack-generator"; import { mockedObject } from "../utils/mocking.helpers"; +import { createMockApp } from "../../__mocks__/appMock"; describe("local databases", () => { const MOCK_DB_OPTIONS: FullDatabaseOptions = { @@ -87,6 +88,7 @@ describe("local databases", () => { databaseManager = new DatabaseManager( extensionContext, + createMockApp({}), mockedObject({ registerDatabase: registerSpy, deregisterDatabase: deregisterSpy, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts index cf2a022c2..49bb5117d 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts @@ -15,7 +15,14 @@ import { import { dump } from "js-yaml"; import * as tmp from "tmp"; import { join } from "path"; -import { writeFileSync, mkdirSync, ensureDirSync, symlinkSync } from "fs-extra"; +import { + writeFileSync, + mkdirSync, + ensureDirSync, + symlinkSync, + writeFile, + mkdir, +} from "fs-extra"; import { DirResult } from "tmp"; import { @@ -24,13 +31,16 @@ import { isFolderAlreadyInWorkspace, isLikelyDatabaseRoot, isLikelyDbLanguageFolder, + prepareCodeTour, showBinaryChoiceDialog, showBinaryChoiceWithUrlDialog, showInformationMessageWithAction, walkDirectory, } from "../../../src/helpers"; -import { reportStreamProgress } from "../../../src/commandRunner"; +import { reportStreamProgress } from "../../../src/progress"; import { QueryLanguage } from "../../../src/common/query-language"; +import { Setting } from "../../../src/config"; +import { createMockCommandManager } from "../../__mocks__/commandsMock"; describe("helpers", () => { describe("Invocation rate limiter", () => { @@ -559,3 +569,105 @@ describe("isFolderAlreadyInWorkspace", () => { expect(isFolderAlreadyInWorkspace("/third/path")).toBe(false); }); }); + +describe("prepareCodeTour", () => { + let dir: tmp.DirResult; + let showInformationMessageSpy: jest.SpiedFunction< + typeof window.showInformationMessage + >; + + beforeEach(() => { + dir = tmp.dirSync(); + + const mockWorkspaceFolders = [ + { + uri: Uri.file(dir.name), + name: "test", + index: 0, + }, + ] as WorkspaceFolder[]; + + jest + .spyOn(workspace, "workspaceFolders", "get") + .mockReturnValue(mockWorkspaceFolders); + + showInformationMessageSpy = jest + .spyOn(window, "showInformationMessage") + .mockResolvedValue({ title: "Yes" }); + }); + + afterEach(() => { + dir.removeCallback(); + }); + + describe("if we're in the tour repo", () => { + describe("if the workspace is not already open", () => { + it("should open the tutorial workspace", async () => { + // set up directory to have a 'tutorial.code-workspace' file + const tutorialWorkspacePath = join(dir.name, "tutorial.code-workspace"); + await writeFile(tutorialWorkspacePath, "{}"); + + // set up a .tours directory to indicate we're in the tour codespace + const tourDirPath = join(dir.name, ".tours"); + await mkdir(tourDirPath); + + // spy that we open the workspace file by calling the 'vscode.openFolder' command + const executeCommand = jest.fn(); + await prepareCodeTour(createMockCommandManager({ executeCommand })); + + expect(showInformationMessageSpy).toHaveBeenCalled(); + expect(executeCommand).toHaveBeenCalledWith( + "vscode.openFolder", + expect.objectContaining({ + path: Uri.parse(tutorialWorkspacePath).fsPath, + }), + ); + }); + }); + + describe("if the workspace is already open", () => { + it("should not open the tutorial workspace", async () => { + // Set isCodespaceTemplate to true to indicate the workspace has already been opened + jest.spyOn(Setting.prototype, "getValue").mockReturnValue(true); + + // set up directory to have a 'tutorial.code-workspace' file + const tutorialWorkspacePath = join(dir.name, "tutorial.code-workspace"); + await writeFile(tutorialWorkspacePath, "{}"); + + // set up a .tours directory to indicate we're in the tour codespace + const tourDirPath = join(dir.name, ".tours"); + await mkdir(tourDirPath); + + // spy that we open the workspace file by calling the 'vscode.openFolder' command + const executeCommand = jest.fn(); + await prepareCodeTour(createMockCommandManager({ executeCommand })); + + expect(executeCommand).not.toHaveBeenCalled(); + }); + }); + }); + + describe("if we're in a different tour repo", () => { + it("should not open the tutorial workspace", async () => { + // set up a .tours directory + const tourDirPath = join(dir.name, ".tours"); + await mkdir(tourDirPath); + + // spy that we open the workspace file by calling the 'vscode.openFolder' command + const executeCommand = jest.fn(); + await prepareCodeTour(createMockCommandManager({ executeCommand })); + + expect(executeCommand).not.toHaveBeenCalled(); + }); + }); + + describe("if we're in a different repo with no tour", () => { + it("should not open the tutorial workspace", async () => { + // spy that we open the workspace file by calling the 'vscode.openFolder' command + const executeCommand = jest.fn(); + await prepareCodeTour(createMockCommandManager({ executeCommand })); + + expect(executeCommand).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/history-tree-data-provider.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/history-tree-data-provider.test.ts index b63448a40..ca0b0add8 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/history-tree-data-provider.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/history-tree-data-provider.test.ts @@ -27,6 +27,7 @@ import { } from "../../../../src/query-history/history-tree-data-provider"; import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager"; import { createMockQueryHistoryDirs } from "../../../factories/query-history/query-history-dirs"; +import { createMockApp } from "../../../__mocks__/appMock"; describe("HistoryTreeDataProvider", () => { const mockExtensionLocation = join(tmpDir.name, "mock-extension-location"); @@ -421,6 +422,7 @@ describe("HistoryTreeDataProvider", () => { async function createMockQueryHistory(allHistory: QueryHistoryInfo[]) { const qhm = new QueryHistoryManager( + createMockApp({}), {} as QueryRunner, {} as DatabaseManager, localQueriesResultsViewStub, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/query-history-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/query-history-manager.test.ts index 07dfbe51d..2ca728c6b 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/query-history-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/query-history-manager.test.ts @@ -22,56 +22,49 @@ import { createMockVariantAnalysisHistoryItem } from "../../../factories/query-h import { VariantAnalysisHistoryItem } from "../../../../src/query-history/variant-analysis-history-item"; import { QueryStatus } from "../../../../src/query-status"; import { VariantAnalysisStatus } from "../../../../src/variant-analysis/shared/variant-analysis"; -import { TextEditor } from "vscode"; import { WebviewReveal } from "../../../../src/interface-utils"; import * as helpers from "../../../../src/helpers"; -import { mockedObject, mockedQuickPickItem } from "../../utils/mocking.helpers"; +import { mockedQuickPickItem } from "../../utils/mocking.helpers"; import { createMockQueryHistoryDirs } from "../../../factories/query-history/query-history-dirs"; +import { createMockApp } from "../../../__mocks__/appMock"; +import { App } from "../../../../src/common/app"; +import { createMockCommandManager } from "../../../__mocks__/commandsMock"; describe("QueryHistoryManager", () => { const mockExtensionLocation = join(tmpDir.name, "mock-extension-location"); let configListener: QueryHistoryConfigListener; - let showTextDocumentSpy: jest.SpiedFunction< - typeof vscode.window.showTextDocument - >; - let showInformationMessageSpy: jest.SpiedFunction< - typeof vscode.window.showInformationMessage - >; let showQuickPickSpy: jest.SpiedFunction; - let executeCommandSpy: jest.SpiedFunction< - typeof vscode.commands.executeCommand + let cancelVariantAnalysisSpy: jest.SpiedFunction< + typeof variantAnalysisManagerStub.cancelVariantAnalysis >; const doCompareCallback = jest.fn(); + let executeCommand: jest.MockedFn< + (commandName: string, ...args: any[]) => Promise + >; + let mockApp: App; + let queryHistoryManager: QueryHistoryManager; let localQueriesResultsViewStub: ResultsView; let variantAnalysisManagerStub: VariantAnalysisManager; - let tryOpenExternalFile: Function; - let allHistory: QueryHistoryInfo[]; let localQueryHistory: LocalQueryInfo[]; let variantAnalysisHistory: VariantAnalysisHistoryItem[]; beforeEach(() => { - showTextDocumentSpy = jest - .spyOn(vscode.window, "showTextDocument") - .mockResolvedValue(mockedObject({})); - showInformationMessageSpy = jest - .spyOn(vscode.window, "showInformationMessage") - .mockResolvedValue(undefined); showQuickPickSpy = jest .spyOn(vscode.window, "showQuickPick") .mockResolvedValue(undefined); - executeCommandSpy = jest - .spyOn(vscode.commands, "executeCommand") - .mockResolvedValue(undefined); + + executeCommand = jest.fn(); + mockApp = createMockApp({ + commands: createMockCommandManager({ executeCommand }), + }); jest.spyOn(extLogger, "log").mockResolvedValue(undefined); - tryOpenExternalFile = (QueryHistoryManager.prototype as any) - .tryOpenExternalFile; configListener = new QueryHistoryConfigListener(); localQueriesResultsViewStub = { showResults: jest.fn(), @@ -82,9 +75,15 @@ describe("QueryHistoryManager", () => { onVariantAnalysisStatusUpdated: jest.fn(), onVariantAnalysisRemoved: jest.fn(), removeVariantAnalysis: jest.fn(), + cancelVariantAnalysis: jest.fn(), + exportResults: jest.fn(), showView: jest.fn(), } as any as VariantAnalysisManager; + cancelVariantAnalysisSpy = jest + .spyOn(variantAnalysisManagerStub, "cancelVariantAnalysis") + .mockResolvedValue(undefined); + localQueryHistory = [ // completed createMockLocalQueryInfo({ @@ -148,51 +147,6 @@ describe("QueryHistoryManager", () => { queryHistoryManager.dispose(); } }); - describe("tryOpenExternalFile", () => { - it("should open an external file", async () => { - await tryOpenExternalFile("xxx"); - expect(showTextDocumentSpy).toHaveBeenCalledTimes(1); - expect(showTextDocumentSpy).toHaveBeenCalledWith( - vscode.Uri.file("xxx"), - expect.anything(), - ); - expect(executeCommandSpy).not.toBeCalled(); - }); - - [ - "too large to open", - "Files above 50MB cannot be synchronized with extensions", - ].forEach((msg) => { - it(`should fail to open a file because "${msg}" and open externally`, async () => { - showTextDocumentSpy.mockRejectedValue(new Error(msg)); - showInformationMessageSpy.mockResolvedValue({ title: "Yes" }); - - await tryOpenExternalFile("xxx"); - const uri = vscode.Uri.file("xxx"); - expect(showTextDocumentSpy).toHaveBeenCalledTimes(1); - expect(showTextDocumentSpy).toHaveBeenCalledWith( - uri, - expect.anything(), - ); - expect(executeCommandSpy).toHaveBeenCalledWith("revealFileInOS", uri); - }); - - it(`should fail to open a file because "${msg}" and NOT open externally`, async () => { - showTextDocumentSpy.mockRejectedValue(new Error(msg)); - showInformationMessageSpy.mockResolvedValue({ title: "No" }); - - await tryOpenExternalFile("xxx"); - const uri = vscode.Uri.file("xxx"); - expect(showTextDocumentSpy).toHaveBeenCalledTimes(1); - expect(showTextDocumentSpy).toHaveBeenCalledWith( - uri, - expect.anything(), - ); - expect(showInformationMessageSpy).toBeCalled(); - expect(executeCommandSpy).not.toBeCalled(); - }); - }); - }); describe("handleItemClicked", () => { describe("single click", () => { @@ -344,7 +298,6 @@ describe("QueryHistoryManager", () => { }); it("should remove the item", () => { - expect(toDelete.completedQuery!.dispose).toBeCalledTimes(1); expect(queryHistoryManager.treeDataProvider.allHistory).toEqual( expect.not.arrayContaining([toDelete]), ); @@ -387,7 +340,6 @@ describe("QueryHistoryManager", () => { }); it("should remove the item", () => { - expect(toDelete.completedQuery!.dispose).toBeCalledTimes(1); expect(queryHistoryManager.treeDataProvider.allHistory).toEqual( expect.not.arrayContaining([toDelete]), ); @@ -731,8 +683,7 @@ describe("QueryHistoryManager", () => { const inProgress1 = variantAnalysisHistory[1]; await queryHistoryManager.handleCancel(inProgress1, [inProgress1]); - expect(executeCommandSpy).toBeCalledWith( - "codeQL.cancelVariantAnalysis", + expect(cancelVariantAnalysisSpy).toBeCalledWith( inProgress1.variantAnalysis.id, ); }); @@ -748,12 +699,10 @@ describe("QueryHistoryManager", () => { inProgress1, inProgress2, ]); - expect(executeCommandSpy).toBeCalledWith( - "codeQL.cancelVariantAnalysis", + expect(cancelVariantAnalysisSpy).toBeCalledWith( inProgress1.variantAnalysis.id, ); - expect(executeCommandSpy).toBeCalledWith( - "codeQL.cancelVariantAnalysis", + expect(cancelVariantAnalysisSpy).toBeCalledWith( inProgress2.variantAnalysis.id, ); }); @@ -795,8 +744,7 @@ describe("QueryHistoryManager", () => { await queryHistoryManager.handleCancel(completedVariantAnalysis, [ completedVariantAnalysis, ]); - expect(executeCommandSpy).not.toBeCalledWith( - "codeQL.cancelVariantAnalysis", + expect(cancelVariantAnalysisSpy).not.toBeCalledWith( completedVariantAnalysis.variantAnalysis, ); }); @@ -812,12 +760,10 @@ describe("QueryHistoryManager", () => { completedVariantAnalysis, failedVariantAnalysis, ]); - expect(executeCommandSpy).not.toBeCalledWith( - "codeQL.cancelVariantAnalysis", + expect(cancelVariantAnalysisSpy).not.toBeCalledWith( completedVariantAnalysis.variantAnalysis.id, ); - expect(executeCommandSpy).not.toBeCalledWith( - "codeQL.cancelVariantAnalysis", + expect(cancelVariantAnalysisSpy).not.toBeCalledWith( failedVariantAnalysis.variantAnalysis.id, ); }); @@ -831,7 +777,7 @@ describe("QueryHistoryManager", () => { const item = localQueryHistory[4]; await queryHistoryManager.handleCopyRepoList(item, [item]); - expect(executeCommandSpy).not.toBeCalled(); + expect(executeCommand).not.toBeCalled(); }); it("should copy repo list for a single variant analysis", async () => { @@ -839,7 +785,7 @@ describe("QueryHistoryManager", () => { const item = variantAnalysisHistory[1]; await queryHistoryManager.handleCopyRepoList(item, [item]); - expect(executeCommandSpy).toBeCalledWith( + expect(executeCommand).toBeCalledWith( "codeQL.copyVariantAnalysisRepoList", item.variantAnalysis.id, ); @@ -851,7 +797,7 @@ describe("QueryHistoryManager", () => { const item1 = variantAnalysisHistory[1]; const item2 = variantAnalysisHistory[3]; await queryHistoryManager.handleCopyRepoList(item1, [item1, item2]); - expect(executeCommandSpy).not.toBeCalled(); + expect(executeCommand).not.toBeCalled(); }); }); @@ -862,7 +808,7 @@ describe("QueryHistoryManager", () => { const item = localQueryHistory[4]; await queryHistoryManager.handleExportResults(item, [item]); - expect(executeCommandSpy).not.toBeCalled(); + expect(variantAnalysisManagerStub.exportResults).not.toBeCalled(); }); it("should export results for a single variant analysis", async () => { @@ -870,8 +816,7 @@ describe("QueryHistoryManager", () => { const item = variantAnalysisHistory[1]; await queryHistoryManager.handleExportResults(item, [item]); - expect(executeCommandSpy).toBeCalledWith( - "codeQL.exportVariantAnalysisResults", + expect(variantAnalysisManagerStub.exportResults).toBeCalledWith( item.variantAnalysis.id, ); }); @@ -882,7 +827,7 @@ describe("QueryHistoryManager", () => { const item1 = variantAnalysisHistory[1]; const item2 = variantAnalysisHistory[3]; await queryHistoryManager.handleExportResults(item1, [item1, item2]); - expect(executeCommandSpy).not.toBeCalled(); + expect(variantAnalysisManagerStub.exportResults).not.toBeCalled(); }); }); @@ -1149,6 +1094,7 @@ describe("QueryHistoryManager", () => { async function createMockQueryHistory(allHistory: QueryHistoryInfo[]) { const qhm = new QueryHistoryManager( + mockApp, {} as QueryRunner, {} as DatabaseManager, localQueriesResultsViewStub, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-serialization.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts similarity index 89% rename from extensions/ql-vscode/test/vscode-tests/no-workspace/query-serialization.test.ts rename to extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts index 5b22b9aef..9adb3d550 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-serialization.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/store/query-history-store.test.ts @@ -1,19 +1,22 @@ import { deserializeQueryHistory, serializeQueryHistory, -} from "../../../src/query-serialization"; +} from "../../../../../src/query-history/store/query-history-store"; import { join } from "path"; import { writeFileSync, mkdirpSync, writeFile } from "fs-extra"; -import { LocalQueryInfo, InitialQueryInfo } from "../../../src/query-results"; -import { QueryWithResults } from "../../../src/run-queries-shared"; -import { DatabaseInfo } from "../../../src/pure/interface-types"; +import { + LocalQueryInfo, + InitialQueryInfo, +} from "../../../../../src/query-results"; +import { QueryWithResults } from "../../../../../src/run-queries-shared"; +import { DatabaseInfo } from "../../../../../src/pure/interface-types"; import { CancellationTokenSource, Uri } from "vscode"; -import { tmpDir } from "../../../src/helpers"; -import { QueryResultType } from "../../../src/pure/legacy-messages"; -import { QueryInProgress } from "../../../src/legacy-query-server/run-queries"; -import { VariantAnalysisHistoryItem } from "../../../src/query-history/variant-analysis-history-item"; -import { QueryHistoryInfo } from "../../../src/query-history/query-history-info"; -import { createMockVariantAnalysisHistoryItem } from "../../factories/query-history/variant-analysis-history-item"; +import { tmpDir } from "../../../../../src/helpers"; +import { QueryResultType } from "../../../../../src/pure/legacy-messages"; +import { QueryInProgress } from "../../../../../src/legacy-query-server/run-queries"; +import { VariantAnalysisHistoryItem } from "../../../../../src/query-history/variant-analysis-history-item"; +import { QueryHistoryInfo } from "../../../../../src/query-history/query-history-info"; +import { createMockVariantAnalysisHistoryItem } from "../../../../factories/query-history/variant-analysis-history-item"; import { nanoid } from "nanoid"; describe("serialize and deserialize", () => { @@ -259,7 +262,6 @@ describe("serialize and deserialize", () => { query: query.queryEvalInfo, successful: didRunSuccessfully, message: "foo", - dispose: jest.fn(), result: { evaluationTime: 1, queryId: 0, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/variant-analysis-history.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/variant-analysis-history.test.ts index 6441d2fea..f6115af31 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/variant-analysis-history.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/variant-analysis-history.test.ts @@ -8,7 +8,7 @@ import { } from "fs-extra"; import { join } from "path"; -import { commands, ExtensionContext, Uri } from "vscode"; +import { ExtensionContext, Uri } from "vscode"; import { DatabaseManager } from "../../../../src/local-databases"; import { tmpDir, walkDirectory } from "../../../../src/helpers"; import { DisposableBucket } from "../../disposable-bucket"; @@ -21,6 +21,7 @@ import { VariantAnalysisManager } from "../../../../src/variant-analysis/variant import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager"; import { mockedObject } from "../../utils/mocking.helpers"; import { createMockQueryHistoryDirs } from "../../../factories/query-history/query-history-dirs"; +import { createMockApp } from "../../../__mocks__/appMock"; // set a higher timeout since recursive delete may take a while, expecially on Windows. jest.setTimeout(120000); @@ -54,9 +55,12 @@ describe("Variant Analyses and QueryHistoryManager", () => { rehydrateVariantAnalysis: rehydrateVariantAnalysisStub, onVariantAnalysisStatusUpdated: jest.fn(), showView: showViewStub, + openQueryText: jest.fn(), } as any as VariantAnalysisManager; - let executeCommandSpy: jest.SpiedFunction; + let openQueryTextSpy: jest.SpiedFunction< + typeof variantAnalysisManagerStub.openQueryText + >; beforeEach(async () => { // Since these tests change the state of the query history manager, we need to copy the original @@ -70,6 +74,7 @@ describe("Variant Analyses and QueryHistoryManager", () => { ).queries; qhm = new QueryHistoryManager( + createMockApp({}), {} as QueryRunner, {} as DatabaseManager, localQueriesResultsViewStub, @@ -95,8 +100,8 @@ describe("Variant Analyses and QueryHistoryManager", () => { ); disposables.push(qhm); - executeCommandSpy = jest - .spyOn(commands, "executeCommand") + openQueryTextSpy = jest + .spyOn(variantAnalysisManagerStub, "openQueryText") .mockResolvedValue(undefined); }); @@ -180,8 +185,7 @@ describe("Variant Analyses and QueryHistoryManager", () => { await qhm.readQueryHistory(); await qhm.handleShowQueryText(qhm.treeDataProvider.allHistory[0], []); - expect(executeCommandSpy).toHaveBeenCalledWith( - "codeQL.openVariantAnalysisQueryText", + expect(openQueryTextSpy).toHaveBeenCalledWith( rawQueryHistory[0].variantAnalysis.id, ); }); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/vscode-utils/external-files.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/vscode-utils/external-files.test.ts new file mode 100644 index 000000000..b87ee3fe6 --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-history/vscode-utils/external-files.test.ts @@ -0,0 +1,69 @@ +import * as vscode from "vscode"; +import { tryOpenExternalFile } from "../../../../../src/vscode-utils/external-files"; +import { createMockCommandManager } from "../../../../__mocks__/commandsMock"; +import { mockedObject } from "../../../utils/mocking.helpers"; + +describe("tryOpenExternalFile", () => { + let showTextDocumentSpy: jest.SpiedFunction< + typeof vscode.window.showTextDocument + >; + let showInformationMessageSpy: jest.SpiedFunction< + typeof vscode.window.showInformationMessage + >; + + beforeEach(() => { + showTextDocumentSpy = jest + .spyOn(vscode.window, "showTextDocument") + .mockResolvedValue(mockedObject({})); + showInformationMessageSpy = jest + .spyOn(vscode.window, "showInformationMessage") + .mockResolvedValue(undefined); + }); + + it("should open an external file", async () => { + const executeCommand = jest.fn(); + const commandManager = createMockCommandManager({ executeCommand }); + + await tryOpenExternalFile(commandManager, "xxx"); + expect(showTextDocumentSpy).toHaveBeenCalledTimes(1); + expect(showTextDocumentSpy).toHaveBeenCalledWith( + vscode.Uri.file("xxx"), + expect.anything(), + ); + expect(executeCommand).not.toBeCalled(); + }); + + [ + "too large to open", + "Files above 50MB cannot be synchronized with extensions", + ].forEach((msg) => { + it(`should fail to open a file because "${msg}" and open externally`, async () => { + const executeCommand = jest.fn(); + const commandManager = createMockCommandManager({ executeCommand }); + + showTextDocumentSpy.mockRejectedValue(new Error(msg)); + showInformationMessageSpy.mockResolvedValue({ title: "Yes" }); + + await tryOpenExternalFile(commandManager, "xxx"); + const uri = vscode.Uri.file("xxx"); + expect(showTextDocumentSpy).toHaveBeenCalledTimes(1); + expect(showTextDocumentSpy).toHaveBeenCalledWith(uri, expect.anything()); + expect(executeCommand).toHaveBeenCalledWith("revealFileInOS", uri); + }); + + it(`should fail to open a file because "${msg}" and NOT open externally`, async () => { + const executeCommand = jest.fn(); + const commandManager = createMockCommandManager({ executeCommand }); + + showTextDocumentSpy.mockRejectedValue(new Error(msg)); + showInformationMessageSpy.mockResolvedValue({ title: "No" }); + + await tryOpenExternalFile(commandManager, "xxx"); + const uri = vscode.Uri.file("xxx"); + expect(showTextDocumentSpy).toHaveBeenCalledTimes(1); + expect(showTextDocumentSpy).toHaveBeenCalledWith(uri, expect.anything()); + expect(showInformationMessageSpy).toBeCalled(); + expect(executeCommand).not.toBeCalled(); + }); + }); +}); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-results.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-results.test.ts index 14992fee2..e32078ccc 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/query-results.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/query-results.test.ts @@ -470,7 +470,6 @@ describe("query-results", () => { query: query.queryEvalInfo, successful: didRunSuccessfully, message: "foo", - dispose: jest.fn(), result: { evaluationTime: 1, queryId: 0, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts index f7f74c5e3..d576b1004 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/run-queries.test.ts @@ -190,6 +190,7 @@ describe("run-queries", () => { mockQlProgram, mockProgress as any, mockCancel as any, + qs.logger, ); expect(results).toEqual([{ message: "err", severity: Severity.ERROR }]); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/telemetry.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/telemetry.test.ts index 4517837cf..47738ba85 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/telemetry.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/telemetry.test.ts @@ -9,7 +9,7 @@ import { TelemetryListener, telemetryListener as globalTelemetryListener, } from "../../../src/telemetry"; -import { UserCancellationException } from "../../../src/commandRunner"; +import { UserCancellationException } from "../../../src/progress"; import { ENABLE_TELEMETRY } from "../../../src/config"; import { createMockExtensionContext } from "./index"; import { vscodeGetConfigurationMock } from "../test-config";