Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1572c095ca | ||
|
|
83df05e731 | ||
|
|
b43b1d4c8e | ||
|
|
79ff9e0378 | ||
|
|
d7a82cc686 | ||
|
|
39ad5b28c7 | ||
|
|
68c159f1fe | ||
|
|
355a6fc552 | ||
|
|
6f54bebe35 | ||
|
|
bea108a412 | ||
|
|
088d2fa91e | ||
|
|
6c92a5b800 | ||
|
|
db0d062443 | ||
|
|
673ea13af8 | ||
|
|
2208b42485 | ||
|
|
376d6b75d6 | ||
|
|
d8a7dcd7d2 | ||
|
|
580257f024 | ||
|
|
233cb1cd41 | ||
|
|
d9fcfa0374 | ||
|
|
1dc48b3309 | ||
|
|
2babb0b106 | ||
|
|
247df2e1f9 | ||
|
|
ba9007e426 | ||
|
|
616b613fbd | ||
|
|
47ae0fe9f5 | ||
|
|
9d6b4e8e5a | ||
|
|
5ae70696a8 | ||
|
|
712dd525ee | ||
|
|
fdc5645928 | ||
|
|
5c81c0e9bf | ||
|
|
0e07e5e3eb | ||
|
|
c90acdb4e1 | ||
|
|
dad37f202d | ||
|
|
1f937ad208 | ||
|
|
086d8e2104 | ||
|
|
c2ddd680c1 | ||
|
|
9a14896a4e | ||
|
|
871fc0b817 | ||
|
|
ca092a1623 | ||
|
|
529bbe3ffa | ||
|
|
31b2d24ca9 | ||
|
|
f54cace05b | ||
|
|
f773f9f675 | ||
|
|
67aa216ea1 | ||
|
|
4b67f4dd54 | ||
|
|
572a8b7828 | ||
|
|
b7d6aeda3c | ||
|
|
2da77acd9e | ||
|
|
607a8380cc | ||
|
|
f698ac321f | ||
|
|
85abd9a1ac | ||
|
|
329eba9536 | ||
|
|
733a0328d0 | ||
|
|
1c7f92b503 | ||
|
|
884ec21e5a | ||
|
|
35c533606f | ||
|
|
4adf60a978 | ||
|
|
69775414f0 | ||
|
|
10c74d5d8c | ||
|
|
325f5d6667 | ||
|
|
e968c226ba | ||
|
|
44429c2513 | ||
|
|
96881f2bf2 | ||
|
|
06f4e33719 | ||
|
|
570e410e06 | ||
|
|
1d5b4f3fbb | ||
|
|
0e5b460e67 | ||
|
|
a39f4582e3 | ||
|
|
18f12e53c9 | ||
|
|
ac8dd9189e | ||
|
|
7d564b50e7 | ||
|
|
013bf65c0a | ||
|
|
bd6862efda | ||
|
|
64f0115070 | ||
|
|
596049a8cb | ||
|
|
58a5a43609 | ||
|
|
67ad9cb6ce | ||
|
|
29c36584f8 | ||
|
|
e59145372e | ||
|
|
ad17a4f828 | ||
|
|
f17319a5cf | ||
|
|
0d52be1dbf | ||
|
|
13a61094c1 | ||
|
|
230cdd024c | ||
|
|
29a47305a9 | ||
|
|
26ec371730 | ||
|
|
58de4864c0 | ||
|
|
17ff592a60 | ||
|
|
1c267e4c28 | ||
|
|
2f67ccea56 | ||
|
|
b3fdb3a126 | ||
|
|
07ba537590 | ||
|
|
8abde36dcb | ||
|
|
27d6a749f8 | ||
|
|
e7af631737 | ||
|
|
cdd889452a | ||
|
|
4abaa22f62 | ||
|
|
df322ce17d | ||
|
|
0df508d1c6 | ||
|
|
2e7586c39f | ||
|
|
f854ab50d5 | ||
|
|
3cc4005500 | ||
|
|
0d4a95d08e | ||
|
|
d67cf4b9f7 | ||
|
|
c37bd9969c | ||
|
|
1dac41d8ea | ||
|
|
48a03bd383 | ||
|
|
b6897e4faf | ||
|
|
d20a1eb61b | ||
|
|
8ef3ae0ff5 | ||
|
|
c7a048b0dc | ||
|
|
9026076635 | ||
|
|
135dc7084a | ||
|
|
8c784d8391 | ||
|
|
fed30b6fed | ||
|
|
232d4c3f41 | ||
|
|
acb6b8b490 | ||
|
|
3f4ed48787 | ||
|
|
e1d9aa813d | ||
|
|
8a3a6d7e7e | ||
|
|
bf9bfb13b7 |
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -17,6 +17,9 @@ updates:
|
||||
octokit:
|
||||
patterns:
|
||||
- "@octokit/*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
storybook:
|
||||
patterns:
|
||||
- "@storybook/*"
|
||||
@@ -34,3 +37,10 @@ updates:
|
||||
day: "thursday" # Thursday is arbitrary
|
||||
labels:
|
||||
- "Update dependencies"
|
||||
- package-ecosystem: docker
|
||||
directory: "extensions/ql-vscode/test/e2e"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "thursday" # Thursday is arbitrary
|
||||
labels:
|
||||
- "Update dependencies"
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -134,7 +134,7 @@ jobs:
|
||||
|
||||
- name: Publish to Registry
|
||||
run: |
|
||||
npx vsce publish -p $VSCE_TOKEN --packagePath *.vsix
|
||||
npx @vscode/vsce publish -p $VSCE_TOKEN --packagePath *.vsix
|
||||
|
||||
open-vsx-publish:
|
||||
name: Publish to Open VSX Registry
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Releasing (write access required)
|
||||
|
||||
1. Make sure the needed authentication keys are valid. Most likely the Azure DevOps PAT needs to be regenerated. See below.
|
||||
1. Determine the new version number. We default to increasing the patch version number, but make our own judgement about whether a change is big enough to warrant a minor version bump. Common reasons for a minor bump could include:
|
||||
- Making substantial new features available to all users. This can include lifting a feature flag.
|
||||
- Breakage in compatibility with recent versions of the CLI.
|
||||
@@ -60,7 +61,7 @@
|
||||
|
||||
## Secrets and authentication for publishing
|
||||
|
||||
Repository administrators, will need to manage the authentication keys for publishing to the VS Code marketplace and Open VSX. Each requires an authentication token. The VS Code marketplace token expires yearly.
|
||||
Repository administrators, will need to manage the authentication keys for publishing to the VS Code marketplace and Open VSX. Each requires an authentication token.
|
||||
|
||||
To regenerate the Open VSX token:
|
||||
|
||||
@@ -69,4 +70,4 @@ To regenerate the Open VSX token:
|
||||
1. Go to the [Access Tokens](https://open-vsx.org/user-settings/tokens) page and generate a new token.
|
||||
1. Update the secret in the `publish-open-vsx` environment in the project settings.
|
||||
|
||||
To regenerate the VSCode Marketplace token, please see our internal documentation. Note that Azure DevOps PATs expire every 90 days and must be regenerated.
|
||||
To regenerate the VSCode Marketplace token, please see our internal documentation. Note that Azure DevOps PATs expire every 7 days and must be regenerated.
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# CodeQL for Visual Studio Code: Changelog
|
||||
|
||||
## 1.13.1 - 29 May 2024
|
||||
|
||||
- Fix a bug when re-importing test databases that erroneously showed old source code. [#3616](https://github.com/github/vscode-codeql/pull/3616)
|
||||
- Update the progress window with details on potentially long-running post-processing steps after running a query. [#3622](https://github.com/github/vscode-codeql/pull/3622)
|
||||
|
||||
## 1.13.0 - 1 May 2024
|
||||
|
||||
- Add Ruby support to the CodeQL Model Editor. [#3584](https://github.com/github/vscode-codeql/pull/3584)
|
||||
- Remove support for CodeQL CLI versions older than 2.14.6. [#3562](https://github.com/github/vscode-codeql/pull/3562)
|
||||
|
||||
## 1.12.5 - 9 April 2024
|
||||
|
||||
- Add new supported source and sink kinds in the CodeQL Model Editor [#3511](https://github.com/github/vscode-codeql/pull/3511)
|
||||
@@ -492,7 +502,7 @@ No user facing changes.
|
||||
- Allow setting `codeQL.runningQueries.numberOfThreads` and `codeQL.runningTests.numberOfThreads` to 0, (which is interpreted as 'use one thread per core on the machine').
|
||||
- Clear the problems view of all CodeQL query results when a database is removed.
|
||||
- Add a `View DIL` command on query history items. This opens a text editor containing the Datalog Intermediary Language representation of the compiled query.
|
||||
- Remove feature flag for the AST Viewer. For more information on how to use the AST Viewer, [see the documentation](https://help.semmle.com/codeql/codeql-for-vscode/procedures/exploring-the-structure-of-your-source-code.html).
|
||||
- Remove feature flag for the AST Viewer. For more information on how to use the AST Viewer, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/exploring-the-structure-of-your-source-code/).
|
||||
- The `codeQL.runningTests.numberOfThreads` setting is now used correctly when running tests.
|
||||
- Alter structure of the _Test Explorer_ tree. It now follows the structure of the filesystem instead of the qlpacks.
|
||||
- Ensure output of CodeQL test runs includes compilation error messages and test failure messages.
|
||||
|
||||
@@ -9,10 +9,12 @@ This project is an extension for Visual Studio Code that adds rich language supp
|
||||
|
||||
To see what has changed in the last few versions of the extension, see the [Changelog](https://github.com/github/vscode-codeql/blob/main/extensions/ql-vscode/CHANGELOG.md).
|
||||
|
||||
You can also read full documentation for the extension on the [GitHub documentation website](https://docs.github.com/code-security/codeql-for-vs-code/using-the-advanced-functionality-of-the-codeql-for-vs-code-extension).
|
||||
|
||||
## Quick start overview
|
||||
|
||||
The information in this `README` file describes the quickest way to start using CodeQL.
|
||||
For information about other configurations, see the separate [CodeQL help](https://codeql.github.com/docs/codeql-for-visual-studio-code/).
|
||||
For information about other configurations, see "[Using the advanced functionality of the CodeQL for VS Code extension](https://docs.github.com/code-security/codeql-for-vs-code/using-the-advanced-functionality-of-the-codeql-for-vs-code-extension)" in the GitHub documentation.
|
||||
|
||||
### Quick start: Installing and configuring the extension
|
||||
|
||||
@@ -42,7 +44,7 @@ The CodeQL extension requires a minimum of Visual Studio Code 1.39. Older versio
|
||||
|
||||
The extension uses the [CodeQL CLI](https://codeql.github.com/docs/codeql-cli/) to compile and run queries. The extension automatically manages access to the CLI for you by default (recommended). To check for updates to the CodeQL CLI, you can use the **CodeQL: Check for CLI Updates** command.
|
||||
|
||||
If you want to override the default behavior and use a CodeQL CLI that's already on your machine, see [Configuring access to the CodeQL CLI](https://codeql.github.com/docs/codeql-for-visual-studio-code/setting-up-codeql-in-visual-studio-code/#configuring-access-to-the-codeql-cli).
|
||||
If you want to override the default behavior and use a CodeQL CLI that's already on your machine, see "[Configuring access to the CodeQL CLI](https://docs.github.com/code-security/codeql-for-vs-code/using-the-advanced-functionality-of-the-codeql-for-vs-code-extension/configuring-access-to-the-codeql-cli)" in the GitHub documentation.
|
||||
|
||||
If you have any difficulty with CodeQL CLI access, see the **CodeQL Extension Log** in the **Output** view for any error messages.
|
||||
|
||||
@@ -52,7 +54,7 @@ When you're working with CodeQL, you need access to the standard CodeQL librarie
|
||||
Initially, we recommend that you clone and use the ready-to-use [starter workspace](https://github.com/github/vscode-codeql-starter/).
|
||||
This includes libraries and queries for the main supported languages, with folders set up ready for your custom queries. After cloning the workspace (use `git clone --recursive`), you can use it in the same way as any other VS Code workspace—with the added advantage that you can easily update the CodeQL libraries.
|
||||
|
||||
For information about configuring an existing workspace for CodeQL, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/setting-up-codeql-in-visual-studio-code/#updating-an-existing-workspace-for-codeql).
|
||||
For information about configuring an existing workspace for CodeQL, see "[Setting up a CodeQL workspace](https://docs.github.com/code-security/codeql-for-vs-code/using-the-advanced-functionality-of-the-codeql-for-vs-code-extension/setting-up-a-codeql-workspace#option-2-updating-an-existing-workspace-for-codeql-advanced)" in the GitHub documentation.
|
||||
|
||||
## Upgrading CodeQL standard libraries
|
||||
|
||||
@@ -85,7 +87,7 @@ While you can use the [CodeQL CLI to create your own databases](https://codeql.g
|
||||
1. Select the language for the database you want to download (only required if the project has databases for multiple languages).
|
||||
1. Once the CodeQL database has been imported, it is displayed in the Databases view.
|
||||
|
||||
For more information, see [Choosing a database](https://codeql.github.com/docs/codeql-for-visual-studio-code/analyzing-your-projects/#choosing-a-database) on codeql.github.com.
|
||||
For more information, see "[Managing CodeQL databases](https://docs.github.com/code-security/codeql-for-vs-code/getting-started-with-codeql-for-vs-code/managing-codeql-databases#choosing-a-database-to-analyze)" in the GitHub documentation.
|
||||
|
||||
### Running a query
|
||||
|
||||
@@ -106,7 +108,7 @@ If you wish to navigate the query results from your keyboard, you can bind short
|
||||
|
||||
## What next?
|
||||
|
||||
For more information about the CodeQL extension, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/). Otherwise, you could:
|
||||
We recommend reading the [full documentation for the extension](https://docs.github.com/code-security/codeql-for-vs-code/) on the GitHub documentation website. You may also find the following resources useful:
|
||||
|
||||
- [Create a database for a different codebase](https://codeql.github.com/docs/codeql-cli/creating-codeql-databases/).
|
||||
- [Try out variant analysis](https://help.semmle.com/QL/learn-ql/ql-training.html).
|
||||
@@ -119,4 +121,4 @@ The CodeQL extension for Visual Studio Code is [licensed](LICENSE.md) under the
|
||||
|
||||
## Data and Telemetry
|
||||
|
||||
If you specifically opt-in to permit GitHub to do so, GitHub will collect usage data and metrics for the purposes of helping the core developers to improve the CodeQL extension for VS Code. This data will not be shared with any parties outside of GitHub. IP addresses and installation IDs will be retained for a maximum of 30 days. Anonymous data will be retained for a maximum of 180 days. For more information about telemetry, [see the documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code).
|
||||
If you specifically opt-in to permit GitHub to do so, GitHub will collect usage data and metrics for the purposes of helping the core developers to improve the CodeQL extension for VS Code. This data will not be shared with any parties outside of GitHub. IP addresses and installation IDs will be retained for a maximum of 30 days. Anonymous data will be retained for a maximum of 180 days. For more information, see "[Telemetry in CodeQL for Visual Studio Code](https://docs.github.com/code-security/codeql-for-vs-code/using-the-advanced-functionality-of-the-codeql-for-vs-code-extension/telemetry-in-codeql-for-visual-studio-code)" in the GitHub documentation.
|
||||
|
||||
3880
extensions/ql-vscode/package-lock.json
generated
3880
extensions/ql-vscode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.12.5",
|
||||
"version": "1.13.1",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -470,6 +470,11 @@
|
||||
"type": "string",
|
||||
"default": ".github/codeql/extensions/${name}-${language}",
|
||||
"markdownDescription": "Location for newly created CodeQL model packs. The location can be either absolute or relative. If relative, it is relative to the workspace root.\n\nThe following variables are supported:\n* **${database}** - the name of the database\n* **${language}** - the name of the language\n* **${name}** - the name of the GitHub repository, or the name of the database if the database was not downloaded from GitHub\n* **${owner}** - the owner of the GitHub repository, or an empty string if the database was not downloaded from GitHub"
|
||||
},
|
||||
"codeQL.model.packName": {
|
||||
"type": "string",
|
||||
"default": "${owner}/${name}-${language}",
|
||||
"markdownDescription": "Name of newly created CodeQL model packs. If the result is not a valid pack name, it will be converted to a valid pack name.\n\nThe following variables are supported:\n* **${database}** - the name of the database\n* **${language}** - the name of the language\n* **${name}** - the name of the GitHub repository, or the name of the database if the database was not downloaded from GitHub\n* **${owner}** - the owner of the GitHub repository, or an empty string if the database was not downloaded from GitHub"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1950,21 +1955,22 @@
|
||||
"format": "prettier --write **/*.{ts,tsx} && eslint . --ext .ts,.tsx --fix",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx --max-warnings=0",
|
||||
"lint:markdown": "markdownlint-cli2 \"../../**/*.{md,mdx}\" \"!**/node_modules/**\" \"!**/.vscode-test/**\" \"!**/build/cli/v*/**\"",
|
||||
"find-deadcode": "ts-node scripts/find-deadcode.ts",
|
||||
"find-deadcode": "vite-node scripts/find-deadcode.ts",
|
||||
"format-staged": "lint-staged",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"lint:scenarios": "ts-node scripts/lint-scenarios.ts",
|
||||
"lint:scenarios": "vite-node scripts/lint-scenarios.ts",
|
||||
"generate": "npm-run-all -p generate:*",
|
||||
"generate:schemas": "ts-node scripts/generate-schemas.ts",
|
||||
"generate:chromium-version": "ts-node scripts/generate-chromium-version.ts",
|
||||
"generate:schemas": "vite-node scripts/generate-schemas.ts",
|
||||
"generate:chromium-version": "vite-node scripts/generate-chromium-version.ts",
|
||||
"check-types": "find . -type f -name \"tsconfig.json\" -not -path \"./node_modules/*\" | sed -r 's|/[^/]+$||' | sort | uniq | xargs -I {} sh -c \"echo Checking types in {} && cd {} && npx tsc --noEmit\"",
|
||||
"postinstall": "patch-package",
|
||||
"prepare": "cd ../.. && husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.26.10",
|
||||
"@floating-ui/react": "^0.26.12",
|
||||
"@octokit/plugin-retry": "^6.0.1",
|
||||
"@octokit/plugin-throttling": "^8.0.0",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@vscode/codicons": "^0.0.35",
|
||||
"@vscode/debugadapter": "^1.59.0",
|
||||
@@ -1977,17 +1983,17 @@
|
||||
"d3-graphviz": "^5.0.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"msw": "^2.0.13",
|
||||
"nanoid": "^5.0.6",
|
||||
"msw": "^2.2.13",
|
||||
"nanoid": "^5.0.7",
|
||||
"node-fetch": "^2.6.7",
|
||||
"p-queue": "^8.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"semver": "^7.6.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"semver": "^7.6.2",
|
||||
"source-map": "^0.7.4",
|
||||
"source-map-support": "^0.5.21",
|
||||
"stream-json": "^1.7.3",
|
||||
"styled-components": "^6.1.8",
|
||||
"styled-components": "^6.1.9",
|
||||
"tmp": "^0.2.1",
|
||||
"tmp-promise": "^3.0.2",
|
||||
"tree-kill": "^1.2.2",
|
||||
@@ -1998,31 +2004,30 @@
|
||||
"zip-a-folder": "^3.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/core": "^7.24.4",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
|
||||
"@babel/preset-env": "^7.24.0",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-env": "^7.24.4",
|
||||
"@babel/preset-react": "^7.24.1",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@github/markdownlint-github": "^0.6.2",
|
||||
"@octokit/plugin-throttling": "^8.0.0",
|
||||
"@playwright/test": "^1.40.1",
|
||||
"@storybook/addon-a11y": "^8.0.5",
|
||||
"@storybook/addon-actions": "^8.0.5",
|
||||
"@storybook/addon-essentials": "^8.0.5",
|
||||
"@storybook/addon-interactions": "^8.0.5",
|
||||
"@storybook/addon-links": "^8.0.5",
|
||||
"@storybook/addon-a11y": "^8.1.3",
|
||||
"@storybook/addon-actions": "^8.1.3",
|
||||
"@storybook/addon-essentials": "^8.1.3",
|
||||
"@storybook/addon-interactions": "^8.1.3",
|
||||
"@storybook/addon-links": "^8.1.3",
|
||||
"@storybook/blocks": "^8.0.2",
|
||||
"@storybook/components": "^8.0.2",
|
||||
"@storybook/csf": "^0.1.3",
|
||||
"@storybook/csf": "^0.1.7",
|
||||
"@storybook/icons": "^1.2.9",
|
||||
"@storybook/manager-api": "^8.0.5",
|
||||
"@storybook/react": "^8.0.5",
|
||||
"@storybook/react-vite": "^8.0.5",
|
||||
"@storybook/theming": "^8.0.5",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^14.2.2",
|
||||
"@storybook/manager-api": "^8.1.3",
|
||||
"@storybook/react": "^8.1.3",
|
||||
"@storybook/react-vite": "^8.1.3",
|
||||
"@storybook/theming": "^8.1.3",
|
||||
"@testing-library/dom": "^10.1.0",
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/child-process-promise": "^2.2.1",
|
||||
"@types/d3": "^7.4.0",
|
||||
@@ -2036,8 +2041,8 @@
|
||||
"@types/nanoid": "^3.0.0",
|
||||
"@types/node": "18.18.*",
|
||||
"@types/node-fetch": "^2.5.2",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/sarif": "^2.1.2",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/stream-json": "^1.7.1",
|
||||
@@ -2052,7 +2057,7 @@
|
||||
"@vscode/test-electron": "^2.3.9",
|
||||
"@vscode/vsce": "^2.24.0",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"applicationinsights": "^2.9.4",
|
||||
"applicationinsights": "^2.9.5",
|
||||
"cosmiconfig": "^9.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"del": "^6.0.0",
|
||||
@@ -2061,11 +2066,11 @@
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-deprecation": "^2.0.0",
|
||||
"eslint-plugin-etc": "^2.0.2",
|
||||
"eslint-plugin-github": "^4.4.1",
|
||||
"eslint-plugin-github": "^4.10.2",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jest-dom": "^5.2.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-storybook": "^0.8.0",
|
||||
"glob": "^10.0.0",
|
||||
@@ -2073,25 +2078,26 @@
|
||||
"gulp-esbuild": "^0.12.0",
|
||||
"gulp-replace": "^1.1.3",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"husky": "^8.0.0",
|
||||
"husky": "^9.0.11",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"jest-runner-vscode": "^3.0.1",
|
||||
"lint-staged": "^15.2.2",
|
||||
"markdownlint-cli2": "^0.12.1",
|
||||
"markdownlint-cli2-formatter-pretty": "^0.0.5",
|
||||
"markdownlint-cli2": "^0.13.0",
|
||||
"markdownlint-cli2-formatter-pretty": "^0.0.6",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^8.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"storybook": "^8.0.5",
|
||||
"storybook": "^8.1.3",
|
||||
"tar-stream": "^3.1.7",
|
||||
"through2": "^4.0.2",
|
||||
"ts-jest": "^29.0.1",
|
||||
"ts-json-schema-generator": "^1.1.2",
|
||||
"ts-node": "^10.7.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-json-schema-generator": "^2.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-unused-exports": "^10.0.0",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^5.2.6"
|
||||
"vite": "^5.2.11",
|
||||
"vite-node": "^1.5.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/*.{json,css,scss}": [
|
||||
|
||||
@@ -1912,22 +1912,7 @@ function shouldDebugCliServer() {
|
||||
export class CliVersionConstraint {
|
||||
// The oldest version of the CLI that we support. This is used to determine
|
||||
// whether to show a warning about the CLI being too old on startup.
|
||||
public static OLDEST_SUPPORTED_CLI_VERSION = new SemVer("2.13.5");
|
||||
|
||||
/**
|
||||
* CLI version where the `generate extensible-predicate-metadata`
|
||||
* command was implemented.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_EXTENSIBLE_PREDICATE_METADATA = new SemVer(
|
||||
"2.14.3",
|
||||
);
|
||||
|
||||
/**
|
||||
* CLI version where the langauge server supports visisbility change notifications.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_VISIBILITY_NOTIFICATIONS = new SemVer(
|
||||
"2.14.0",
|
||||
);
|
||||
public static OLDEST_SUPPORTED_CLI_VERSION = new SemVer("2.14.6");
|
||||
|
||||
/**
|
||||
* CLI version where the query server supports the `evaluation/trimCache` method
|
||||
@@ -1952,18 +1937,6 @@ export class CliVersionConstraint {
|
||||
return (await this.cli.getVersion()).compare(v) >= 0;
|
||||
}
|
||||
|
||||
async supportsVisibilityNotifications() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_VISIBILITY_NOTIFICATIONS,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsGenerateExtensiblePredicateMetadata() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_EXTENSIBLE_PREDICATE_METADATA,
|
||||
);
|
||||
}
|
||||
|
||||
async preservesExtensiblePredicatesInMrvaPack() {
|
||||
// Negated, because we _stopped_ preserving these in 2.16.1.
|
||||
return !(await this.isVersionAtLeast(
|
||||
|
||||
@@ -31,4 +31,9 @@ export interface Credentials {
|
||||
* @returns An OAuth access token, or undefined.
|
||||
*/
|
||||
getExistingAccessToken(): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* Returns the ID of the authentication provider to use.
|
||||
*/
|
||||
authProviderId: string;
|
||||
}
|
||||
|
||||
@@ -29,37 +29,45 @@ function validGitHubNwoOrOwner(
|
||||
|
||||
/**
|
||||
* Extracts an NWO from a GitHub URL.
|
||||
* @param githubUrl The GitHub repository URL
|
||||
* @param repositoryUrl The GitHub repository URL
|
||||
* @param githubUrl The URL of the GitHub instance
|
||||
* @return The corresponding NWO, or undefined if the URL is not valid
|
||||
*/
|
||||
export function getNwoFromGitHubUrl(githubUrl: string): string | undefined {
|
||||
return getNwoOrOwnerFromGitHubUrl(githubUrl, "nwo");
|
||||
export function getNwoFromGitHubUrl(
|
||||
repositoryUrl: string,
|
||||
githubUrl: URL,
|
||||
): string | undefined {
|
||||
return getNwoOrOwnerFromGitHubUrl(repositoryUrl, githubUrl, "nwo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an owner from a GitHub URL.
|
||||
* @param githubUrl The GitHub repository URL
|
||||
* @param repositoryUrl The GitHub repository URL
|
||||
* @param githubUrl The URL of the GitHub instance
|
||||
* @return The corresponding Owner, or undefined if the URL is not valid
|
||||
*/
|
||||
export function getOwnerFromGitHubUrl(githubUrl: string): string | undefined {
|
||||
return getNwoOrOwnerFromGitHubUrl(githubUrl, "owner");
|
||||
export function getOwnerFromGitHubUrl(
|
||||
repositoryUrl: string,
|
||||
githubUrl: URL,
|
||||
): string | undefined {
|
||||
return getNwoOrOwnerFromGitHubUrl(repositoryUrl, githubUrl, "owner");
|
||||
}
|
||||
|
||||
function getNwoOrOwnerFromGitHubUrl(
|
||||
githubUrl: string,
|
||||
repositoryUrl: string,
|
||||
githubUrl: URL,
|
||||
kind: "owner" | "nwo",
|
||||
): string | undefined {
|
||||
const validHostnames = [githubUrl.hostname, `www.${githubUrl.hostname}`];
|
||||
|
||||
try {
|
||||
let paths: string[];
|
||||
const urlElements = githubUrl.split("/");
|
||||
if (
|
||||
urlElements[0] === "github.com" ||
|
||||
urlElements[0] === "www.github.com"
|
||||
) {
|
||||
paths = githubUrl.split("/").slice(1);
|
||||
const urlElements = repositoryUrl.split("/");
|
||||
if (validHostnames.includes(urlElements[0])) {
|
||||
paths = repositoryUrl.split("/").slice(1);
|
||||
} else {
|
||||
const uri = new URL(githubUrl);
|
||||
if (uri.hostname !== "github.com" && uri.hostname !== "www.github.com") {
|
||||
const uri = new URL(repositoryUrl);
|
||||
if (!validHostnames.includes(uri.hostname)) {
|
||||
return;
|
||||
}
|
||||
paths = uri.pathname.split("/").filter((segment: string) => segment);
|
||||
|
||||
@@ -26,6 +26,8 @@ import {
|
||||
// All path operations in this file must be on paths *within* the zip
|
||||
// archive.
|
||||
import { posix } from "path";
|
||||
import { DatabaseEventKind } from "../../databases/local-databases/database-events";
|
||||
import type { DatabaseManager } from "../../databases/local-databases/database-manager";
|
||||
|
||||
const path = posix;
|
||||
|
||||
@@ -242,15 +244,8 @@ export class ArchiveFileSystemProvider implements FileSystemProvider {
|
||||
|
||||
root = new Directory("");
|
||||
|
||||
constructor() {
|
||||
// When a file system archive is removed from the workspace, we should
|
||||
// also remove it from our cache.
|
||||
workspace.onDidChangeWorkspaceFolders((event) => {
|
||||
for (const removed of event.removed) {
|
||||
const zipPath = removed.uri.fsPath;
|
||||
this.archives.delete(zipPath);
|
||||
}
|
||||
});
|
||||
flushCache(zipPath: string) {
|
||||
this.archives.delete(zipPath);
|
||||
}
|
||||
|
||||
// metadata
|
||||
@@ -366,15 +361,35 @@ export class ArchiveFileSystemProvider implements FileSystemProvider {
|
||||
*/
|
||||
export const zipArchiveScheme = "codeql-zip-archive";
|
||||
|
||||
export function activate(ctx: ExtensionContext) {
|
||||
export function activate(ctx: ExtensionContext, dbm?: DatabaseManager) {
|
||||
const afsp = new ArchiveFileSystemProvider();
|
||||
if (dbm) {
|
||||
ctx.subscriptions.push(
|
||||
dbm.onDidChangeDatabaseItem(async ({ kind, item: db }) => {
|
||||
if (kind === DatabaseEventKind.Remove) {
|
||||
if (db?.sourceArchive) {
|
||||
afsp.flushCache(db.sourceArchive.fsPath);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ctx.subscriptions.push(
|
||||
workspace.registerFileSystemProvider(
|
||||
zipArchiveScheme,
|
||||
new ArchiveFileSystemProvider(),
|
||||
{
|
||||
isCaseSensitive: true,
|
||||
isReadonly: true,
|
||||
},
|
||||
),
|
||||
// When a file system archive is removed from the workspace, we should
|
||||
// also remove it from our cache.
|
||||
workspace.onDidChangeWorkspaceFolders((event) => {
|
||||
for (const removed of event.removed) {
|
||||
const zipPath = removed.uri.fsPath;
|
||||
afsp.flushCache(zipPath);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(
|
||||
workspace.registerFileSystemProvider(zipArchiveScheme, afsp, {
|
||||
isCaseSensitive: true,
|
||||
isReadonly: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { authentication } from "vscode";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
import type { Credentials } from "../authentication";
|
||||
import { AppOctokit } from "../octokit";
|
||||
|
||||
export const GITHUB_AUTH_PROVIDER_ID = "github";
|
||||
import { hasGhecDrUri } from "../../config";
|
||||
import { getOctokitBaseUrl } from "./octokit";
|
||||
|
||||
// We need 'repo' scope for triggering workflows, 'gist' scope for exporting results to Gist,
|
||||
// and 'read:packages' for reading private CodeQL packages.
|
||||
@@ -16,30 +16,24 @@ const SCOPES = ["repo", "gist", "read:packages"];
|
||||
*/
|
||||
export class VSCodeCredentials implements Credentials {
|
||||
/**
|
||||
* A specific octokit to return, otherwise a new authenticated octokit will be created when needed.
|
||||
*/
|
||||
private octokit: Octokit | undefined;
|
||||
|
||||
/**
|
||||
* Creates or returns an instance of Octokit.
|
||||
* Creates or returns an instance of Octokit. The returned instance should
|
||||
* not be stored and reused, as it may become out-of-date with the current
|
||||
* authentication session.
|
||||
*
|
||||
* @returns An instance of Octokit.
|
||||
*/
|
||||
async getOctokit(): Promise<Octokit> {
|
||||
if (this.octokit) {
|
||||
return this.octokit;
|
||||
}
|
||||
|
||||
const accessToken = await this.getAccessToken();
|
||||
|
||||
return new AppOctokit({
|
||||
auth: accessToken,
|
||||
baseUrl: getOctokitBaseUrl(),
|
||||
});
|
||||
}
|
||||
|
||||
async getAccessToken(): Promise<string> {
|
||||
const session = await authentication.getSession(
|
||||
GITHUB_AUTH_PROVIDER_ID,
|
||||
this.authProviderId,
|
||||
SCOPES,
|
||||
{ createIfNone: true },
|
||||
);
|
||||
@@ -49,11 +43,18 @@ export class VSCodeCredentials implements Credentials {
|
||||
|
||||
async getExistingAccessToken(): Promise<string | undefined> {
|
||||
const session = await authentication.getSession(
|
||||
GITHUB_AUTH_PROVIDER_ID,
|
||||
this.authProviderId,
|
||||
SCOPES,
|
||||
{ createIfNone: false },
|
||||
);
|
||||
|
||||
return session?.accessToken;
|
||||
}
|
||||
|
||||
public get authProviderId(): string {
|
||||
if (hasGhecDrUri()) {
|
||||
return "github-enterprise";
|
||||
}
|
||||
return "github";
|
||||
}
|
||||
}
|
||||
|
||||
15
extensions/ql-vscode/src/common/vscode/octokit.ts
Normal file
15
extensions/ql-vscode/src/common/vscode/octokit.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { getGitHubInstanceApiUrl } from "../../config";
|
||||
|
||||
/**
|
||||
* Returns the Octokit base URL to use based on the GitHub instance URL.
|
||||
*
|
||||
* This is necessary because the Octokit base URL should not have a trailing
|
||||
* slash, but this is included by default in a URL.
|
||||
*/
|
||||
export function getOctokitBaseUrl(): string {
|
||||
let apiUrl = getGitHubInstanceApiUrl().toString();
|
||||
if (apiUrl.endsWith("/")) {
|
||||
apiUrl = apiUrl.slice(0, -1);
|
||||
}
|
||||
return apiUrl;
|
||||
}
|
||||
@@ -33,6 +33,14 @@ export interface ProgressUpdate {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export function progressUpdate(
|
||||
step: number,
|
||||
maxStep: number,
|
||||
message: string,
|
||||
): ProgressUpdate {
|
||||
return { step, maxStep, message };
|
||||
}
|
||||
|
||||
export type ProgressCallback = (p: ProgressUpdate) => void;
|
||||
|
||||
// Make certain properties within a type optional
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
ConfigurationScope,
|
||||
Event,
|
||||
} from "vscode";
|
||||
import { ConfigurationTarget, EventEmitter, workspace } from "vscode";
|
||||
import { ConfigurationTarget, EventEmitter, workspace, Uri } from "vscode";
|
||||
import type { DistributionManager } from "./codeql-cli/distribution";
|
||||
import { extLogger } from "./common/logging/vscode";
|
||||
import { ONE_DAY_IN_MS } from "./common/time";
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
SortKey,
|
||||
} from "./variant-analysis/shared/variant-analysis-filter-sort";
|
||||
import { substituteConfigVariables } from "./common/config-template";
|
||||
import { getErrorMessage } from "./common/helpers-pure";
|
||||
|
||||
export const ALL_SETTINGS: Setting[] = [];
|
||||
|
||||
@@ -69,6 +70,95 @@ export const VSCODE_SAVE_BEFORE_START_SETTING = new Setting(
|
||||
VSCODE_DEBUG_SETTING,
|
||||
);
|
||||
|
||||
const VSCODE_GITHUB_ENTERPRISE_SETTING = new Setting(
|
||||
"github-enterprise",
|
||||
undefined,
|
||||
);
|
||||
export const VSCODE_GITHUB_ENTERPRISE_URI_SETTING = new Setting(
|
||||
"uri",
|
||||
VSCODE_GITHUB_ENTERPRISE_SETTING,
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the value of the `github-enterprise.uri` setting, parsed as a URI.
|
||||
* If the value is not set or cannot be parsed, return `undefined`.
|
||||
*/
|
||||
export function getEnterpriseUri(): Uri | undefined {
|
||||
const config = VSCODE_GITHUB_ENTERPRISE_URI_SETTING.getValue<string>();
|
||||
if (config) {
|
||||
try {
|
||||
let uri = Uri.parse(config, true);
|
||||
if (uri.scheme === "http") {
|
||||
uri = uri.with({ scheme: "https" });
|
||||
}
|
||||
return uri;
|
||||
} catch (e) {
|
||||
void extLogger.log(
|
||||
`Failed to parse the GitHub Enterprise URI: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the GitHub Enterprise URI set?
|
||||
*/
|
||||
export function hasEnterpriseUri(): boolean {
|
||||
return getEnterpriseUri() !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the uri look like GHEC-DR?
|
||||
*/
|
||||
function isGhecDrUri(uri: Uri | undefined): boolean {
|
||||
return uri !== undefined && uri.authority.toLowerCase().endsWith(".ghe.com");
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the GitHub Enterprise URI set to something that looks like GHEC-DR?
|
||||
*/
|
||||
export function hasGhecDrUri(): boolean {
|
||||
const uri = getEnterpriseUri();
|
||||
return isGhecDrUri(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* The URI for GitHub.com.
|
||||
*/
|
||||
export const GITHUB_URL = new URL("https://github.com");
|
||||
export const GITHUB_API_URL = new URL("https://api.github.com");
|
||||
|
||||
/**
|
||||
* If the GitHub Enterprise URI is set to something that looks like GHEC-DR, return it.
|
||||
*/
|
||||
export function getGhecDrUri(): Uri | undefined {
|
||||
const uri = getEnterpriseUri();
|
||||
if (isGhecDrUri(uri)) {
|
||||
return uri;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getGitHubInstanceUrl(): URL {
|
||||
const ghecDrUri = getGhecDrUri();
|
||||
if (ghecDrUri) {
|
||||
return new URL(ghecDrUri.toString());
|
||||
}
|
||||
return GITHUB_URL;
|
||||
}
|
||||
|
||||
export function getGitHubInstanceApiUrl(): URL {
|
||||
const ghecDrUri = getGhecDrUri();
|
||||
if (ghecDrUri) {
|
||||
const url = new URL(ghecDrUri.toString());
|
||||
url.hostname = `api.${url.hostname}`;
|
||||
return url;
|
||||
}
|
||||
return GITHUB_API_URL;
|
||||
}
|
||||
|
||||
const ROOT_SETTING = new Setting("codeQL");
|
||||
|
||||
// Telemetry configuration
|
||||
@@ -522,6 +612,12 @@ export async function setRemoteControllerRepo(repo: string | undefined) {
|
||||
|
||||
export interface VariantAnalysisConfig {
|
||||
controllerRepo: string | undefined;
|
||||
showSystemDefinedRepositoryLists: boolean;
|
||||
/**
|
||||
* This uses a URL instead of a URI because the URL class is available in
|
||||
* unit tests and is fully browser-compatible.
|
||||
*/
|
||||
githubUrl: URL;
|
||||
onDidChangeConfiguration?: Event<void>;
|
||||
}
|
||||
|
||||
@@ -531,7 +627,7 @@ export class VariantAnalysisConfigListener
|
||||
{
|
||||
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||
this.handleDidChangeConfigurationForRelevantSettings(
|
||||
[VARIANT_ANALYSIS_SETTING],
|
||||
[VARIANT_ANALYSIS_SETTING, VSCODE_GITHUB_ENTERPRISE_URI_SETTING],
|
||||
e,
|
||||
);
|
||||
}
|
||||
@@ -539,6 +635,14 @@ export class VariantAnalysisConfigListener
|
||||
public get controllerRepo(): string | undefined {
|
||||
return getRemoteControllerRepo();
|
||||
}
|
||||
|
||||
public get showSystemDefinedRepositoryLists(): boolean {
|
||||
return !hasEnterpriseUri();
|
||||
}
|
||||
|
||||
public get githubUrl(): URL {
|
||||
return getGitHubInstanceUrl();
|
||||
}
|
||||
}
|
||||
|
||||
const VARIANT_ANALYSIS_FILTER_RESULTS = new Setting(
|
||||
@@ -725,7 +829,6 @@ export async function setAutogenerateQlPacks(choice: AutogenerateQLPacks) {
|
||||
const MODEL_SETTING = new Setting("model", ROOT_SETTING);
|
||||
const FLOW_GENERATION = new Setting("flowGeneration", MODEL_SETTING);
|
||||
const LLM_GENERATION = new Setting("llmGeneration", MODEL_SETTING);
|
||||
const SHOW_TYPE_MODELS = new Setting("showTypeModels", MODEL_SETTING);
|
||||
const LLM_GENERATION_BATCH_SIZE = new Setting(
|
||||
"llmGenerationBatchSize",
|
||||
MODEL_SETTING,
|
||||
@@ -736,11 +839,8 @@ const LLM_GENERATION_DEV_ENDPOINT = new Setting(
|
||||
);
|
||||
const MODEL_EVALUATION = new Setting("evaluation", MODEL_SETTING);
|
||||
const MODEL_PACK_LOCATION = new Setting("packLocation", MODEL_SETTING);
|
||||
const MODEL_PACK_NAME = new Setting("packName", MODEL_SETTING);
|
||||
const ENABLE_PYTHON = new Setting("enablePython", MODEL_SETTING);
|
||||
const ENABLE_ACCESS_PATH_SUGGESTIONS = new Setting(
|
||||
"enableAccessPathSuggestions",
|
||||
MODEL_SETTING,
|
||||
);
|
||||
|
||||
export type ModelConfigPackVariables = {
|
||||
database: string;
|
||||
@@ -752,18 +852,20 @@ export type ModelConfigPackVariables = {
|
||||
export interface ModelConfig {
|
||||
flowGeneration: boolean;
|
||||
llmGeneration: boolean;
|
||||
showTypeModels: boolean;
|
||||
getPackLocation(
|
||||
languageId: string,
|
||||
variables: ModelConfigPackVariables,
|
||||
): string;
|
||||
getPackName(languageId: string, variables: ModelConfigPackVariables): string;
|
||||
enablePython: boolean;
|
||||
enableAccessPathSuggestions: boolean;
|
||||
}
|
||||
|
||||
export class ModelConfigListener extends ConfigListener implements ModelConfig {
|
||||
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||
this.handleDidChangeConfigurationForRelevantSettings([MODEL_SETTING], e);
|
||||
this.handleDidChangeConfigurationForRelevantSettings(
|
||||
[MODEL_SETTING, VSCODE_GITHUB_ENTERPRISE_URI_SETTING],
|
||||
e,
|
||||
);
|
||||
}
|
||||
|
||||
public get flowGeneration(): boolean {
|
||||
@@ -771,11 +873,7 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
|
||||
}
|
||||
|
||||
public get llmGeneration(): boolean {
|
||||
return !!LLM_GENERATION.getValue<boolean>();
|
||||
}
|
||||
|
||||
public get showTypeModels(): boolean {
|
||||
return !!SHOW_TYPE_MODELS.getValue<boolean>();
|
||||
return !!LLM_GENERATION.getValue<boolean>() && !hasEnterpriseUri();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -810,12 +908,20 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
|
||||
);
|
||||
}
|
||||
|
||||
public get enablePython(): boolean {
|
||||
return !!ENABLE_PYTHON.getValue<boolean>();
|
||||
public getPackName(
|
||||
languageId: string,
|
||||
variables: ModelConfigPackVariables,
|
||||
): string {
|
||||
return substituteConfigVariables(
|
||||
MODEL_PACK_NAME.getValue<string>({
|
||||
languageId,
|
||||
}),
|
||||
variables,
|
||||
);
|
||||
}
|
||||
|
||||
public get enableAccessPathSuggestions(): boolean {
|
||||
return !!ENABLE_ACCESS_PATH_SUGGESTIONS.getValue<boolean>();
|
||||
public get enablePython(): boolean {
|
||||
return !!ENABLE_PYTHON.getValue<boolean>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { AppOctokit } from "../common/octokit";
|
||||
import type { ProgressCallback } from "../common/vscode/progress";
|
||||
import { UserCancellationException } from "../common/vscode/progress";
|
||||
import type { EndpointDefaults } from "@octokit/types";
|
||||
import { getOctokitBaseUrl } from "../common/vscode/octokit";
|
||||
|
||||
export async function getCodeSearchRepositories(
|
||||
query: string,
|
||||
@@ -54,6 +55,7 @@ async function provideOctokitWithThrottling(
|
||||
|
||||
const octokit = new MyOctokit({
|
||||
auth,
|
||||
baseUrl: getOctokitBaseUrl(),
|
||||
throttle: {
|
||||
onRateLimit: (retryAfter: number, options: EndpointDefaults): boolean => {
|
||||
void logger.log(
|
||||
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
addDatabaseSourceToWorkspace,
|
||||
allowHttp,
|
||||
downloadTimeout,
|
||||
getGitHubInstanceUrl,
|
||||
hasGhecDrUri,
|
||||
isCanary,
|
||||
} from "../config";
|
||||
import { showAndLogInformationMessage } from "../common/logging";
|
||||
@@ -150,10 +152,11 @@ export class DatabaseFetcher {
|
||||
maxStep: 2,
|
||||
});
|
||||
|
||||
const instanceUrl = getGitHubInstanceUrl();
|
||||
|
||||
const options: InputBoxOptions = {
|
||||
title:
|
||||
'Enter a GitHub repository URL or "name with owner" (e.g. https://github.com/github/codeql or github/codeql)',
|
||||
placeHolder: "https://github.com/<owner>/<repo> or <owner>/<repo>",
|
||||
title: `Enter a GitHub repository URL or "name with owner" (e.g. ${new URL("/github/codeql", instanceUrl).toString()} or github/codeql)`,
|
||||
placeHolder: `${new URL("/", instanceUrl).toString()}<owner>/<repo> or <owner>/<repo>`,
|
||||
ignoreFocusOut: true,
|
||||
};
|
||||
|
||||
@@ -180,12 +183,14 @@ export class DatabaseFetcher {
|
||||
makeSelected = true,
|
||||
addSourceArchiveFolder = addDatabaseSourceToWorkspace(),
|
||||
): Promise<DatabaseItem | undefined> {
|
||||
const nwo = getNwoFromGitHubUrl(githubRepo) || githubRepo;
|
||||
const nwo =
|
||||
getNwoFromGitHubUrl(githubRepo, getGitHubInstanceUrl()) || githubRepo;
|
||||
if (!isValidGitHubNwo(nwo)) {
|
||||
throw new Error(`Invalid GitHub repository: ${githubRepo}`);
|
||||
}
|
||||
|
||||
const credentials = isCanary() ? this.app.credentials : undefined;
|
||||
const credentials =
|
||||
isCanary() || hasGhecDrUri() ? this.app.credentials : undefined;
|
||||
|
||||
const octokit = credentials
|
||||
? await credentials.getOctokit()
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "./db-item-selection";
|
||||
import { createRemoteTree } from "./db-tree-creator";
|
||||
import type { DbConfigValidationError } from "./db-validation-errors";
|
||||
import type { VariantAnalysisConfig } from "../config";
|
||||
|
||||
export class DbManager extends DisposableObject {
|
||||
public readonly onDbItemsChanged: AppEvent<void>;
|
||||
@@ -25,6 +26,7 @@ export class DbManager extends DisposableObject {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly dbConfigStore: DbConfigStore,
|
||||
private readonly variantAnalysisConfigListener: VariantAnalysisConfig,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -36,6 +38,10 @@ export class DbManager extends DisposableObject {
|
||||
this.dbConfigStore.onDidChangeConfig(() => {
|
||||
this.onDbItemsChangesEventEmitter.fire();
|
||||
});
|
||||
|
||||
this.variantAnalysisConfigListener.onDidChangeConfiguration?.(() => {
|
||||
this.onDbItemsChangesEventEmitter.fire();
|
||||
});
|
||||
}
|
||||
|
||||
public getSelectedDbItem(): DbItem | undefined {
|
||||
@@ -56,7 +62,11 @@ export class DbManager extends DisposableObject {
|
||||
|
||||
const expandedItems = this.getExpandedItems();
|
||||
|
||||
const remoteTree = createRemoteTree(configResult.value, expandedItems);
|
||||
const remoteTree = createRemoteTree(
|
||||
configResult.value,
|
||||
this.variantAnalysisConfigListener,
|
||||
expandedItems,
|
||||
);
|
||||
return ValueResult.ok(remoteTree.children);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DbManager } from "./db-manager";
|
||||
import { DbPanel } from "./ui/db-panel";
|
||||
import { DbSelectionDecorationProvider } from "./ui/db-selection-decoration-provider";
|
||||
import type { DatabasePanelCommands } from "../common/commands";
|
||||
import { VariantAnalysisConfigListener } from "../config";
|
||||
|
||||
export class DbModule extends DisposableObject {
|
||||
public readonly dbManager: DbManager;
|
||||
@@ -17,7 +18,13 @@ export class DbModule extends DisposableObject {
|
||||
super();
|
||||
|
||||
this.dbConfigStore = new DbConfigStore(app);
|
||||
this.dbManager = this.push(new DbManager(app, this.dbConfigStore));
|
||||
this.dbManager = this.push(
|
||||
new DbManager(
|
||||
app,
|
||||
this.dbConfigStore,
|
||||
new VariantAnalysisConfigListener(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public static async initialize(app: App): Promise<DbModule> {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { VariantAnalysisConfig } from "../config";
|
||||
import type { DbConfig, RemoteRepositoryList } from "./config/db-config";
|
||||
import { SelectedDbItemKind } from "./config/db-config";
|
||||
import type {
|
||||
@@ -13,13 +14,17 @@ import { ExpandedDbItemKind } from "./db-item-expansion";
|
||||
|
||||
export function createRemoteTree(
|
||||
dbConfig: DbConfig,
|
||||
variantAnalysisConfig: VariantAnalysisConfig,
|
||||
expandedItems: ExpandedDbItem[],
|
||||
): RootRemoteDbItem {
|
||||
const systemDefinedLists = [
|
||||
createSystemDefinedList(10, dbConfig),
|
||||
createSystemDefinedList(100, dbConfig),
|
||||
createSystemDefinedList(1000, dbConfig),
|
||||
];
|
||||
const systemDefinedLists =
|
||||
variantAnalysisConfig.showSystemDefinedRepositoryLists
|
||||
? [
|
||||
createSystemDefinedList(10, dbConfig),
|
||||
createSystemDefinedList(100, dbConfig),
|
||||
createSystemDefinedList(1000, dbConfig),
|
||||
]
|
||||
: [];
|
||||
|
||||
const userDefinedRepoLists =
|
||||
dbConfig.databases.variantAnalysis.repositoryLists.map((r) =>
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Octokit } from "@octokit/rest";
|
||||
import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";
|
||||
import { showNeverAskAgainDialog } from "../../common/vscode/dialog";
|
||||
import type { GitHubDatabaseConfig } from "../../config";
|
||||
import { hasGhecDrUri } from "../../config";
|
||||
import type { Credentials } from "../../common/authentication";
|
||||
import { AppOctokit } from "../../common/octokit";
|
||||
import type { ProgressCallback } from "../../common/vscode/progress";
|
||||
@@ -67,7 +68,10 @@ export async function listDatabases(
|
||||
credentials: Credentials,
|
||||
config: GitHubDatabaseConfig,
|
||||
): Promise<ListDatabasesResult | undefined> {
|
||||
const hasAccessToken = !!(await credentials.getExistingAccessToken());
|
||||
// On GHEC-DR, unauthenticated requests will never work, so we should always ask
|
||||
// for authentication.
|
||||
const hasAccessToken =
|
||||
!!(await credentials.getExistingAccessToken()) || hasGhecDrUri();
|
||||
|
||||
let octokit = hasAccessToken
|
||||
? await credentials.getOctokit()
|
||||
|
||||
@@ -109,9 +109,8 @@ class DatabaseTreeDataProvider
|
||||
// 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);
|
||||
// When a full refresh has occurred, then all items are refreshed by passing undefined.
|
||||
this._onDidChangeTreeData.fire(event.fullRefresh ? undefined : event.item);
|
||||
}
|
||||
|
||||
private handleDidChangeCurrentDatabaseItem(
|
||||
|
||||
@@ -16,4 +16,8 @@ export enum DatabaseEventKind {
|
||||
export interface DatabaseChangedEvent {
|
||||
kind: DatabaseEventKind;
|
||||
item: DatabaseItem | undefined;
|
||||
// If true, event handlers should consider the database manager
|
||||
// to have been fully refreshed. Any state managed by the
|
||||
// event handler should be fully refreshed as well.
|
||||
fullRefresh: boolean;
|
||||
}
|
||||
|
||||
@@ -613,6 +613,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
this._onDidChangeCurrentDatabaseItem.fire({
|
||||
item,
|
||||
kind: DatabaseEventKind.Change,
|
||||
fullRefresh: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -662,8 +663,9 @@ export class DatabaseManager extends DisposableObject {
|
||||
}
|
||||
// note that we use undefined as the item in order to reset the entire tree
|
||||
this._onDidChangeDatabaseItem.fire({
|
||||
item: undefined,
|
||||
item,
|
||||
kind: DatabaseEventKind.Add,
|
||||
fullRefresh: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -671,9 +673,9 @@ export class DatabaseManager extends DisposableObject {
|
||||
item.name = newName;
|
||||
await this.updatePersistedDatabaseList();
|
||||
this._onDidChangeDatabaseItem.fire({
|
||||
// pass undefined so that the entire tree is rebuilt in order to re-sort
|
||||
item: undefined,
|
||||
item,
|
||||
kind: DatabaseEventKind.Rename,
|
||||
fullRefresh: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -720,10 +722,10 @@ export class DatabaseManager extends DisposableObject {
|
||||
);
|
||||
}
|
||||
|
||||
// note that we use undefined as the item in order to reset the entire tree
|
||||
this._onDidChangeDatabaseItem.fire({
|
||||
item: undefined,
|
||||
item,
|
||||
kind: DatabaseEventKind.Remove,
|
||||
fullRefresh: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -776,6 +778,7 @@ export class DatabaseManager extends DisposableObject {
|
||||
this._onDidChangeDatabaseItem.fire({
|
||||
kind: DatabaseEventKind.Refresh,
|
||||
item: databaseItem,
|
||||
fullRefresh: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import type { App } from "../../common/app";
|
||||
import { QueryLanguage } from "../../common/query-language";
|
||||
import { getCodeSearchRepositories } from "../code-search-api";
|
||||
import { showAndLogErrorMessage } from "../../common/logging";
|
||||
import { getGitHubInstanceUrl } from "../../config";
|
||||
|
||||
export interface RemoteDatabaseQuickPickItem extends QuickPickItem {
|
||||
remoteDatabaseKind: string;
|
||||
@@ -146,16 +147,19 @@ export class DbPanel extends DisposableObject {
|
||||
}
|
||||
|
||||
private async addNewRemoteRepo(parentList?: string): Promise<void> {
|
||||
const instanceUrl = getGitHubInstanceUrl();
|
||||
|
||||
const repoName = await window.showInputBox({
|
||||
title: "Add a repository",
|
||||
prompt: "Insert a GitHub repository URL or name with owner",
|
||||
placeHolder: "<owner>/<repo> or https://github.com/<owner>/<repo>",
|
||||
placeHolder: `<owner>/<repo> or ${new URL("/", instanceUrl).toString()}<owner>/<repo>`,
|
||||
});
|
||||
if (!repoName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nwo = getNwoFromGitHubUrl(repoName) || repoName;
|
||||
const nwo =
|
||||
getNwoFromGitHubUrl(repoName, getGitHubInstanceUrl()) || repoName;
|
||||
if (!isValidGitHubNwo(nwo)) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
@@ -176,17 +180,20 @@ export class DbPanel extends DisposableObject {
|
||||
}
|
||||
|
||||
private async addNewRemoteOwner(): Promise<void> {
|
||||
const instanceUrl = getGitHubInstanceUrl();
|
||||
|
||||
const ownerName = await window.showInputBox({
|
||||
title: "Add all repositories of a GitHub org or owner",
|
||||
prompt: "Insert a GitHub organization or owner name",
|
||||
placeHolder: "<owner> or https://github.com/<owner>",
|
||||
placeHolder: `<owner> or ${new URL("/", instanceUrl).toString()}<owner>`,
|
||||
});
|
||||
|
||||
if (!ownerName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const owner = getOwnerFromGitHubUrl(ownerName) || ownerName;
|
||||
const owner =
|
||||
getOwnerFromGitHubUrl(ownerName, getGitHubInstanceUrl()) || ownerName;
|
||||
if (!isValidGitHubOwner(owner)) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
@@ -411,7 +418,7 @@ export class DbPanel extends DisposableObject {
|
||||
if (treeViewItem.dbItem === undefined) {
|
||||
throw new Error("Unable to open on GitHub. Please select a valid item.");
|
||||
}
|
||||
const githubUrl = getGitHubUrl(treeViewItem.dbItem);
|
||||
const githubUrl = getGitHubUrl(treeViewItem.dbItem, getGitHubInstanceUrl());
|
||||
if (!githubUrl) {
|
||||
throw new Error(
|
||||
"Unable to open on GitHub. Please select a variant analysis repository or owner.",
|
||||
|
||||
@@ -62,12 +62,15 @@ function canImportCodeSearch(dbItem: DbItem): boolean {
|
||||
return DbItemKind.RemoteUserDefinedList === dbItem.kind;
|
||||
}
|
||||
|
||||
export function getGitHubUrl(dbItem: DbItem): string | undefined {
|
||||
export function getGitHubUrl(
|
||||
dbItem: DbItem,
|
||||
githubUrl: URL,
|
||||
): string | undefined {
|
||||
switch (dbItem.kind) {
|
||||
case DbItemKind.RemoteOwner:
|
||||
return `https://github.com/${dbItem.ownerName}`;
|
||||
return new URL(`/${dbItem.ownerName}`, githubUrl).toString();
|
||||
case DbItemKind.RemoteRepo:
|
||||
return `https://github.com/${dbItem.repoFullName}`;
|
||||
return new URL(`/${dbItem.repoFullName}`, githubUrl).toString();
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class QLDebugAdapterTracker
|
||||
): Promise<void> {
|
||||
if (this.localQueryRun !== undefined) {
|
||||
const results: CoreQueryResults = body;
|
||||
await this.localQueryRun.complete(results);
|
||||
await this.localQueryRun.complete(results, (_) => {});
|
||||
this.localQueryRun = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
joinOrderWarningThreshold,
|
||||
QueryHistoryConfigListener,
|
||||
QueryServerConfigListener,
|
||||
VariantAnalysisConfigListener,
|
||||
} from "./config";
|
||||
import {
|
||||
AstViewer,
|
||||
@@ -446,8 +447,9 @@ export async function activate(
|
||||
}
|
||||
|
||||
if (
|
||||
CliVersionConstraint.OLDEST_SUPPORTED_CLI_VERSION.compare(ver.version) <
|
||||
0
|
||||
CliVersionConstraint.OLDEST_SUPPORTED_CLI_VERSION.compare(
|
||||
ver.version,
|
||||
) <= 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -864,8 +866,10 @@ async function activateWithInstalledDistribution(
|
||||
"variant-analyses",
|
||||
);
|
||||
await ensureDir(variantAnalysisStorageDir);
|
||||
const variantAnalysisConfig = new VariantAnalysisConfigListener();
|
||||
const variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cliServer,
|
||||
variantAnalysisConfig,
|
||||
extLogger,
|
||||
);
|
||||
|
||||
@@ -875,6 +879,7 @@ async function activateWithInstalledDistribution(
|
||||
variantAnalysisStorageDir,
|
||||
variantAnalysisResultsManager,
|
||||
dbModule.dbManager,
|
||||
variantAnalysisConfig,
|
||||
);
|
||||
ctx.subscriptions.push(variantAnalysisManager);
|
||||
ctx.subscriptions.push(variantAnalysisResultsManager);
|
||||
@@ -942,7 +947,7 @@ async function activateWithInstalledDistribution(
|
||||
ctx.subscriptions.push(compareView);
|
||||
|
||||
void extLogger.log("Initializing source archive filesystem provider.");
|
||||
archiveFilesystemProvider_activate(ctx);
|
||||
archiveFilesystemProvider_activate(ctx, dbm);
|
||||
|
||||
const qhelpTmpDir = dirSync({
|
||||
prefix: "qhelp_",
|
||||
@@ -1078,14 +1083,12 @@ async function activateWithInstalledDistribution(
|
||||
});
|
||||
|
||||
// Handle visibility changes in the CodeQL language client.
|
||||
if (await cliServer.cliConstraints.supportsVisibilityNotifications()) {
|
||||
Window.onDidChangeVisibleTextEditors((editors) => {
|
||||
languageClient.notifyVisibilityChange(editors);
|
||||
});
|
||||
// Send an inital notification to the language server
|
||||
// to set the initial state of the visible editors.
|
||||
languageClient.notifyVisibilityChange(Window.visibleTextEditors);
|
||||
}
|
||||
Window.onDidChangeVisibleTextEditors((editors) => {
|
||||
languageClient.notifyVisibilityChange(editors);
|
||||
});
|
||||
// Send an inital notification to the language server
|
||||
// to set the initial state of the visible editors.
|
||||
languageClient.notifyVisibilityChange(Window.visibleTextEditors);
|
||||
|
||||
// Jump-to-definition and find-references
|
||||
void extLogger.log("Registering jump-to-definition handlers.");
|
||||
|
||||
@@ -485,7 +485,7 @@ export class LocalQueries extends DisposableObject {
|
||||
localQueryRun.logger,
|
||||
);
|
||||
|
||||
await localQueryRun.complete(results);
|
||||
await localQueryRun.complete(results, progress);
|
||||
|
||||
return results;
|
||||
} catch (e) {
|
||||
|
||||
@@ -26,6 +26,8 @@ import type { LocalQueries } from "./local-queries";
|
||||
import { tryGetQueryMetadata } from "../codeql-cli/query-metadata";
|
||||
import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import type { Disposable } from "../common/disposable-object";
|
||||
import type { ProgressCallback } from "../common/vscode/progress";
|
||||
import { progressUpdate } from "../common/vscode/progress";
|
||||
|
||||
function formatResultMessage(result: CoreQueryResults): string {
|
||||
switch (result.resultType) {
|
||||
@@ -79,23 +81,31 @@ export class LocalQueryRun {
|
||||
* This function must be called when the evaluation completes, whether the evaluation was
|
||||
* successful or not.
|
||||
* */
|
||||
public async complete(results: CoreQueryResults): Promise<void> {
|
||||
public async complete(
|
||||
results: CoreQueryResults,
|
||||
progress: ProgressCallback,
|
||||
): Promise<void> {
|
||||
const evalLogPaths = await this.summarizeEvalLog(
|
||||
results.resultType,
|
||||
this.outputDir,
|
||||
this.logger,
|
||||
progress,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
|
||||
}
|
||||
progress(progressUpdate(1, 4, "Getting completed query info"));
|
||||
const queryWithResults = await this.getCompletedQueryInfo(results);
|
||||
progress(progressUpdate(2, 4, "Updating query history"));
|
||||
this.queryHistoryManager.completeQuery(this.queryInfo, queryWithResults);
|
||||
progress(progressUpdate(3, 4, "Showing results"));
|
||||
await this.localQueries.showResultsForCompletedQuery(
|
||||
this.queryInfo as CompletedLocalQueryInfo,
|
||||
WebviewReveal.Forced,
|
||||
);
|
||||
// Note we must update the query history view after showing results as the
|
||||
// display and sorting might depend on the number of results
|
||||
progress(progressUpdate(4, 4, "Updating query history"));
|
||||
await this.queryHistoryManager.refreshTreeView();
|
||||
|
||||
this.logger.dispose();
|
||||
@@ -109,6 +119,7 @@ export class LocalQueryRun {
|
||||
QueryResultType.OTHER_ERROR,
|
||||
this.outputDir,
|
||||
this.logger,
|
||||
(_) => {},
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
this.queryInfo.setEvaluatorLogPaths(evalLogPaths);
|
||||
@@ -128,10 +139,12 @@ export class LocalQueryRun {
|
||||
resultType: QueryResultType,
|
||||
outputDir: QueryOutputDir,
|
||||
logger: BaseLogger,
|
||||
progress: ProgressCallback,
|
||||
): Promise<EvaluatorLogPaths | undefined> {
|
||||
const evalLogPaths = await generateEvalLogSummaries(
|
||||
this.cliServer,
|
||||
outputDir,
|
||||
progress,
|
||||
);
|
||||
if (evalLogPaths !== undefined) {
|
||||
if (evalLogPaths.endSummary !== undefined) {
|
||||
|
||||
@@ -15,11 +15,19 @@ export function formatPackName(packName: ExtensionPackName): string {
|
||||
return `${packName.scope}/${packName.name}`;
|
||||
}
|
||||
|
||||
export function autoNameExtensionPack(
|
||||
name: string,
|
||||
language: string,
|
||||
): ExtensionPackName | undefined {
|
||||
let packName = `${name}-${language}`;
|
||||
export function sanitizePackName(userPackName: string): ExtensionPackName {
|
||||
let packName = userPackName;
|
||||
|
||||
packName = packName.trim();
|
||||
|
||||
while (packName.startsWith("/")) {
|
||||
packName = packName.slice(1);
|
||||
}
|
||||
|
||||
while (packName.endsWith("/")) {
|
||||
packName = packName.slice(0, -1);
|
||||
}
|
||||
|
||||
if (!packName.includes("/")) {
|
||||
packName = `pack/${packName}`;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ import type { NotificationLogger } from "../common/logging";
|
||||
import { showAndLogErrorMessage } from "../common/logging";
|
||||
import type { ModelConfig, ModelConfigPackVariables } from "../config";
|
||||
import type { ExtensionPackName } from "./extension-pack-name";
|
||||
import { autoNameExtensionPack, formatPackName } from "./extension-pack-name";
|
||||
import {
|
||||
validatePackName,
|
||||
sanitizePackName,
|
||||
formatPackName,
|
||||
} from "./extension-pack-name";
|
||||
import {
|
||||
ensurePackLocationIsInWorkspaceFolder,
|
||||
packLocationToAbsolute,
|
||||
@@ -80,15 +84,20 @@ export async function pickExtensionPack(
|
||||
|
||||
await ensurePackLocationIsInWorkspaceFolder(packPath, modelConfig, logger);
|
||||
|
||||
// Generate the name of the extension pack
|
||||
const packName = autoNameExtensionPack(
|
||||
databaseItem.name,
|
||||
const userPackName = modelConfig.getPackName(
|
||||
databaseItem.language,
|
||||
getModelConfigPackVariables(databaseItem),
|
||||
);
|
||||
if (!packName) {
|
||||
|
||||
// Generate the name of the extension pack
|
||||
const packName = sanitizePackName(userPackName);
|
||||
|
||||
// Validate that the name isn't too long etc.
|
||||
const packNameError = validatePackName(formatPackName(packName));
|
||||
if (packNameError) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
`Could not automatically name extension pack for database ${databaseItem.name}`,
|
||||
`Invalid model pack name '${formatPackName(packName)}' for database ${databaseItem.name}: ${packNameError}`,
|
||||
);
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { AccessPathSuggestionRow } from "../suggestions";
|
||||
// This is a subset of the model config that doesn't import the vscode module.
|
||||
// It only includes settings that are actually used.
|
||||
export type ModelConfig = {
|
||||
showTypeModels: boolean;
|
||||
flowGeneration: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -32,12 +32,12 @@ export type ModelConfig = {
|
||||
*/
|
||||
export function createModelConfig(modelConfig: ModelConfig): ModelConfig {
|
||||
return {
|
||||
showTypeModels: modelConfig.showTypeModels,
|
||||
flowGeneration: modelConfig.flowGeneration,
|
||||
};
|
||||
}
|
||||
|
||||
export const defaultModelConfig: ModelConfig = {
|
||||
showTypeModels: false,
|
||||
flowGeneration: false,
|
||||
};
|
||||
|
||||
type GenerateMethodDefinition<T> = (method: T) => DataTuple[];
|
||||
@@ -105,15 +105,34 @@ type ParseResultsToYaml = (
|
||||
logger: BaseLogger,
|
||||
) => ModelExtension[];
|
||||
|
||||
export enum AutoModelGenerationType {
|
||||
/**
|
||||
* Auto model generation is disabled and will not be run.
|
||||
*/
|
||||
Disabled = "disabled",
|
||||
/**
|
||||
* The models are generated to a separate file (suffixed with .model.generated.yml).
|
||||
*/
|
||||
SeparateFile = "separateFile",
|
||||
/**
|
||||
* The models are added as a model in the model editor, but are not automatically saved.
|
||||
* The user can view them and choose to save them.
|
||||
*/
|
||||
Models = "models",
|
||||
}
|
||||
|
||||
type ModelsAsDataLanguageAutoModelGeneration = {
|
||||
queryConstraints: (mode: Mode) => QueryConstraints;
|
||||
filterQueries?: (queryPath: string) => boolean;
|
||||
/**
|
||||
* This function is only used when type is `separateFile`.
|
||||
*/
|
||||
parseResultsToYaml: ParseResultsToYaml;
|
||||
/**
|
||||
* By default, auto model generation is enabled for all modes. This function can be used to
|
||||
* override that behavior.
|
||||
* This function is only used when type is `models`.
|
||||
*/
|
||||
enabled?: (context: GenerationContext) => boolean;
|
||||
parseResults: ParseGenerationResults;
|
||||
type: (context: GenerationContext) => AutoModelGenerationType;
|
||||
};
|
||||
|
||||
type ModelsAsDataLanguageAccessPathSuggestions = {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { BaseLogger } from "../../../common/logging";
|
||||
import type { DecodedBqrs } from "../../../common/bqrs-cli-types";
|
||||
import type {
|
||||
GenerationContext,
|
||||
ModelsAsDataLanguage,
|
||||
} from "../models-as-data";
|
||||
import type { ModelsAsDataLanguage } from "../models-as-data";
|
||||
import type { ModeledMethod } from "../../modeled-method";
|
||||
import type { DataTuple } from "../../model-extension-file";
|
||||
|
||||
@@ -12,21 +9,10 @@ export function parseGenerateModelResults(
|
||||
bqrs: DecodedBqrs,
|
||||
modelsAsDataLanguage: ModelsAsDataLanguage,
|
||||
logger: BaseLogger,
|
||||
{ config }: GenerationContext,
|
||||
): ModeledMethod[] {
|
||||
const modeledMethods: ModeledMethod[] = [];
|
||||
|
||||
for (const resultSetName in bqrs) {
|
||||
if (
|
||||
resultSetName ===
|
||||
modelsAsDataLanguage.predicates.type?.extensiblePredicate &&
|
||||
!config.showTypeModels
|
||||
) {
|
||||
// Don't load generated type results when type models are hidden. These are already
|
||||
// automatically generated on start-up.
|
||||
continue;
|
||||
}
|
||||
|
||||
const definition = Object.values(modelsAsDataLanguage.predicates).find(
|
||||
(definition) => definition.extensiblePredicate === resultSetName,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ModelsAsDataLanguage } from "../models-as-data";
|
||||
import { AutoModelGenerationType } from "../models-as-data";
|
||||
import { sharedExtensiblePredicates, sharedKinds } from "../shared";
|
||||
import { Mode } from "../../shared/mode";
|
||||
import { parseGenerateModelResults } from "./generate";
|
||||
@@ -169,7 +170,6 @@ export const ruby: ModelsAsDataLanguage = {
|
||||
methodParameters: "",
|
||||
};
|
||||
},
|
||||
isHidden: ({ config }) => !config.showTypeModels,
|
||||
},
|
||||
},
|
||||
modelGeneration: {
|
||||
@@ -209,9 +209,32 @@ export const ruby: ModelsAsDataLanguage = {
|
||||
},
|
||||
];
|
||||
},
|
||||
parseResults: (queryPath, bqrs, modelsAsDataLanguage, logger) => {
|
||||
// Only parse type models when automatically generating models
|
||||
const typePredicate = modelsAsDataLanguage.predicates.type;
|
||||
if (!typePredicate) {
|
||||
throw new Error("Type predicate not found");
|
||||
}
|
||||
|
||||
const typeTuples = bqrs[typePredicate.extensiblePredicate];
|
||||
if (!typeTuples) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return parseGenerateModelResults(
|
||||
queryPath,
|
||||
{
|
||||
[typePredicate.extensiblePredicate]: typeTuples,
|
||||
},
|
||||
modelsAsDataLanguage,
|
||||
logger,
|
||||
);
|
||||
},
|
||||
// Only enabled for framework mode when type models are hidden
|
||||
enabled: ({ mode, config }) =>
|
||||
mode === Mode.Framework && !config.showTypeModels,
|
||||
type: ({ mode }) =>
|
||||
mode === Mode.Framework
|
||||
? AutoModelGenerationType.Models
|
||||
: AutoModelGenerationType.Disabled,
|
||||
},
|
||||
accessPathSuggestions: {
|
||||
queryConstraints: (mode) => ({
|
||||
|
||||
@@ -54,7 +54,11 @@ import { telemetryListener } from "../common/vscode/telemetry";
|
||||
import type { ModelingStore } from "./modeling-store";
|
||||
import type { ModelingEvents } from "./modeling-events";
|
||||
import type { ModelsAsDataLanguage } from "./languages";
|
||||
import { createModelConfig, getModelsAsDataLanguage } from "./languages";
|
||||
import {
|
||||
AutoModelGenerationType,
|
||||
createModelConfig,
|
||||
getModelsAsDataLanguage,
|
||||
} from "./languages";
|
||||
import { runGenerateQueries } from "./generate";
|
||||
import { ResponseError } from "vscode-jsonrpc";
|
||||
import { LSPErrorCodes } from "vscode-languageclient";
|
||||
@@ -274,7 +278,6 @@ export class ModelEditorView extends AbstractWebview<
|
||||
modeledMethods,
|
||||
mode,
|
||||
this.cliServer,
|
||||
this.modelConfig,
|
||||
this.app.logger,
|
||||
);
|
||||
|
||||
@@ -348,17 +351,7 @@ export class ModelEditorView extends AbstractWebview<
|
||||
withProgress((progress) => this.loadMethods(progress), {
|
||||
cancellable: false,
|
||||
}),
|
||||
// Only load access path suggestions if the feature is enabled
|
||||
this.modelConfig.enableAccessPathSuggestions
|
||||
? withProgress(
|
||||
(progress) => this.loadAccessPathSuggestions(progress),
|
||||
{
|
||||
cancellable: false,
|
||||
location: ProgressLocation.Window,
|
||||
title: "Loading access path suggestions",
|
||||
},
|
||||
)
|
||||
: undefined,
|
||||
this.loadAccessPathSuggestions(),
|
||||
]);
|
||||
void telemetryListener?.sendUIInteraction("model-editor-switch-modes");
|
||||
|
||||
@@ -416,14 +409,7 @@ export class ModelEditorView extends AbstractWebview<
|
||||
await this.generateModeledMethodsOnStartup();
|
||||
}),
|
||||
this.loadExistingModeledMethods(),
|
||||
// Only load access path suggestions if the feature is enabled
|
||||
this.modelConfig.enableAccessPathSuggestions
|
||||
? withProgress((progress) => this.loadAccessPathSuggestions(progress), {
|
||||
cancellable: false,
|
||||
location: ProgressLocation.Window,
|
||||
title: "Loading access path suggestions",
|
||||
})
|
||||
: undefined,
|
||||
this.loadAccessPathSuggestions(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -500,7 +486,6 @@ export class ModelEditorView extends AbstractWebview<
|
||||
this.extensionPack,
|
||||
this.language,
|
||||
this.cliServer,
|
||||
this.modelConfig,
|
||||
this.app.logger,
|
||||
);
|
||||
this.modelingStore.setModeledMethods(this.databaseItem, modeledMethods);
|
||||
@@ -568,9 +553,7 @@ export class ModelEditorView extends AbstractWebview<
|
||||
}
|
||||
}
|
||||
|
||||
protected async loadAccessPathSuggestions(
|
||||
progress: ProgressCallback,
|
||||
): Promise<void> {
|
||||
protected async loadAccessPathSuggestions(): Promise<void> {
|
||||
const mode = this.modelingStore.getMode(this.databaseItem);
|
||||
|
||||
const modelsAsDataLanguage = getModelsAsDataLanguage(this.language);
|
||||
@@ -579,46 +562,55 @@ export class ModelEditorView extends AbstractWebview<
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const suggestions = await runSuggestionsQuery(mode, {
|
||||
parseResults: (results) =>
|
||||
accessPathSuggestions.parseResults(
|
||||
results,
|
||||
modelsAsDataLanguage,
|
||||
await withProgress(
|
||||
async (progress) => {
|
||||
try {
|
||||
const suggestions = await runSuggestionsQuery(mode, {
|
||||
parseResults: (results) =>
|
||||
accessPathSuggestions.parseResults(
|
||||
results,
|
||||
modelsAsDataLanguage,
|
||||
this.app.logger,
|
||||
),
|
||||
queryConstraints: accessPathSuggestions.queryConstraints(mode),
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
databaseItem: this.databaseItem,
|
||||
progress,
|
||||
token: this.cancellationTokenSource.token,
|
||||
logger: this.app.logger,
|
||||
});
|
||||
|
||||
if (!suggestions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options: AccessPathSuggestionOptions = {
|
||||
input: parseAccessPathSuggestionRowsToOptions(suggestions.input),
|
||||
output: parseAccessPathSuggestionRowsToOptions(suggestions.output),
|
||||
};
|
||||
|
||||
await this.postMessage({
|
||||
t: "setAccessPathSuggestions",
|
||||
accessPathSuggestions: options,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
),
|
||||
queryConstraints: accessPathSuggestions.queryConstraints(mode),
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
queryStorageDir: this.queryStorageDir,
|
||||
databaseItem: this.databaseItem,
|
||||
progress,
|
||||
token: this.cancellationTokenSource.token,
|
||||
logger: this.app.logger,
|
||||
});
|
||||
|
||||
if (!suggestions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options: AccessPathSuggestionOptions = {
|
||||
input: parseAccessPathSuggestionRowsToOptions(suggestions.input),
|
||||
output: parseAccessPathSuggestionRowsToOptions(suggestions.output),
|
||||
};
|
||||
|
||||
await this.postMessage({
|
||||
t: "setAccessPathSuggestions",
|
||||
accessPathSuggestions: options,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
this.app.logger,
|
||||
this.app.telemetry,
|
||||
redactableError(
|
||||
asError(e),
|
||||
)`Failed to fetch access path suggestions: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
this.app.telemetry,
|
||||
redactableError(
|
||||
asError(e),
|
||||
)`Failed to fetch access path suggestions: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
cancellable: false,
|
||||
location: ProgressLocation.Window,
|
||||
title: "Loading access path suggestions",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
protected async generateModeledMethods(): Promise<void> {
|
||||
@@ -710,10 +702,12 @@ export class ModelEditorView extends AbstractWebview<
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
autoModelGeneration.enabled &&
|
||||
!autoModelGeneration.enabled({ mode, config: this.modelConfig })
|
||||
) {
|
||||
const autoModelType = autoModelGeneration.type({
|
||||
mode,
|
||||
config: this.modelConfig,
|
||||
});
|
||||
|
||||
if (autoModelType === AutoModelGenerationType.Disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -734,14 +728,37 @@ export class ModelEditorView extends AbstractWebview<
|
||||
queryConstraints: autoModelGeneration.queryConstraints(mode),
|
||||
filterQueries: autoModelGeneration.filterQueries,
|
||||
onResults: (queryPath, results) => {
|
||||
const extensions = autoModelGeneration.parseResultsToYaml(
|
||||
queryPath,
|
||||
results,
|
||||
modelsAsDataLanguage,
|
||||
this.app.logger,
|
||||
);
|
||||
switch (autoModelType) {
|
||||
case AutoModelGenerationType.SeparateFile: {
|
||||
const extensions = autoModelGeneration.parseResultsToYaml(
|
||||
queryPath,
|
||||
results,
|
||||
modelsAsDataLanguage,
|
||||
this.app.logger,
|
||||
);
|
||||
|
||||
extensionFile.extensions.push(...extensions);
|
||||
extensionFile.extensions.push(...extensions);
|
||||
break;
|
||||
}
|
||||
case AutoModelGenerationType.Models: {
|
||||
const modeledMethods = autoModelGeneration.parseResults(
|
||||
queryPath,
|
||||
results,
|
||||
modelsAsDataLanguage,
|
||||
this.app.logger,
|
||||
{
|
||||
mode,
|
||||
config: this.modelConfig,
|
||||
},
|
||||
);
|
||||
|
||||
this.addModeledMethodsFromArray(modeledMethods);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertNever(autoModelType);
|
||||
}
|
||||
}
|
||||
},
|
||||
cliServer: this.cliServer,
|
||||
queryRunner: this.queryRunner,
|
||||
@@ -761,22 +778,24 @@ export class ModelEditorView extends AbstractWebview<
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
step: 4000,
|
||||
maxStep: 4000,
|
||||
message: "Saving generated models",
|
||||
});
|
||||
if (autoModelType === AutoModelGenerationType.SeparateFile) {
|
||||
progress({
|
||||
step: 4000,
|
||||
maxStep: 4000,
|
||||
message: "Saving generated models",
|
||||
});
|
||||
|
||||
const fileContents = `# This file was automatically generated from ${this.databaseItem.name}. Manual changes will not persist.\n\n${modelExtensionFileToYaml(extensionFile)}`;
|
||||
const filePath = join(
|
||||
this.extensionPack.path,
|
||||
"models",
|
||||
`${this.language}${GENERATED_MODELS_SUFFIX}`,
|
||||
);
|
||||
const fileContents = `# This file was automatically generated from ${this.databaseItem.name}. Manual changes will not persist.\n\n${modelExtensionFileToYaml(extensionFile)}`;
|
||||
const filePath = join(
|
||||
this.extensionPack.path,
|
||||
"models",
|
||||
`${this.language}${GENERATED_MODELS_SUFFIX}`,
|
||||
);
|
||||
|
||||
await outputFile(filePath, fileContents);
|
||||
await outputFile(filePath, fileContents);
|
||||
|
||||
void this.app.logger.log(`Saved generated model file to ${filePath}`);
|
||||
void this.app.logger.log(`Saved generated model file to ${filePath}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
cancellable: false,
|
||||
|
||||
@@ -12,7 +12,6 @@ import { load as loadYaml } from "js-yaml";
|
||||
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { pathsEqual } from "../common/files";
|
||||
import type { QueryLanguage } from "../common/query-language";
|
||||
import type { ModelConfig } from "./languages";
|
||||
|
||||
export const GENERATED_MODELS_SUFFIX = ".model.generated.yml";
|
||||
|
||||
@@ -23,14 +22,12 @@ export async function saveModeledMethods(
|
||||
modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
|
||||
mode: Mode,
|
||||
cliServer: CodeQLCliServer,
|
||||
modelConfig: ModelConfig,
|
||||
logger: NotificationLogger,
|
||||
): Promise<void> {
|
||||
const existingModeledMethods = await loadModeledMethodFiles(
|
||||
extensionPack,
|
||||
language,
|
||||
cliServer,
|
||||
modelConfig,
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -53,14 +50,9 @@ async function loadModeledMethodFiles(
|
||||
extensionPack: ExtensionPack,
|
||||
language: QueryLanguage,
|
||||
cliServer: CodeQLCliServer,
|
||||
modelConfig: ModelConfig,
|
||||
logger: NotificationLogger,
|
||||
): Promise<Record<string, Record<string, ModeledMethod[]>>> {
|
||||
const modelFiles = await listModelFiles(
|
||||
extensionPack.path,
|
||||
cliServer,
|
||||
modelConfig,
|
||||
);
|
||||
const modelFiles = await listModelFiles(extensionPack.path, cliServer);
|
||||
|
||||
const modeledMethodsByFile: Record<
|
||||
string,
|
||||
@@ -92,7 +84,6 @@ export async function loadModeledMethods(
|
||||
extensionPack: ExtensionPack,
|
||||
language: QueryLanguage,
|
||||
cliServer: CodeQLCliServer,
|
||||
modelConfig: ModelConfig,
|
||||
logger: NotificationLogger,
|
||||
): Promise<Record<string, ModeledMethod[]>> {
|
||||
const existingModeledMethods: Record<string, ModeledMethod[]> = {};
|
||||
@@ -101,7 +92,6 @@ export async function loadModeledMethods(
|
||||
extensionPack,
|
||||
language,
|
||||
cliServer,
|
||||
modelConfig,
|
||||
logger,
|
||||
);
|
||||
for (const modeledMethods of Object.values(modeledMethodsByFile)) {
|
||||
@@ -120,7 +110,6 @@ export async function loadModeledMethods(
|
||||
export async function listModelFiles(
|
||||
extensionPackPath: string,
|
||||
cliServer: CodeQLCliServer,
|
||||
modelConfig: ModelConfig,
|
||||
): Promise<Set<string>> {
|
||||
const result = await cliServer.resolveExtensions(
|
||||
extensionPackPath,
|
||||
@@ -131,11 +120,8 @@ export async function listModelFiles(
|
||||
for (const [path, extensions] of Object.entries(result.data)) {
|
||||
if (pathsEqual(path, extensionPackPath)) {
|
||||
for (const extension of extensions) {
|
||||
// We only load generated models when type models are shown
|
||||
if (
|
||||
!modelConfig.showTypeModels &&
|
||||
extension.file.endsWith(GENERATED_MODELS_SUFFIX)
|
||||
) {
|
||||
// We never load generated models
|
||||
if (extension.file.endsWith(GENERATED_MODELS_SUFFIX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { QueryLanguage } from "../common/query-language";
|
||||
import type { ModelConfig } from "../config";
|
||||
import { isCanary } from "../config";
|
||||
|
||||
/**
|
||||
* Languages that are always supported by the model editor. These languages
|
||||
@@ -9,6 +8,7 @@ import { isCanary } from "../config";
|
||||
export const SUPPORTED_LANGUAGES: QueryLanguage[] = [
|
||||
QueryLanguage.Java,
|
||||
QueryLanguage.CSharp,
|
||||
QueryLanguage.Ruby,
|
||||
];
|
||||
|
||||
export function isSupportedLanguage(
|
||||
@@ -19,11 +19,6 @@ export function isSupportedLanguage(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (language === QueryLanguage.Ruby) {
|
||||
// Ruby is only enabled when in canary mode
|
||||
return isCanary();
|
||||
}
|
||||
|
||||
if (language === QueryLanguage.Python) {
|
||||
// Python is only enabled when the config setting is set
|
||||
return modelConfig.enablePython;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
getActionsWorkflowRunUrl as getVariantAnalysisActionsWorkflowRunUrl,
|
||||
} from "../variant-analysis/shared/variant-analysis";
|
||||
import type { QueryLanguage } from "../common/query-language";
|
||||
import { getGitHubInstanceUrl } from "../config";
|
||||
|
||||
export type QueryHistoryInfo = LocalQueryInfo | VariantAnalysisHistoryItem;
|
||||
|
||||
@@ -79,5 +80,8 @@ export function buildRepoLabel(item: VariantAnalysisHistoryItem): string {
|
||||
export function getActionsWorkflowRunUrl(
|
||||
item: VariantAnalysisHistoryItem,
|
||||
): string {
|
||||
return getVariantAnalysisActionsWorkflowRunUrl(item.variantAnalysis);
|
||||
return getVariantAnalysisActionsWorkflowRunUrl(
|
||||
item.variantAnalysis,
|
||||
getGitHubInstanceUrl(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -240,8 +240,32 @@ export class QueryServerClient extends DisposableObject {
|
||||
this.nextCallback = 0;
|
||||
this.nextProgress = 0;
|
||||
this.progressCallbacks = {};
|
||||
child.on("close", () => {
|
||||
this.restartQueryServerOnFailure();
|
||||
|
||||
// 'exit' may or may not fire after 'error' event, so we want to guard against restarting the
|
||||
// query server twice if both events fire.
|
||||
let wasExitOrErrorHandled = false;
|
||||
child.on("error", (err: Error) => {
|
||||
if (!wasExitOrErrorHandled) {
|
||||
void this.logger.log(`Query server terminated with error: ${err}.`);
|
||||
this.restartQueryServerOnFailure();
|
||||
wasExitOrErrorHandled = true;
|
||||
}
|
||||
});
|
||||
child.on("exit", (code: number, signal: string) => {
|
||||
if (!wasExitOrErrorHandled) {
|
||||
if (code !== null) {
|
||||
void this.logger.log(
|
||||
`Query server terminated with exit code: ${code}.`,
|
||||
);
|
||||
}
|
||||
if (signal !== null) {
|
||||
void this.logger.log(
|
||||
`Query server terminated due to receipt of signal: ${signal}.`,
|
||||
);
|
||||
}
|
||||
this.restartQueryServerOnFailure();
|
||||
wasExitOrErrorHandled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ import { generateSummarySymbolsFile } from "./log-insights/summary-parser";
|
||||
import { getErrorMessage } from "./common/helpers-pure";
|
||||
import { createHash } from "crypto";
|
||||
import { QueryOutputDir } from "./local-queries/query-output-dir";
|
||||
import { progressUpdate } from "./common/vscode/progress";
|
||||
import type { ProgressCallback } from "./common/vscode/progress";
|
||||
|
||||
/**
|
||||
* run-queries.ts
|
||||
@@ -519,6 +521,7 @@ export async function createInitialQueryInfo(
|
||||
export async function generateEvalLogSummaries(
|
||||
cliServer: CodeQLCliServer,
|
||||
outputDir: QueryOutputDir,
|
||||
progress: ProgressCallback,
|
||||
): Promise<EvaluatorLogPaths | undefined> {
|
||||
const log = outputDir.evalLogPath;
|
||||
if (!(await pathExists(log))) {
|
||||
@@ -527,6 +530,7 @@ export async function generateEvalLogSummaries(
|
||||
}
|
||||
let humanReadableSummary: string | undefined = undefined;
|
||||
let endSummary: string | undefined = undefined;
|
||||
progress(progressUpdate(1, 3, "Generating evaluator log summary"));
|
||||
if (await generateHumanReadableLogSummary(cliServer, outputDir)) {
|
||||
humanReadableSummary = outputDir.evalLogSummaryPath;
|
||||
endSummary = outputDir.evalLogEndSummaryPath;
|
||||
@@ -535,10 +539,12 @@ export async function generateEvalLogSummaries(
|
||||
let summarySymbols: string | undefined = undefined;
|
||||
if (isCanary()) {
|
||||
// Generate JSON summary for viewer.
|
||||
progress(progressUpdate(2, 3, "Generating JSON log summary"));
|
||||
jsonSummary = outputDir.jsonEvalLogSummaryPath;
|
||||
await cliServer.generateJsonLogSummary(log, jsonSummary);
|
||||
|
||||
if (humanReadableSummary !== undefined) {
|
||||
progress(progressUpdate(3, 3, "Generating summary symbols file"));
|
||||
summarySymbols = outputDir.evalLogSummarySymbolsPath;
|
||||
await generateSummarySymbolsFile(humanReadableSummary, summarySymbols);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type ErrorResponse = {
|
||||
|
||||
export function handleRequestError(
|
||||
e: RequestError,
|
||||
githubUrl: URL,
|
||||
logger: NotificationLogger,
|
||||
): boolean {
|
||||
if (e.status !== 422) {
|
||||
@@ -60,9 +61,12 @@ export function handleRequestError(
|
||||
return false;
|
||||
}
|
||||
|
||||
const createBranchURL = `https://github.com/${
|
||||
missingDefaultBranchError.repository
|
||||
}/new/${encodeURIComponent(missingDefaultBranchError.default_branch)}`;
|
||||
const createBranchURL = new URL(
|
||||
`/${
|
||||
missingDefaultBranchError.repository
|
||||
}/new/${encodeURIComponent(missingDefaultBranchError.default_branch)}`,
|
||||
githubUrl,
|
||||
).toString();
|
||||
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
|
||||
10
extensions/ql-vscode/src/variant-analysis/ghec-dr.ts
Normal file
10
extensions/ql-vscode/src/variant-analysis/ghec-dr.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { hasEnterpriseUri, hasGhecDrUri } from "../config";
|
||||
|
||||
/**
|
||||
* Determines whether MRVA should be enabled or not for the current GitHub host.
|
||||
* MRVA is enabled on github.com and GHEC-DR.
|
||||
* This is based on the `github-enterprise.uri` setting.
|
||||
*/
|
||||
export function isVariantAnalysisEnabledForGitHubHost(): boolean {
|
||||
return !hasEnterpriseUri() || hasGhecDrUri();
|
||||
}
|
||||
@@ -204,18 +204,14 @@ async function copyExistingQueryPack(
|
||||
// Also include query files that contain extensible predicates. These query files are not
|
||||
// needed for the query to run, but they are needed for the query pack to pass deep validation
|
||||
// of data extensions.
|
||||
if (
|
||||
await cliServer.cliConstraints.supportsGenerateExtensiblePredicateMetadata()
|
||||
) {
|
||||
const metadata = await cliServer.generateExtensiblePredicateMetadata(
|
||||
qlPackDetails.qlPackRootPath,
|
||||
);
|
||||
metadata.extensible_predicates.forEach((predicate) => {
|
||||
if (predicate.path.endsWith(".ql")) {
|
||||
toCopy.push(join(qlPackDetails.qlPackRootPath, predicate.path));
|
||||
}
|
||||
});
|
||||
}
|
||||
const metadata = await cliServer.generateExtensiblePredicateMetadata(
|
||||
qlPackDetails.qlPackRootPath,
|
||||
);
|
||||
metadata.extensible_predicates.forEach((predicate) => {
|
||||
if (predicate.path.endsWith(".ql")) {
|
||||
toCopy.push(join(qlPackDetails.qlPackRootPath, predicate.path));
|
||||
}
|
||||
});
|
||||
|
||||
[
|
||||
// also copy the lock file (either new name or old name) and the query file itself. These are not included in the packlist.
|
||||
|
||||
@@ -295,10 +295,14 @@ export function getSkippedRepoCount(
|
||||
|
||||
export function getActionsWorkflowRunUrl(
|
||||
variantAnalysis: VariantAnalysis,
|
||||
githubUrl: URL,
|
||||
): string {
|
||||
const {
|
||||
actionsWorkflowRunId,
|
||||
controllerRepo: { fullName },
|
||||
} = variantAnalysis;
|
||||
return `https://github.com/${fullName}/actions/runs/${actionsWorkflowRunId}`;
|
||||
return new URL(
|
||||
`/${fullName}/actions/runs/${actionsWorkflowRunId}`,
|
||||
githubUrl,
|
||||
).toString();
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ import {
|
||||
REPO_STATES_FILENAME,
|
||||
writeRepoStates,
|
||||
} from "./repo-states-store";
|
||||
import { GITHUB_AUTH_PROVIDER_ID } from "../common/vscode/authentication";
|
||||
import { FetchError } from "node-fetch";
|
||||
import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
@@ -97,6 +96,9 @@ import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { findVariantAnalysisQlPackRoot } from "./ql";
|
||||
import { resolveCodeScanningQueryPack } from "./code-scanning-pack";
|
||||
import { isSarifResultsQueryKind } from "../common/query-metadata";
|
||||
import { isVariantAnalysisEnabledForGitHubHost } from "./ghec-dr";
|
||||
import type { VariantAnalysisConfig } from "../config";
|
||||
import { getEnterpriseUri } from "../config";
|
||||
|
||||
const maxRetryCount = 3;
|
||||
|
||||
@@ -156,6 +158,7 @@ export class VariantAnalysisManager
|
||||
private readonly storagePath: string,
|
||||
private readonly variantAnalysisResultsManager: VariantAnalysisResultsManager,
|
||||
private readonly dbManager: DbManager,
|
||||
private readonly config: VariantAnalysisConfig,
|
||||
) {
|
||||
super();
|
||||
this.variantAnalysisMonitor = this.push(
|
||||
@@ -327,6 +330,12 @@ export class VariantAnalysisManager
|
||||
token: CancellationToken,
|
||||
openViewAfterSubmission = true,
|
||||
): Promise<number | undefined> {
|
||||
if (!isVariantAnalysisEnabledForGitHubHost()) {
|
||||
throw new Error(
|
||||
`Multi-repository variant analysis is not enabled for ${getEnterpriseUri()}`,
|
||||
);
|
||||
}
|
||||
|
||||
await saveBeforeStart();
|
||||
|
||||
progress({
|
||||
@@ -418,7 +427,10 @@ export class VariantAnalysisManager
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
// If the error is handled by the handleRequestError function, we don't need to throw
|
||||
if (e instanceof RequestError && handleRequestError(e, this.app.logger)) {
|
||||
if (
|
||||
e instanceof RequestError &&
|
||||
handleRequestError(e, this.config.githubUrl, this.app.logger)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -737,7 +749,7 @@ export class VariantAnalysisManager
|
||||
private async onDidChangeSessions(
|
||||
event: AuthenticationSessionsChangeEvent,
|
||||
): Promise<void> {
|
||||
if (event.provider.id !== GITHUB_AUTH_PROVIDER_ID) {
|
||||
if (event.provider.id !== this.app.credentials.authProviderId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -943,7 +955,10 @@ export class VariantAnalysisManager
|
||||
throw new Error(`No variant analysis with id: ${variantAnalysisId}`);
|
||||
}
|
||||
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysis);
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(
|
||||
variantAnalysis,
|
||||
this.config.githubUrl,
|
||||
);
|
||||
|
||||
await this.app.commands.execute(
|
||||
"vscode.open",
|
||||
|
||||
@@ -23,6 +23,7 @@ import { DisposableObject } from "../common/disposable-object";
|
||||
import { EventEmitter } from "vscode";
|
||||
import { unzipToDirectoryConcurrently } from "../common/unzip-concurrently";
|
||||
import { readRepoTask, writeRepoTask } from "./repo-tasks-store";
|
||||
import type { VariantAnalysisConfig } from "../config";
|
||||
|
||||
type CacheKey = `${number}/${string}`;
|
||||
|
||||
@@ -62,6 +63,7 @@ export class VariantAnalysisResultsManager extends DisposableObject {
|
||||
|
||||
constructor(
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
private readonly config: VariantAnalysisConfig,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
super();
|
||||
@@ -192,7 +194,7 @@ export class VariantAnalysisResultsManager extends DisposableObject {
|
||||
throw new Error("Missing database commit SHA");
|
||||
}
|
||||
|
||||
const fileLinkPrefix = this.createGitHubDotcomFileLinkPrefix(
|
||||
const fileLinkPrefix = this.createGitHubFileLinkPrefix(
|
||||
repoTask.repository.fullName,
|
||||
repoTask.databaseCommitSha,
|
||||
);
|
||||
@@ -283,11 +285,11 @@ export class VariantAnalysisResultsManager extends DisposableObject {
|
||||
return join(variantAnalysisStoragePath, fullName);
|
||||
}
|
||||
|
||||
private createGitHubDotcomFileLinkPrefix(
|
||||
fullName: string,
|
||||
sha: string,
|
||||
): string {
|
||||
return `https://github.com/${fullName}/blob/${sha}`;
|
||||
private createGitHubFileLinkPrefix(fullName: string, sha: string): string {
|
||||
return new URL(
|
||||
`/${fullName}/blob/${sha}`,
|
||||
this.config.githubUrl,
|
||||
).toString();
|
||||
}
|
||||
|
||||
public removeAnalysisResults(variantAnalysis: VariantAnalysis) {
|
||||
|
||||
@@ -36,33 +36,7 @@ describe(ModelTypeDropdown.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("allows changing the type to 'Type' for Ruby when type models are shown", async () => {
|
||||
const method = createMethod();
|
||||
const modeledMethod = createNoneModeledMethod();
|
||||
|
||||
render(
|
||||
<ModelTypeDropdown
|
||||
language={QueryLanguage.Ruby}
|
||||
modeledMethod={modeledMethod}
|
||||
modelPending={false}
|
||||
onChange={onChange}
|
||||
method={method}
|
||||
modelConfig={{
|
||||
...defaultModelConfig,
|
||||
showTypeModels: true,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await userEvent.selectOptions(screen.getByRole("combobox"), "type");
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "type",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not allow changing the type to 'Type' for Ruby when type models are not shown", async () => {
|
||||
it("allows changing the type to 'Type' for Ruby", async () => {
|
||||
const method = createMethod();
|
||||
const modeledMethod = createNoneModeledMethod();
|
||||
|
||||
@@ -77,9 +51,12 @@ describe(ModelTypeDropdown.name, () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByRole("option", { name: "Type" }),
|
||||
).not.toBeInTheDocument();
|
||||
await userEvent.selectOptions(screen.getByRole("combobox"), "type");
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "type",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not allow changing the type to 'Type' for Java", async () => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
[
|
||||
"v2.17.0",
|
||||
"v2.17.3",
|
||||
"v2.16.6",
|
||||
"v2.15.5",
|
||||
"v2.14.6",
|
||||
"v2.13.5",
|
||||
"nightly"
|
||||
]
|
||||
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
depends_on:
|
||||
- files-init
|
||||
files-init:
|
||||
image: alpine:3.19.0
|
||||
image: alpine:3.19.1
|
||||
restart: "no"
|
||||
# Since we're not running the code-server container using the same user as our host user,
|
||||
# we need to set the permissions on the mounted volumes to match the user inside the container.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM codercom/code-server:4.20.0
|
||||
FROM codercom/code-server:4.23.1
|
||||
|
||||
USER root
|
||||
|
||||
@@ -7,7 +7,7 @@ RUN apt-get update \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN wget -q -O /tmp/codeql.zip https://github.com/github/codeql-cli-binaries/releases/download/v2.15.5/codeql-linux64.zip \
|
||||
RUN wget -q -O /tmp/codeql.zip https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql-linux64.zip \
|
||||
&& unzip -q /tmp/codeql.zip -d /opt \
|
||||
&& rm -rf /tmp/codeql.zip
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ function makeTestOctokit(octokit: Octokit): Credentials {
|
||||
"getExistingAccessToken not supported by test credentials",
|
||||
);
|
||||
},
|
||||
authProviderId: "github",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
10
extensions/ql-vscode/test/factories/config.ts
Normal file
10
extensions/ql-vscode/test/factories/config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { VariantAnalysisConfig } from "../../src/config";
|
||||
|
||||
export function createMockVariantAnalysisConfig(): VariantAnalysisConfig {
|
||||
return {
|
||||
controllerRepo: "foo/bar",
|
||||
showSystemDefinedRepositoryLists: true,
|
||||
githubUrl: new URL("https://github.com"),
|
||||
onDidChangeConfiguration: jest.fn(),
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
} from "../../../src/common/github-url-identifier-helper";
|
||||
|
||||
describe("github url identifier helper", () => {
|
||||
const githubUrl = new URL("https://github.com");
|
||||
|
||||
describe("valid GitHub Nwo Or Owner method", () => {
|
||||
it("should return true for valid owner", () => {
|
||||
expect(isValidGitHubOwner("github")).toBe(true);
|
||||
@@ -23,51 +25,96 @@ describe("github url identifier helper", () => {
|
||||
|
||||
describe("getNwoFromGitHubUrl method", () => {
|
||||
it("should handle invalid urls", () => {
|
||||
expect(getNwoFromGitHubUrl("")).toBe(undefined);
|
||||
expect(getNwoFromGitHubUrl("https://ww.github.com/foo/bar")).toBe(
|
||||
expect(getNwoFromGitHubUrl("", githubUrl)).toBe(undefined);
|
||||
expect(
|
||||
getNwoFromGitHubUrl("https://ww.github.com/foo/bar", githubUrl),
|
||||
).toBe(undefined);
|
||||
expect(
|
||||
getNwoFromGitHubUrl("https://tenant.ghe.com/foo/bar", githubUrl),
|
||||
).toBe(undefined);
|
||||
expect(getNwoFromGitHubUrl("https://www.github.com/foo", githubUrl)).toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(getNwoFromGitHubUrl("https://www.github.com/foo")).toBe(undefined);
|
||||
expect(getNwoFromGitHubUrl("foo")).toBe(undefined);
|
||||
expect(getNwoFromGitHubUrl("foo/bar")).toBe(undefined);
|
||||
expect(getNwoFromGitHubUrl("foo", githubUrl)).toBe(undefined);
|
||||
expect(getNwoFromGitHubUrl("foo/bar", githubUrl)).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should handle valid urls", () => {
|
||||
expect(getNwoFromGitHubUrl("github.com/foo/bar")).toBe("foo/bar");
|
||||
expect(getNwoFromGitHubUrl("www.github.com/foo/bar")).toBe("foo/bar");
|
||||
expect(getNwoFromGitHubUrl("https://github.com/foo/bar")).toBe("foo/bar");
|
||||
expect(getNwoFromGitHubUrl("http://github.com/foo/bar")).toBe("foo/bar");
|
||||
expect(getNwoFromGitHubUrl("https://www.github.com/foo/bar")).toBe(
|
||||
expect(getNwoFromGitHubUrl("github.com/foo/bar", githubUrl)).toBe(
|
||||
"foo/bar",
|
||||
);
|
||||
expect(getNwoFromGitHubUrl("https://github.com/foo/bar/sub/pages")).toBe(
|
||||
expect(getNwoFromGitHubUrl("www.github.com/foo/bar", githubUrl)).toBe(
|
||||
"foo/bar",
|
||||
);
|
||||
expect(getNwoFromGitHubUrl("https://github.com/foo/bar", githubUrl)).toBe(
|
||||
"foo/bar",
|
||||
);
|
||||
expect(getNwoFromGitHubUrl("http://github.com/foo/bar", githubUrl)).toBe(
|
||||
"foo/bar",
|
||||
);
|
||||
expect(
|
||||
getNwoFromGitHubUrl("https://www.github.com/foo/bar", githubUrl),
|
||||
).toBe("foo/bar");
|
||||
expect(
|
||||
getNwoFromGitHubUrl("https://github.com/foo/bar/sub/pages", githubUrl),
|
||||
).toBe("foo/bar");
|
||||
expect(
|
||||
getNwoFromGitHubUrl(
|
||||
"https://tenant.ghe.com/foo/bar",
|
||||
new URL("https://tenant.ghe.com"),
|
||||
),
|
||||
).toBe("foo/bar");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getOwnerFromGitHubUrl method", () => {
|
||||
it("should handle invalid urls", () => {
|
||||
expect(getOwnerFromGitHubUrl("")).toBe(undefined);
|
||||
expect(getOwnerFromGitHubUrl("https://ww.github.com/foo/bar")).toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(getOwnerFromGitHubUrl("foo")).toBe(undefined);
|
||||
expect(getOwnerFromGitHubUrl("foo/bar")).toBe(undefined);
|
||||
expect(getOwnerFromGitHubUrl("", githubUrl)).toBe(undefined);
|
||||
expect(
|
||||
getOwnerFromGitHubUrl("https://ww.github.com/foo/bar", githubUrl),
|
||||
).toBe(undefined);
|
||||
expect(
|
||||
getOwnerFromGitHubUrl("https://tenant.ghe.com/foo/bar", githubUrl),
|
||||
).toBe(undefined);
|
||||
expect(getOwnerFromGitHubUrl("foo", githubUrl)).toBe(undefined);
|
||||
expect(getOwnerFromGitHubUrl("foo/bar", githubUrl)).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should handle valid urls", () => {
|
||||
expect(getOwnerFromGitHubUrl("http://github.com/foo/bar")).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("https://github.com/foo/bar")).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("https://www.github.com/foo/bar")).toBe(
|
||||
expect(
|
||||
getOwnerFromGitHubUrl("http://github.com/foo/bar", githubUrl),
|
||||
).toBe("foo");
|
||||
expect(
|
||||
getOwnerFromGitHubUrl("https://github.com/foo/bar", githubUrl),
|
||||
).toBe("foo");
|
||||
expect(
|
||||
getOwnerFromGitHubUrl("https://www.github.com/foo/bar", githubUrl),
|
||||
).toBe("foo");
|
||||
expect(
|
||||
getOwnerFromGitHubUrl(
|
||||
"https://github.com/foo/bar/sub/pages",
|
||||
githubUrl,
|
||||
),
|
||||
).toBe("foo");
|
||||
expect(
|
||||
getOwnerFromGitHubUrl("https://www.github.com/foo", githubUrl),
|
||||
).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("github.com/foo", githubUrl)).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("www.github.com/foo", githubUrl)).toBe(
|
||||
"foo",
|
||||
);
|
||||
expect(
|
||||
getOwnerFromGitHubUrl("https://github.com/foo/bar/sub/pages"),
|
||||
getOwnerFromGitHubUrl(
|
||||
"https://tenant.ghe.com/foo/bar",
|
||||
new URL("https://tenant.ghe.com"),
|
||||
),
|
||||
).toBe("foo");
|
||||
expect(
|
||||
getOwnerFromGitHubUrl(
|
||||
"https://tenant.ghe.com/foo",
|
||||
new URL("https://tenant.ghe.com"),
|
||||
),
|
||||
).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("https://www.github.com/foo")).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("github.com/foo")).toBe("foo");
|
||||
expect(getOwnerFromGitHubUrl("www.github.com/foo")).toBe("foo");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ import { DbManager } from "../../../src/databases/db-manager";
|
||||
import { createDbConfig } from "../../factories/db-config-factories";
|
||||
import { createRemoteUserDefinedListDbItem } from "../../factories/db-item-factories";
|
||||
import { createMockApp } from "../../__mocks__/appMock";
|
||||
import { createMockVariantAnalysisConfig } from "../../factories/config";
|
||||
|
||||
// Note: Although these are "unit tests" (i.e. not integrating with VS Code), they do
|
||||
// test the interaction/"integration" between the DbManager and the DbConfigStore.
|
||||
@@ -46,7 +47,11 @@ describe("db manager", () => {
|
||||
// We don't need to watch changes to the config file in these tests, so we
|
||||
// pass `false` to the dbConfigStore constructor.
|
||||
dbConfigStore = new DbConfigStore(app, false);
|
||||
dbManager = new DbManager(app, dbConfigStore);
|
||||
dbManager = new DbManager(
|
||||
app,
|
||||
dbConfigStore,
|
||||
createMockVariantAnalysisConfig(),
|
||||
);
|
||||
await ensureDir(tempWorkspaceStoragePath);
|
||||
|
||||
dbConfigFilePath = join(
|
||||
|
||||
@@ -10,13 +10,20 @@ import type { ExpandedDbItem } from "../../../src/databases/db-item-expansion";
|
||||
import { ExpandedDbItemKind } from "../../../src/databases/db-item-expansion";
|
||||
import { createRemoteTree } from "../../../src/databases/db-tree-creator";
|
||||
import { createDbConfig } from "../../factories/db-config-factories";
|
||||
import { createMockVariantAnalysisConfig } from "../../factories/config";
|
||||
|
||||
describe("db tree creator", () => {
|
||||
const defaultVariantAnalysisConfig = createMockVariantAnalysisConfig();
|
||||
|
||||
describe("createRemoteTree", () => {
|
||||
it("should build root node and system defined lists", () => {
|
||||
const dbConfig = createDbConfig();
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, []);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
@@ -45,6 +52,24 @@ describe("db tree creator", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("displays empty list when no remote user defined list nodes and system defined lists are disabled", () => {
|
||||
const dbConfig = createDbConfig();
|
||||
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
{
|
||||
...defaultVariantAnalysisConfig,
|
||||
showSystemDefinedRepositoryLists: false,
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
expect(dbTreeRoot.expanded).toBe(false);
|
||||
expect(dbTreeRoot.children.length).toBe(0);
|
||||
});
|
||||
|
||||
it("should create remote user defined list nodes", () => {
|
||||
const dbConfig = createDbConfig({
|
||||
remoteLists: [
|
||||
@@ -59,10 +84,15 @@ describe("db tree creator", () => {
|
||||
],
|
||||
});
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, []);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
expect(dbTreeRoot.children.length).toBe(5);
|
||||
const repositoryListNodes = dbTreeRoot.children.filter(
|
||||
isRemoteUserDefinedListDbItem,
|
||||
);
|
||||
@@ -102,12 +132,76 @@ describe("db tree creator", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows only user defined list nodes when system defined lists are disabled", () => {
|
||||
const dbConfig = createDbConfig({
|
||||
remoteLists: [
|
||||
{
|
||||
name: "my-list-1",
|
||||
repositories: ["owner1/repo1", "owner1/repo2", "owner2/repo1"],
|
||||
},
|
||||
{
|
||||
name: "my-list-2",
|
||||
repositories: ["owner3/repo1", "owner3/repo2", "owner4/repo1"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
{
|
||||
...defaultVariantAnalysisConfig,
|
||||
showSystemDefinedRepositoryLists: false,
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
expect(dbTreeRoot.children.length).toBe(2);
|
||||
expect(dbTreeRoot.children[0]).toEqual({
|
||||
kind: DbItemKind.RemoteUserDefinedList,
|
||||
selected: false,
|
||||
expanded: false,
|
||||
listName: dbConfig.databases.variantAnalysis.repositoryLists[0].name,
|
||||
repos:
|
||||
dbConfig.databases.variantAnalysis.repositoryLists[0].repositories.map(
|
||||
(repo) => ({
|
||||
kind: DbItemKind.RemoteRepo,
|
||||
selected: false,
|
||||
repoFullName: repo,
|
||||
parentListName:
|
||||
dbConfig.databases.variantAnalysis.repositoryLists[0].name,
|
||||
}),
|
||||
),
|
||||
});
|
||||
expect(dbTreeRoot.children[1]).toEqual({
|
||||
kind: DbItemKind.RemoteUserDefinedList,
|
||||
selected: false,
|
||||
expanded: false,
|
||||
listName: dbConfig.databases.variantAnalysis.repositoryLists[1].name,
|
||||
repos:
|
||||
dbConfig.databases.variantAnalysis.repositoryLists[1].repositories.map(
|
||||
(repo) => ({
|
||||
kind: DbItemKind.RemoteRepo,
|
||||
selected: false,
|
||||
repoFullName: repo,
|
||||
parentListName:
|
||||
dbConfig.databases.variantAnalysis.repositoryLists[1].name,
|
||||
}),
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
it("should create remote owner nodes", () => {
|
||||
const dbConfig: DbConfig = createDbConfig({
|
||||
remoteOwners: ["owner1", "owner2"],
|
||||
});
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, []);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
@@ -131,7 +225,11 @@ describe("db tree creator", () => {
|
||||
remoteRepos: ["owner1/repo1", "owner1/repo2", "owner2/repo1"],
|
||||
});
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, []);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
@@ -170,7 +268,11 @@ describe("db tree creator", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, []);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
@@ -191,7 +293,11 @@ describe("db tree creator", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, []);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
@@ -213,7 +319,11 @@ describe("db tree creator", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, []);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
@@ -240,7 +350,11 @@ describe("db tree creator", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, []);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
[],
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
|
||||
@@ -265,7 +379,11 @@ describe("db tree creator", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, expanded);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
expanded,
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
@@ -291,7 +409,11 @@ describe("db tree creator", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const dbTreeRoot = createRemoteTree(dbConfig, expanded);
|
||||
const dbTreeRoot = createRemoteTree(
|
||||
dbConfig,
|
||||
defaultVariantAnalysisConfig,
|
||||
expanded,
|
||||
);
|
||||
|
||||
expect(dbTreeRoot).toBeTruthy();
|
||||
expect(dbTreeRoot.kind).toBe(DbItemKind.RootRemote);
|
||||
|
||||
@@ -92,32 +92,52 @@ describe("getDbItemActions", () => {
|
||||
});
|
||||
|
||||
describe("getGitHubUrl", () => {
|
||||
it("should return the correct url for a remote owner", () => {
|
||||
const githubUrl = new URL("https://github.com");
|
||||
|
||||
it("should return the correct url for a remote owner with github.com", () => {
|
||||
const dbItem = createRemoteOwnerDbItem();
|
||||
|
||||
const actualUrl = getGitHubUrl(dbItem);
|
||||
const actualUrl = getGitHubUrl(dbItem, githubUrl);
|
||||
const expectedUrl = `https://github.com/${dbItem.ownerName}`;
|
||||
|
||||
expect(actualUrl).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it("should return the correct url for a remote repo", () => {
|
||||
it("should return the correct url for a remote owner with GHEC-DR", () => {
|
||||
const dbItem = createRemoteOwnerDbItem();
|
||||
|
||||
const actualUrl = getGitHubUrl(dbItem, new URL("https://tenant.ghe.com"));
|
||||
const expectedUrl = `https://tenant.ghe.com/${dbItem.ownerName}`;
|
||||
|
||||
expect(actualUrl).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it("should return the correct url for a remote repo with github.com", () => {
|
||||
const dbItem = createRemoteRepoDbItem();
|
||||
|
||||
const actualUrl = getGitHubUrl(dbItem);
|
||||
const actualUrl = getGitHubUrl(dbItem, githubUrl);
|
||||
const expectedUrl = `https://github.com/${dbItem.repoFullName}`;
|
||||
|
||||
expect(actualUrl).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it("should return the correct url for a remote repo with GHEC-DR", () => {
|
||||
const dbItem = createRemoteRepoDbItem();
|
||||
|
||||
const actualUrl = getGitHubUrl(dbItem, new URL("https://tenant.ghe.com"));
|
||||
const expectedUrl = `https://tenant.ghe.com/${dbItem.repoFullName}`;
|
||||
|
||||
expect(actualUrl).toEqual(expectedUrl);
|
||||
});
|
||||
|
||||
it("should return undefined for other remote db items", () => {
|
||||
const dbItem0 = createRootRemoteDbItem();
|
||||
const dbItem1 = createRemoteSystemDefinedListDbItem();
|
||||
const dbItem2 = createRemoteUserDefinedListDbItem();
|
||||
|
||||
const actualUrl0 = getGitHubUrl(dbItem0);
|
||||
const actualUrl1 = getGitHubUrl(dbItem1);
|
||||
const actualUrl2 = getGitHubUrl(dbItem2);
|
||||
const actualUrl0 = getGitHubUrl(dbItem0, githubUrl);
|
||||
const actualUrl1 = getGitHubUrl(dbItem1, githubUrl);
|
||||
const actualUrl2 = getGitHubUrl(dbItem2, githubUrl);
|
||||
|
||||
expect(actualUrl0).toBeUndefined();
|
||||
expect(actualUrl1).toBeUndefined();
|
||||
|
||||
@@ -1,82 +1,81 @@
|
||||
import {
|
||||
autoNameExtensionPack,
|
||||
sanitizePackName,
|
||||
formatPackName,
|
||||
parsePackName,
|
||||
validatePackName,
|
||||
} from "../../../src/model-editor/extension-pack-name";
|
||||
|
||||
describe("autoNameExtensionPack", () => {
|
||||
describe("sanitizePackName", () => {
|
||||
const testCases: Array<{
|
||||
name: string;
|
||||
language: string;
|
||||
expected: string;
|
||||
}> = [
|
||||
{
|
||||
name: "github/vscode-codeql",
|
||||
language: "javascript",
|
||||
name: "github/vscode-codeql-javascript",
|
||||
expected: "github/vscode-codeql-javascript",
|
||||
},
|
||||
{
|
||||
name: "vscode-codeql",
|
||||
language: "a",
|
||||
name: "vscode-codeql-a",
|
||||
expected: "pack/vscode-codeql-a",
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
language: "java",
|
||||
name: "b-java",
|
||||
expected: "pack/b-java",
|
||||
},
|
||||
{
|
||||
name: "a/b",
|
||||
language: "csharp",
|
||||
name: "a/b-csharp",
|
||||
expected: "a/b-csharp",
|
||||
},
|
||||
{
|
||||
name: "-/b",
|
||||
language: "csharp",
|
||||
name: "-/b-csharp",
|
||||
expected: "pack/b-csharp",
|
||||
},
|
||||
{
|
||||
name: "a/b/c/d",
|
||||
language: "csharp",
|
||||
name: "a/b/c/d-csharp",
|
||||
expected: "a/b-c-d-csharp",
|
||||
},
|
||||
{
|
||||
name: "JAVA/CodeQL",
|
||||
language: "csharp",
|
||||
name: "JAVA/CodeQL-csharp",
|
||||
expected: "java/codeql-csharp",
|
||||
},
|
||||
{
|
||||
name: "my new pack",
|
||||
language: "swift",
|
||||
name: "my new pack-swift",
|
||||
expected: "pack/my-new-pack-swift",
|
||||
},
|
||||
{
|
||||
name: "gïthub/vscode-codeql",
|
||||
language: "javascript",
|
||||
name: "gïthub/vscode-codeql-javascript",
|
||||
expected: "gthub/vscode-codeql-javascript",
|
||||
},
|
||||
{
|
||||
name: "a/b-",
|
||||
language: "csharp",
|
||||
name: "a/b-csharp",
|
||||
expected: "a/b-csharp",
|
||||
},
|
||||
{
|
||||
name: "-a-/b",
|
||||
language: "ruby",
|
||||
name: "-a-/b-ruby",
|
||||
expected: "a/b-ruby",
|
||||
},
|
||||
{
|
||||
name: "a/b--d--e-d-",
|
||||
language: "csharp",
|
||||
name: "a/b--d--e-d-csharp",
|
||||
expected: "a/b-d-e-d-csharp",
|
||||
},
|
||||
{
|
||||
name: "/github/vscode-codeql",
|
||||
expected: "github/vscode-codeql",
|
||||
},
|
||||
{
|
||||
name: "github/vscode-codeql/",
|
||||
expected: "github/vscode-codeql",
|
||||
},
|
||||
{
|
||||
name: "///github/vscode-codeql///",
|
||||
expected: "github/vscode-codeql",
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)(
|
||||
"$name with $language = $expected",
|
||||
({ name, language, expected }) => {
|
||||
const result = autoNameExtensionPack(name, language);
|
||||
({ name, expected }) => {
|
||||
const result = sanitizePackName(name);
|
||||
expect(result).not.toBeUndefined();
|
||||
if (!result) {
|
||||
return;
|
||||
|
||||
@@ -4,8 +4,6 @@ import { ruby } from "../../../../../src/model-editor/languages/ruby";
|
||||
import { createMockLogger } from "../../../../__mocks__/loggerMock";
|
||||
import type { ModeledMethod } from "../../../../../src/model-editor/modeled-method";
|
||||
import { EndpointType } from "../../../../../src/model-editor/method";
|
||||
import { Mode } from "../../../../../src/model-editor/shared/mode";
|
||||
import { defaultModelConfig } from "../../../../../src/model-editor/languages";
|
||||
|
||||
describe("parseGenerateModelResults", () => {
|
||||
it("should return the results", async () => {
|
||||
@@ -78,13 +76,6 @@ describe("parseGenerateModelResults", () => {
|
||||
bqrs,
|
||||
ruby,
|
||||
createMockLogger(),
|
||||
{
|
||||
mode: Mode.Framework,
|
||||
config: {
|
||||
...defaultModelConfig,
|
||||
showTypeModels: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(result).toEqual([
|
||||
{
|
||||
|
||||
@@ -151,13 +151,29 @@ describe("isVariantAnalysisComplete", () => {
|
||||
});
|
||||
|
||||
describe("getActionsWorkflowRunUrl", () => {
|
||||
it("should get the run url", () => {
|
||||
it("should get the run url on github.com", () => {
|
||||
const variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(variantAnalysis);
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(
|
||||
variantAnalysis,
|
||||
new URL("https://github.com"),
|
||||
);
|
||||
|
||||
expect(actionsWorkflowRunUrl).toBe(
|
||||
`https://github.com/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("should get the run url on GHEC-DR", () => {
|
||||
const variantAnalysis = createMockVariantAnalysis({});
|
||||
|
||||
const actionsWorkflowRunUrl = getActionsWorkflowRunUrl(
|
||||
variantAnalysis,
|
||||
new URL("https://tenant.ghe.com"),
|
||||
);
|
||||
|
||||
expect(actionsWorkflowRunUrl).toBe(
|
||||
`https://tenant.ghe.com/${variantAnalysis.controllerRepo.fullName}/actions/runs/${variantAnalysis.actionsWorkflowRunId}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,13 +4,14 @@ import { handleRequestError } from "../../../src/variant-analysis/custom-errors"
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
describe("handleRequestError", () => {
|
||||
const githubUrl = new URL("https://github.com");
|
||||
const logger = createMockLogger();
|
||||
|
||||
it("returns false when handling a non-422 error", () => {
|
||||
const e = mockRequestError(404, {
|
||||
message: "Not Found",
|
||||
});
|
||||
expect(handleRequestError(e, logger)).toBe(false);
|
||||
expect(handleRequestError(e, githubUrl, logger)).toBe(false);
|
||||
expect(logger.showErrorMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -19,13 +20,13 @@ describe("handleRequestError", () => {
|
||||
message:
|
||||
"Unable to trigger a variant analysis. None of the requested repositories could be found.",
|
||||
});
|
||||
expect(handleRequestError(e, logger)).toBe(false);
|
||||
expect(handleRequestError(e, githubUrl, logger)).toBe(false);
|
||||
expect(logger.showErrorMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns false when handling an error without response body", () => {
|
||||
const e = mockRequestError(422, undefined);
|
||||
expect(handleRequestError(e, logger)).toBe(false);
|
||||
expect(handleRequestError(e, githubUrl, logger)).toBe(false);
|
||||
expect(logger.showErrorMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -42,7 +43,7 @@ describe("handleRequestError", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(handleRequestError(e, logger)).toBe(false);
|
||||
expect(handleRequestError(e, githubUrl, logger)).toBe(false);
|
||||
expect(logger.showErrorMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -58,7 +59,7 @@ describe("handleRequestError", () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(handleRequestError(e, logger)).toBe(false);
|
||||
expect(handleRequestError(e, githubUrl, logger)).toBe(false);
|
||||
expect(logger.showErrorMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -75,7 +76,7 @@ describe("handleRequestError", () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(handleRequestError(e, logger)).toBe(false);
|
||||
expect(handleRequestError(e, githubUrl, logger)).toBe(false);
|
||||
expect(logger.showErrorMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -92,11 +93,11 @@ describe("handleRequestError", () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(handleRequestError(e, logger)).toBe(false);
|
||||
expect(handleRequestError(e, githubUrl, logger)).toBe(false);
|
||||
expect(logger.showErrorMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows notification when handling a missing default branch error", () => {
|
||||
it("shows notification when handling a missing default branch error with github.com URL", () => {
|
||||
const e = mockRequestError(422, {
|
||||
message:
|
||||
"Variant analysis failed because controller repository github/pickles does not have a branch 'main'. Please create a 'main' branch in the repository and re-run the variant analysis.",
|
||||
@@ -110,11 +111,33 @@ describe("handleRequestError", () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(handleRequestError(e, logger)).toBe(true);
|
||||
expect(handleRequestError(e, githubUrl, logger)).toBe(true);
|
||||
expect(logger.showErrorMessage).toHaveBeenCalledWith(
|
||||
"Variant analysis failed because the controller repository github/pickles does not have a branch 'main'. Please create a 'main' branch by clicking [here](https://github.com/github/pickles/new/main) and re-run the variant analysis query.",
|
||||
);
|
||||
});
|
||||
|
||||
it("shows notification when handling a missing default branch error with GHEC-DR URL", () => {
|
||||
const e = mockRequestError(422, {
|
||||
message:
|
||||
"Variant analysis failed because controller repository github/pickles does not have a branch 'main'. Please create a 'main' branch in the repository and re-run the variant analysis.",
|
||||
errors: [
|
||||
{
|
||||
resource: "Repository",
|
||||
field: "default_branch",
|
||||
code: "missing",
|
||||
repository: "github/pickles",
|
||||
default_branch: "main",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(
|
||||
handleRequestError(e, new URL("https://tenant.ghe.com"), logger),
|
||||
).toBe(true);
|
||||
expect(logger.showErrorMessage).toHaveBeenCalledWith(
|
||||
"Variant analysis failed because the controller repository github/pickles does not have a branch 'main'. Please create a 'main' branch by clicking [here](https://tenant.ghe.com/github/pickles/new/main) and re-run the variant analysis query.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function mockRequestError(status: number, body: any): RequestError {
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
writeRepoStates,
|
||||
} from "../../../../src/variant-analysis/repo-states-store";
|
||||
import { permissiveFilterSortState } from "../../../unit-tests/variant-analysis-filter-sort.test";
|
||||
import { createMockVariantAnalysisConfig } from "../../../factories/config";
|
||||
|
||||
// up to 3 minutes per test
|
||||
jest.setTimeout(3 * 60 * 1000);
|
||||
@@ -72,9 +73,15 @@ describe("Variant Analysis Manager", () => {
|
||||
const extension = await getActivatedExtension();
|
||||
const cli = mockedObject<CodeQLCliServer>({});
|
||||
app = new ExtensionApp(extension.ctx);
|
||||
const dbManager = new DbManager(app, new DbConfigStore(app));
|
||||
const dbManager = new DbManager(
|
||||
app,
|
||||
new DbConfigStore(app),
|
||||
createMockVariantAnalysisConfig(),
|
||||
);
|
||||
const variantAnalysisConfig = createMockVariantAnalysisConfig();
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cli,
|
||||
variantAnalysisConfig,
|
||||
extLogger,
|
||||
);
|
||||
variantAnalysisManager = new VariantAnalysisManager(
|
||||
@@ -83,6 +90,7 @@ describe("Variant Analysis Manager", () => {
|
||||
storagePath,
|
||||
variantAnalysisResultsManager,
|
||||
dbManager,
|
||||
variantAnalysisConfig,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
VariantAnalysisScannedRepositoryResult,
|
||||
} from "../../../../src/variant-analysis/shared/variant-analysis";
|
||||
import { mockedObject } from "../../utils/mocking.helpers";
|
||||
import { createMockVariantAnalysisConfig } from "../../../factories/config";
|
||||
|
||||
jest.setTimeout(10_000);
|
||||
|
||||
@@ -27,8 +28,10 @@ describe(VariantAnalysisResultsManager.name, () => {
|
||||
variantAnalysisId = faker.number.int();
|
||||
|
||||
const cli = mockedObject<CodeQLCliServer>({});
|
||||
const variantAnalysisConfig = createMockVariantAnalysisConfig();
|
||||
variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cli,
|
||||
variantAnalysisConfig,
|
||||
extLogger,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@ import { homedir, tmpdir } from "os";
|
||||
import { mkdir, rm } from "fs-extra";
|
||||
import { nanoid } from "nanoid";
|
||||
import { QueryLanguage } from "../../../../src/common/query-language";
|
||||
import { defaultModelConfig } from "../../../../src/model-editor/languages";
|
||||
|
||||
const dummyExtensionPackContents = `
|
||||
name: dummy/pack
|
||||
@@ -56,6 +55,12 @@ describe("modeled-method-fs", () => {
|
||||
let cli: CodeQLCliServer;
|
||||
|
||||
beforeEach(async () => {
|
||||
if (!process.env.TEST_CODEQL_PATH) {
|
||||
fail(
|
||||
"TEST_CODEQL_PATH environment variable not set. It should point to the absolute path to a checkout of the codeql repository.",
|
||||
);
|
||||
}
|
||||
|
||||
// On windows, make sure to use a temp directory that isn't an alias and therefore won't be canonicalised by CodeQL.
|
||||
// The tmp package doesn't support this, so we have to do it manually.
|
||||
// See https://github.com/github/vscode-codeql/pull/2605 for more context.
|
||||
@@ -74,11 +79,16 @@ describe("modeled-method-fs", () => {
|
||||
name: "workspace",
|
||||
index: 0,
|
||||
};
|
||||
const codeqlWorkspaceFolder = {
|
||||
uri: Uri.file(process.env.TEST_CODEQL_PATH),
|
||||
name: "ql",
|
||||
index: 1,
|
||||
};
|
||||
workspacePath = workspaceFolder.uri.fsPath;
|
||||
mkdirSync(workspacePath);
|
||||
jest
|
||||
.spyOn(workspace, "workspaceFolders", "get")
|
||||
.mockReturnValue([workspaceFolder]);
|
||||
.mockReturnValue([workspaceFolder, codeqlWorkspaceFolder]);
|
||||
|
||||
const extension = await getActivatedExtension();
|
||||
cli = extension.cliServer;
|
||||
@@ -136,11 +146,7 @@ describe("modeled-method-fs", () => {
|
||||
it("should return the empty set when the extension pack is empty", async () => {
|
||||
const extensionPackPath = writeExtensionPackFiles("extension-pack", []);
|
||||
|
||||
const modelFiles = await listModelFiles(
|
||||
extensionPackPath,
|
||||
cli,
|
||||
defaultModelConfig,
|
||||
);
|
||||
const modelFiles = await listModelFiles(extensionPackPath, cli);
|
||||
expect(modelFiles).toEqual(new Set());
|
||||
});
|
||||
|
||||
@@ -150,11 +156,7 @@ describe("modeled-method-fs", () => {
|
||||
"library2.model.yml",
|
||||
]);
|
||||
|
||||
const modelFiles = await listModelFiles(
|
||||
extensionPackPath,
|
||||
cli,
|
||||
defaultModelConfig,
|
||||
);
|
||||
const modelFiles = await listModelFiles(extensionPackPath, cli);
|
||||
expect(modelFiles).toEqual(
|
||||
new Set([
|
||||
join("models", "library1.model.yml"),
|
||||
@@ -163,18 +165,14 @@ describe("modeled-method-fs", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should ignore generated type models when type models are hidden", async () => {
|
||||
it("should ignore generated models", async () => {
|
||||
const extensionPackPath = writeExtensionPackFiles("extension-pack", [
|
||||
"library1.model.yml",
|
||||
"library2.model.yml",
|
||||
"library.model.generated.yml",
|
||||
]);
|
||||
|
||||
const modelFiles = await listModelFiles(
|
||||
extensionPackPath,
|
||||
cli,
|
||||
defaultModelConfig,
|
||||
);
|
||||
const modelFiles = await listModelFiles(extensionPackPath, cli);
|
||||
expect(modelFiles).toEqual(
|
||||
new Set([
|
||||
join("models", "library1.model.yml"),
|
||||
@@ -183,37 +181,13 @@ describe("modeled-method-fs", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should include generated type models when type models are shown", async () => {
|
||||
const extensionPackPath = writeExtensionPackFiles("extension-pack", [
|
||||
"library1.model.yml",
|
||||
"library2.model.yml",
|
||||
"library.model.generated.yml",
|
||||
]);
|
||||
|
||||
const modelFiles = await listModelFiles(extensionPackPath, cli, {
|
||||
...defaultModelConfig,
|
||||
showTypeModels: true,
|
||||
});
|
||||
expect(modelFiles).toEqual(
|
||||
new Set([
|
||||
join("models", "library.model.generated.yml"),
|
||||
join("models", "library1.model.yml"),
|
||||
join("models", "library2.model.yml"),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("should ignore model files from other extension packs", async () => {
|
||||
const extensionPackPath = writeExtensionPackFiles("extension-pack", [
|
||||
"library1.model.yml",
|
||||
]);
|
||||
writeExtensionPackFiles("another-extension-pack", ["library2.model.yml"]);
|
||||
|
||||
const modelFiles = await listModelFiles(
|
||||
extensionPackPath,
|
||||
cli,
|
||||
defaultModelConfig,
|
||||
);
|
||||
const modelFiles = await listModelFiles(extensionPackPath, cli);
|
||||
expect(modelFiles).toEqual(
|
||||
new Set([join("models", "library1.model.yml")]),
|
||||
);
|
||||
@@ -230,7 +204,6 @@ describe("modeled-method-fs", () => {
|
||||
makeExtensionPack(extensionPackPath),
|
||||
QueryLanguage.Java,
|
||||
cli,
|
||||
defaultModelConfig,
|
||||
extLogger,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { CancellationTokenSource, commands, window, Uri } from "vscode";
|
||||
import {
|
||||
CancellationTokenSource,
|
||||
commands,
|
||||
window,
|
||||
Uri,
|
||||
ConfigurationTarget,
|
||||
} from "vscode";
|
||||
import { extLogger } from "../../../../src/common/logging/vscode";
|
||||
import { setRemoteControllerRepo } from "../../../../src/config";
|
||||
import {
|
||||
VSCODE_GITHUB_ENTERPRISE_URI_SETTING,
|
||||
setRemoteControllerRepo,
|
||||
} from "../../../../src/config";
|
||||
import * as ghApiClient from "../../../../src/variant-analysis/gh-api/gh-api-client";
|
||||
import { isAbsolute, join } from "path";
|
||||
|
||||
@@ -27,6 +36,7 @@ import type { QlPackLockFile } from "../../../../src/packaging/qlpack-lock-file"
|
||||
//import { expect } from "@jest/globals";
|
||||
import "../../../matchers/toExistInCodeQLPack";
|
||||
import type { QlPackDetails } from "../../../../src/variant-analysis/ql-pack-details";
|
||||
import { createMockVariantAnalysisConfig } from "../../../factories/config";
|
||||
|
||||
describe("Variant Analysis Manager", () => {
|
||||
let cli: CodeQLCliServer;
|
||||
@@ -41,9 +51,15 @@ describe("Variant Analysis Manager", () => {
|
||||
const extension = await getActivatedExtension();
|
||||
cli = extension.cliServer;
|
||||
const app = new ExtensionApp(extension.ctx);
|
||||
const dbManager = new DbManager(app, new DbConfigStore(app));
|
||||
const dbManager = new DbManager(
|
||||
app,
|
||||
new DbConfigStore(app),
|
||||
createMockVariantAnalysisConfig(),
|
||||
);
|
||||
const variantAnalysisConfig = createMockVariantAnalysisConfig();
|
||||
const variantAnalysisResultsManager = new VariantAnalysisResultsManager(
|
||||
cli,
|
||||
variantAnalysisConfig,
|
||||
extLogger,
|
||||
);
|
||||
variantAnalysisManager = new VariantAnalysisManager(
|
||||
@@ -52,6 +68,7 @@ describe("Variant Analysis Manager", () => {
|
||||
storagePath,
|
||||
variantAnalysisResultsManager,
|
||||
dbManager,
|
||||
variantAnalysisConfig,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -99,6 +116,32 @@ describe("Variant Analysis Manager", () => {
|
||||
await setRemoteControllerRepo("github/vscode-codeql");
|
||||
});
|
||||
|
||||
it("fails if MRVA is not supported for this GHE URI", async () => {
|
||||
await VSCODE_GITHUB_ENTERPRISE_URI_SETTING.updateValue(
|
||||
"https://github.example.com",
|
||||
ConfigurationTarget.Global,
|
||||
);
|
||||
|
||||
const qlPackDetails: QlPackDetails = {
|
||||
queryFiles: [getFileOrDir("data-remote-qlpack/in-pack.ql")],
|
||||
qlPackRootPath: getFileOrDir("data-remote-qlpack"),
|
||||
qlPackFilePath: getFileOrDir("data-remote-qlpack/qlpack.yml"),
|
||||
language: QueryLanguage.Javascript,
|
||||
};
|
||||
|
||||
await expect(
|
||||
variantAnalysisManager.runVariantAnalysis(
|
||||
qlPackDetails,
|
||||
progress,
|
||||
cancellationTokenSource.token,
|
||||
),
|
||||
).rejects.toThrow(
|
||||
new Error(
|
||||
"Multi-repository variant analysis is not enabled for https://github.example.com/",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("should run a variant analysis that is part of a qlpack", async () => {
|
||||
const filePath = getFileOrDir("data-remote-qlpack/in-pack.ql");
|
||||
const qlPackRootPath = getFileOrDir("data-remote-qlpack");
|
||||
@@ -295,15 +338,6 @@ describe("Variant Analysis Manager", () => {
|
||||
// Test running core java queries to ensure that we can compile queries in packs
|
||||
// that contain queries with extensible predicates
|
||||
it("should run a remote query that is part of the java pack", async () => {
|
||||
if (
|
||||
!(await cli.cliConstraints.supportsGenerateExtensiblePredicateMetadata())
|
||||
) {
|
||||
console.log(
|
||||
`Skipping test because generating extensible predicate metadata was only introduced in CLI version ${CliVersionConstraint.CLI_VERSION_WITH_EXTENSIBLE_PREDICATE_METADATA}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!process.env.TEST_CODEQL_PATH) {
|
||||
fail(
|
||||
"TEST_CODEQL_PATH environment variable not set. It should point to the absolute path to a checkout of the codeql repository.",
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { workspace } from "vscode";
|
||||
import { ConfigurationTarget, workspace } from "vscode";
|
||||
|
||||
import type { ConfigListener } from "../../../src/config";
|
||||
import {
|
||||
CliConfigListener,
|
||||
QueryHistoryConfigListener,
|
||||
QueryServerConfigListener,
|
||||
VSCODE_GITHUB_ENTERPRISE_URI_SETTING,
|
||||
getEnterpriseUri,
|
||||
hasEnterpriseUri,
|
||||
hasGhecDrUri,
|
||||
} from "../../../src/config";
|
||||
import { vscodeGetConfigurationMock } from "../test-config";
|
||||
|
||||
@@ -126,3 +130,49 @@ describe("config listeners", () => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
});
|
||||
|
||||
describe("enterprise URI", () => {
|
||||
it("detects no enterprise URI when config value is not set", async () => {
|
||||
expect(getEnterpriseUri()).toBeUndefined();
|
||||
expect(hasEnterpriseUri()).toBe(false);
|
||||
expect(hasGhecDrUri()).toBe(false);
|
||||
});
|
||||
|
||||
it("detects no enterprise URI when config value is set to an invalid value", async () => {
|
||||
await VSCODE_GITHUB_ENTERPRISE_URI_SETTING.updateValue(
|
||||
"invalid-uri",
|
||||
ConfigurationTarget.Global,
|
||||
);
|
||||
expect(getEnterpriseUri()).toBeUndefined();
|
||||
expect(hasEnterpriseUri()).toBe(false);
|
||||
expect(hasGhecDrUri()).toBe(false);
|
||||
});
|
||||
|
||||
it("detects an enterprise URI when config value is set to a GHES URI", async () => {
|
||||
await VSCODE_GITHUB_ENTERPRISE_URI_SETTING.updateValue(
|
||||
"https://github.example.com",
|
||||
ConfigurationTarget.Global,
|
||||
);
|
||||
expect(getEnterpriseUri()?.toString()).toBe("https://github.example.com/");
|
||||
expect(hasEnterpriseUri()).toBe(true);
|
||||
expect(hasGhecDrUri()).toBe(false);
|
||||
});
|
||||
|
||||
it("detects a GHEC-DR URI when config value is set to a GHEC-DR URI", async () => {
|
||||
await VSCODE_GITHUB_ENTERPRISE_URI_SETTING.updateValue(
|
||||
"https://example.ghe.com",
|
||||
ConfigurationTarget.Global,
|
||||
);
|
||||
expect(getEnterpriseUri()?.toString()).toBe("https://example.ghe.com/");
|
||||
expect(hasEnterpriseUri()).toBe(true);
|
||||
expect(hasGhecDrUri()).toBe(true);
|
||||
});
|
||||
|
||||
it("Upgrades HTTP URIs to HTTPS", async () => {
|
||||
await VSCODE_GITHUB_ENTERPRISE_URI_SETTING.updateValue(
|
||||
"http://example.ghe.com",
|
||||
ConfigurationTarget.Global,
|
||||
);
|
||||
expect(getEnterpriseUri()?.toString()).toBe("https://example.ghe.com/");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ExtensionApp } from "../../../../src/common/vscode/extension-app";
|
||||
import { createMockExtensionContext } from "../../../factories/extension-context";
|
||||
import { createDbConfig } from "../../../factories/db-config-factories";
|
||||
import { setRemoteControllerRepo } from "../../../../src/config";
|
||||
import { createMockVariantAnalysisConfig } from "../../../factories/config";
|
||||
|
||||
describe("db panel rendering nodes", () => {
|
||||
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
|
||||
@@ -35,7 +36,11 @@ describe("db panel rendering nodes", () => {
|
||||
const app = new ExtensionApp(extensionContext);
|
||||
|
||||
dbConfigStore = new DbConfigStore(app, false);
|
||||
dbManager = new DbManager(app, dbConfigStore);
|
||||
dbManager = new DbManager(
|
||||
app,
|
||||
dbConfigStore,
|
||||
createMockVariantAnalysisConfig(),
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ExtensionApp } from "../../../../src/common/vscode/extension-app";
|
||||
import { createMockExtensionContext } from "../../../factories/extension-context";
|
||||
import { createDbConfig } from "../../../factories/db-config-factories";
|
||||
import { setRemoteControllerRepo } from "../../../../src/config";
|
||||
import { createMockVariantAnalysisConfig } from "../../../factories/config";
|
||||
|
||||
describe("db panel", () => {
|
||||
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
|
||||
@@ -34,7 +35,11 @@ describe("db panel", () => {
|
||||
const app = new ExtensionApp(extensionContext);
|
||||
|
||||
dbConfigStore = new DbConfigStore(app, false);
|
||||
dbManager = new DbManager(app, dbConfigStore);
|
||||
dbManager = new DbManager(
|
||||
app,
|
||||
dbConfigStore,
|
||||
createMockVariantAnalysisConfig(),
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -140,7 +140,8 @@ describe("local databases", () => {
|
||||
},
|
||||
]);
|
||||
expect(onDidChangeDatabaseItem).toHaveBeenCalledWith({
|
||||
item: undefined,
|
||||
fullRefresh: true,
|
||||
item: mockDbItem,
|
||||
kind: DatabaseEventKind.Add,
|
||||
});
|
||||
|
||||
@@ -152,7 +153,8 @@ describe("local databases", () => {
|
||||
expect((databaseManager as any)._databaseItems).toEqual([]);
|
||||
expect(updateSpy).toHaveBeenCalledWith("databaseList", []);
|
||||
expect(onDidChangeDatabaseItem).toHaveBeenCalledWith({
|
||||
item: undefined,
|
||||
fullRefresh: true,
|
||||
item: mockDbItem,
|
||||
kind: DatabaseEventKind.Remove,
|
||||
});
|
||||
});
|
||||
@@ -175,7 +177,8 @@ describe("local databases", () => {
|
||||
]);
|
||||
|
||||
expect(onDidChangeDatabaseItem).toHaveBeenCalledWith({
|
||||
item: undefined,
|
||||
fullRefresh: true,
|
||||
item: mockDbItem,
|
||||
kind: DatabaseEventKind.Rename,
|
||||
});
|
||||
});
|
||||
@@ -198,7 +201,8 @@ describe("local databases", () => {
|
||||
]);
|
||||
|
||||
const mockEvent = {
|
||||
item: undefined,
|
||||
fullRefresh: true,
|
||||
item: mockDbItem,
|
||||
kind: DatabaseEventKind.Add,
|
||||
};
|
||||
expect(onDidChangeDatabaseItem).toHaveBeenCalledWith(mockEvent);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ConfigurationTarget } from "vscode";
|
||||
import { VSCODE_GITHUB_ENTERPRISE_URI_SETTING } from "../../../../src/config";
|
||||
import { isVariantAnalysisEnabledForGitHubHost } from "../../../../src/variant-analysis/ghec-dr";
|
||||
|
||||
describe("checkVariantAnalysisEnabled", () => {
|
||||
it("returns true when no enterprise URI is set", async () => {
|
||||
expect(isVariantAnalysisEnabledForGitHubHost()).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when GHES enterprise URI is set", async () => {
|
||||
await VSCODE_GITHUB_ENTERPRISE_URI_SETTING.updateValue(
|
||||
"https://github.example.com",
|
||||
ConfigurationTarget.Global,
|
||||
);
|
||||
expect(isVariantAnalysisEnabledForGitHubHost()).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true when a GHEC-DR URI is set", async () => {
|
||||
await VSCODE_GITHUB_ENTERPRISE_URI_SETTING.updateValue(
|
||||
"https://example.ghe.com",
|
||||
ConfigurationTarget.Global,
|
||||
);
|
||||
expect(isVariantAnalysisEnabledForGitHubHost()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,7 @@ import { ExtensionApp } from "../../../../src/common/vscode/extension-app";
|
||||
import { createMockExtensionContext } from "../../../factories/extension-context";
|
||||
import { createDbConfig } from "../../../factories/db-config-factories";
|
||||
import { setRemoteControllerRepo } from "../../../../src/config";
|
||||
import { createMockVariantAnalysisConfig } from "../../../factories/config";
|
||||
|
||||
describe("db panel selection", () => {
|
||||
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
|
||||
@@ -36,7 +37,11 @@ describe("db panel selection", () => {
|
||||
const app = new ExtensionApp(extensionContext);
|
||||
|
||||
dbConfigStore = new DbConfigStore(app, false);
|
||||
dbManager = new DbManager(app, dbConfigStore);
|
||||
dbManager = new DbManager(
|
||||
app,
|
||||
dbConfigStore,
|
||||
createMockVariantAnalysisConfig(),
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -37,6 +37,9 @@ describe("pickExtensionPack", () => {
|
||||
let workspaceFoldersSpy: jest.SpyInstance;
|
||||
let additionalPacks: string[];
|
||||
let workspaceFolder: WorkspaceFolder;
|
||||
|
||||
let getPackLocation: jest.MockedFunction<ModelConfig["getPackLocation"]>;
|
||||
let getPackName: jest.MockedFunction<ModelConfig["getPackName"]>;
|
||||
let modelConfig: ModelConfig;
|
||||
|
||||
const logger = createMockLogger();
|
||||
@@ -74,13 +77,20 @@ describe("pickExtensionPack", () => {
|
||||
.spyOn(workspace, "workspaceFolders", "get")
|
||||
.mockReturnValue([workspaceFolder]);
|
||||
|
||||
getPackLocation = jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(language, { name }) => `.github/codeql/extensions/${name}-${language}`,
|
||||
);
|
||||
getPackName = jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(language, { name, owner }) => `${owner}/${name}-${language}`,
|
||||
);
|
||||
|
||||
modelConfig = mockedObject<ModelConfig>({
|
||||
getPackLocation: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(language, { name }) =>
|
||||
`.github/codeql/extensions/${name}-${language}`,
|
||||
),
|
||||
getPackLocation,
|
||||
getPackName,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -178,6 +188,12 @@ describe("pickExtensionPack", () => {
|
||||
name: "vscode-codeql",
|
||||
owner: "github",
|
||||
});
|
||||
expect(modelConfig.getPackName).toHaveBeenCalledWith("java", {
|
||||
database: "github/vscode-codeql",
|
||||
language: "java",
|
||||
name: "vscode-codeql",
|
||||
owner: "github",
|
||||
});
|
||||
|
||||
expect(
|
||||
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
||||
@@ -195,9 +211,7 @@ describe("pickExtensionPack", () => {
|
||||
it("creates a new extension pack when absolute custom pack location is set in config", async () => {
|
||||
const packLocation = join(Uri.file(tmpDir).fsPath, "java/ql/lib");
|
||||
|
||||
const modelConfig = mockedObject<ModelConfig>({
|
||||
getPackLocation: jest.fn().mockReturnValue(packLocation),
|
||||
});
|
||||
getPackLocation.mockReturnValue(packLocation);
|
||||
|
||||
const cliServer = mockCliServer({});
|
||||
|
||||
@@ -246,11 +260,7 @@ describe("pickExtensionPack", () => {
|
||||
it("creates a new extension pack when relative custom pack location is set in config", async () => {
|
||||
const packLocation = join(Uri.file(tmpDir).fsPath, "java/ql/lib");
|
||||
|
||||
const modelConfig = mockedObject<ModelConfig>({
|
||||
getPackLocation: jest
|
||||
.fn()
|
||||
.mockImplementation((language) => `${language}/ql/lib`),
|
||||
});
|
||||
getPackLocation.mockImplementation((language) => `${language}/ql/lib`);
|
||||
|
||||
const cliServer = mockCliServer({});
|
||||
|
||||
@@ -296,6 +306,120 @@ describe("pickExtensionPack", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a new extension pack when valid custom pack name is set in config", async () => {
|
||||
const packName = "codeql/java-extensions";
|
||||
const packLocation = join(
|
||||
Uri.file(tmpDir).fsPath,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
"vscode-codeql-java",
|
||||
);
|
||||
|
||||
getPackName.mockImplementation(
|
||||
(language) => `codeql/${language}-extensions`,
|
||||
);
|
||||
|
||||
const cliServer = mockCliServer({});
|
||||
|
||||
expect(
|
||||
await pickExtensionPack(
|
||||
cliServer,
|
||||
databaseItem,
|
||||
modelConfig,
|
||||
logger,
|
||||
progress,
|
||||
token,
|
||||
maxStep,
|
||||
),
|
||||
).toEqual({
|
||||
path: packLocation,
|
||||
yamlPath: join(packLocation, "codeql-pack.yml"),
|
||||
name: packName,
|
||||
version: "0.0.0",
|
||||
language: "java",
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
});
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||
expect(modelConfig.getPackLocation).toHaveBeenCalledWith("java", {
|
||||
database: "github/vscode-codeql",
|
||||
language: "java",
|
||||
name: "vscode-codeql",
|
||||
owner: "github",
|
||||
});
|
||||
|
||||
expect(
|
||||
loadYaml(await readFile(join(packLocation, "codeql-pack.yml"), "utf8")),
|
||||
).toEqual({
|
||||
name: packName,
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a new extension pack when invalid custom pack name is set in config", async () => {
|
||||
const packName = "pack/java-extensions";
|
||||
const packLocation = join(
|
||||
Uri.file(tmpDir).fsPath,
|
||||
".github",
|
||||
"codeql",
|
||||
"extensions",
|
||||
"vscode-codeql-java",
|
||||
);
|
||||
|
||||
getPackName.mockImplementation((language) => `${language} Extensions`);
|
||||
|
||||
const cliServer = mockCliServer({});
|
||||
|
||||
expect(
|
||||
await pickExtensionPack(
|
||||
cliServer,
|
||||
databaseItem,
|
||||
modelConfig,
|
||||
logger,
|
||||
progress,
|
||||
token,
|
||||
maxStep,
|
||||
),
|
||||
).toEqual({
|
||||
path: packLocation,
|
||||
yamlPath: join(packLocation, "codeql-pack.yml"),
|
||||
name: packName,
|
||||
version: "0.0.0",
|
||||
language: "java",
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
});
|
||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||
expect(modelConfig.getPackLocation).toHaveBeenCalledWith("java", {
|
||||
database: "github/vscode-codeql",
|
||||
language: "java",
|
||||
name: "vscode-codeql",
|
||||
owner: "github",
|
||||
});
|
||||
|
||||
expect(
|
||||
loadYaml(await readFile(join(packLocation, "codeql-pack.yml"), "utf8")),
|
||||
).toEqual({
|
||||
name: packName,
|
||||
version: "0.0.0",
|
||||
library: true,
|
||||
extensionTargets: {
|
||||
"codeql/java-all": "*",
|
||||
},
|
||||
dataExtensions: ["models/**/*.yml"],
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a new extension pack with non-github origin database", async () => {
|
||||
const databaseItem: Pick<DatabaseItem, "name" | "language" | "origin"> = {
|
||||
name: "vscode-codeql",
|
||||
|
||||
@@ -138,10 +138,7 @@ describe("runGenerateQueries", () => {
|
||||
createMockLogger(),
|
||||
{
|
||||
mode: Mode.Framework,
|
||||
config: {
|
||||
...defaultModelConfig,
|
||||
showTypeModels: true,
|
||||
},
|
||||
config: defaultModelConfig,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -4,7 +4,10 @@ import { MethodModelingViewProvider } from "../../../../../src/model-editor/meth
|
||||
import { createMockApp } from "../../../../__mocks__/appMock";
|
||||
import { createMockModelingStore } from "../../../../__mocks__/model-editor/modelingStoreMock";
|
||||
import { mockedObject } from "../../../../mocked-object";
|
||||
import type { FromMethodModelingMessage } from "../../../../../src/common/interface-types";
|
||||
import type {
|
||||
FromMethodModelingMessage,
|
||||
ToMethodModelingMessage,
|
||||
} from "../../../../../src/common/interface-types";
|
||||
import { DisposableObject } from "../../../../../src/common/disposable-object";
|
||||
import { ModelingEvents } from "../../../../../src/model-editor/modeling-events";
|
||||
import type {
|
||||
@@ -17,6 +20,7 @@ import {
|
||||
createMethod,
|
||||
createUsage,
|
||||
} from "../../../../factories/model-editor/method-factories";
|
||||
import { QueryLanguage } from "../../../../../src/common/query-language";
|
||||
|
||||
describe("method modeling view provider", () => {
|
||||
// Modeling store
|
||||
@@ -45,7 +49,7 @@ describe("method modeling view provider", () => {
|
||||
const modelingEvents = new ModelingEvents(app);
|
||||
|
||||
const modelConfigListener = mockedObject<ModelConfigListener>({
|
||||
showTypeModels: true,
|
||||
flowGeneration: true,
|
||||
onDidChangeConfiguration: jest.fn(),
|
||||
});
|
||||
|
||||
@@ -91,10 +95,10 @@ describe("method modeling view provider", () => {
|
||||
viewState: {
|
||||
language: undefined,
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
flowGeneration: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
} satisfies ToMethodModelingMessage);
|
||||
});
|
||||
|
||||
it("should load webview when active DB but no selected method", async () => {
|
||||
@@ -116,10 +120,10 @@ describe("method modeling view provider", () => {
|
||||
viewState: {
|
||||
language: undefined,
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
flowGeneration: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
} satisfies ToMethodModelingMessage);
|
||||
expect(postMessage).toHaveBeenNthCalledWith(2, {
|
||||
t: "setInModelingMode",
|
||||
inModelingMode: true,
|
||||
@@ -127,12 +131,12 @@ describe("method modeling view provider", () => {
|
||||
expect(postMessage).toHaveBeenNthCalledWith(3, {
|
||||
t: "setMethodModelingPanelViewState",
|
||||
viewState: {
|
||||
language: "java",
|
||||
language: QueryLanguage.Java,
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
flowGeneration: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
} satisfies ToMethodModelingMessage);
|
||||
});
|
||||
|
||||
it("should load webview when active DB and a selected method", async () => {
|
||||
@@ -165,10 +169,10 @@ describe("method modeling view provider", () => {
|
||||
viewState: {
|
||||
language: undefined,
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
flowGeneration: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
} satisfies ToMethodModelingMessage);
|
||||
expect(postMessage).toHaveBeenNthCalledWith(2, {
|
||||
t: "setInModelingMode",
|
||||
inModelingMode: true,
|
||||
@@ -176,12 +180,12 @@ describe("method modeling view provider", () => {
|
||||
expect(postMessage).toHaveBeenNthCalledWith(3, {
|
||||
t: "setMethodModelingPanelViewState",
|
||||
viewState: {
|
||||
language: "java",
|
||||
language: QueryLanguage.Java,
|
||||
modelConfig: {
|
||||
showTypeModels: true,
|
||||
flowGeneration: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
} satisfies ToMethodModelingMessage);
|
||||
expect(postMessage).toHaveBeenNthCalledWith(4, {
|
||||
t: "setSelectedMethod",
|
||||
method: selectedMethodDetails.method,
|
||||
|
||||
Reference in New Issue
Block a user