From a61bae45f03215505a17218f119ea61335870fa6 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Tue, 11 Apr 2023 16:55:30 +0200 Subject: [PATCH 01/40] Allow creating new model file in existing data extension This will allow a user to create a new model file in an existing data extension when opening the data extension editor. There is some validation on the name of the model file, which depends on reading in the qlpack. --- extensions/ql-vscode/package-lock.json | 1378 ++++++++++++++++- extensions/ql-vscode/package.json | 1 + .../data-extensions-editor-module.ts | 4 +- .../data-extensions-editor/extension-packs.ts | 103 +- 4 files changed, 1420 insertions(+), 66 deletions(-) diff --git a/extensions/ql-vscode/package-lock.json b/extensions/ql-vscode/package-lock.json index 896a2b677..656a91455 100644 --- a/extensions/ql-vscode/package-lock.json +++ b/extensions/ql-vscode/package-lock.json @@ -23,6 +23,7 @@ "fs-extra": "^11.1.1", "immutable": "^4.0.0", "js-yaml": "^4.1.0", + "minimatch": "^9.0.0", "minimist": "~1.2.6", "msw": "^1.2.0", "nanoid": "^3.2.0", @@ -2645,6 +2646,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@eslint/eslintrc/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2677,6 +2688,18 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/eslintrc/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2913,6 +2936,16 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@humanwhocodes/config-array/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2930,6 +2963,18 @@ } } }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/config-array/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4156,6 +4201,16 @@ "node": ">= 8" } }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@jest/reporters/node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -4361,6 +4416,18 @@ "node": ">=8.6" } }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@jest/reporters/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5430,6 +5497,16 @@ "node": ">=10" } }, + "node_modules/@npmcli/move-file/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@npmcli/move-file/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5450,6 +5527,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@npmcli/move-file/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@npmcli/move-file/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -8607,6 +8696,16 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/@storybook/builder-webpack4/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@storybook/builder-webpack4/node_modules/clean-css": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", @@ -8939,6 +9038,18 @@ "node": ">=4.0.0" } }, + "node_modules/@storybook/builder-webpack4/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@storybook/builder-webpack4/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9687,6 +9798,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@storybook/builder-webpack5/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@storybook/builder-webpack5/node_modules/css-loader": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", @@ -9779,6 +9900,18 @@ "node": ">=8.9.0" } }, + "node_modules/@storybook/builder-webpack5/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@storybook/builder-webpack5/node_modules/nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", @@ -10769,6 +10902,16 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/@storybook/core-common/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@storybook/core-common/node_modules/cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", @@ -10991,6 +11134,18 @@ "yallist": "^3.0.2" } }, + "node_modules/@storybook/core-common/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@storybook/core-common/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -11549,6 +11704,16 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/@storybook/core-server/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@storybook/core-server/node_modules/cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", @@ -11705,6 +11870,18 @@ "yallist": "^3.0.2" } }, + "node_modules/@storybook/core-server/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@storybook/core-server/node_modules/schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -12404,6 +12581,16 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/@storybook/manager-webpack4/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@storybook/manager-webpack4/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -12757,6 +12944,18 @@ "node": ">=4.0.0" } }, + "node_modules/@storybook/manager-webpack4/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@storybook/manager-webpack4/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -17753,6 +17952,16 @@ "node": ">=8.9.3" } }, + "node_modules/@vscode/test-electron/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@vscode/test-electron/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -17773,6 +17982,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@vscode/test-electron/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@vscode/test-electron/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -17822,6 +18043,16 @@ "node": ">= 14" } }, + "node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@vscode/vsce/node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -17875,6 +18106,18 @@ "node": ">=10" } }, + "node_modules/@vscode/vsce/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@vscode/vsce/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -18704,6 +18947,15 @@ "node": ">= 6" } }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/archiver-utils/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -18723,6 +18975,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/archiver/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -20413,12 +20676,11 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -20737,6 +20999,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/c8/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/c8/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -20826,6 +21098,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/c8/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/c8/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -20962,6 +21246,16 @@ "node": ">= 10" } }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/cacache/node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -21003,6 +21297,18 @@ "node": ">=10" } }, + "node_modules/cacache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/cacache/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -21966,7 +22272,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -22280,6 +22586,16 @@ "node": ">=0.10.0" } }, + "node_modules/cpy/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/cpy/node_modules/dir-glob": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", @@ -22370,6 +22686,18 @@ "node": ">=6" } }, + "node_modules/cpy/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/cpy/node_modules/p-map": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", @@ -23524,6 +23852,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/del/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/del/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -23544,6 +23882,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/del/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/del/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -25139,6 +25489,16 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-import/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -25151,6 +25511,18 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-jest-dom": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-4.0.2.tgz", @@ -25210,12 +25582,34 @@ "node": ">=6.0" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -25295,6 +25689,16 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -25316,6 +25720,18 @@ "node": ">=4.0" } }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", @@ -25458,6 +25874,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -25637,6 +26063,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -27112,6 +27550,16 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flat-cache/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/flat-cache/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -27132,6 +27580,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/flat-cache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/flat-cache/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -27330,6 +27790,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -27408,6 +27878,18 @@ "node": ">=8" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -27833,6 +28315,16 @@ "node": ">= 0.10" } }, + "node_modules/glob-stream/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/glob-stream/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -27875,6 +28367,18 @@ "node": ">=0.10.0" } }, + "node_modules/glob-stream/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -27998,15 +28502,6 @@ "node": ">=0.10" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/glob/node_modules/minimatch": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", @@ -30840,6 +31335,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/jest-config/node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -30984,6 +31489,18 @@ "node": ">=8.6" } }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-config/node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -33259,6 +33776,16 @@ "node": ">= 8" } }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/jest-runtime/node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -33478,6 +34005,18 @@ "node": ">=8.6" } }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest-runtime/node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -36406,14 +36945,17 @@ "dev": true }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -36967,6 +37509,28 @@ "node": ">= 0.10.5" } }, + "node_modules/node-dir/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-dir/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -37135,6 +37699,16 @@ "node": ">= 4" } }, + "node_modules/npm-run-all/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/npm-run-all/node_modules/cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -37166,6 +37740,18 @@ "node": ">=4" } }, + "node_modules/npm-run-all/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/npm-run-all/node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -39536,6 +40122,26 @@ "minimatch": "^3.0.4" } }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -40236,6 +40842,15 @@ "rimraf": "bin.js" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -40255,6 +40870,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -42211,6 +42837,16 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -42231,6 +42867,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -42347,6 +42995,15 @@ "tmp": "^0.2.0" } }, + "node_modules/tmp-promise/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/tmp-promise/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -42366,6 +43023,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/tmp-promise/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/tmp-promise/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -42787,15 +43455,6 @@ "node": ">=10.0.0" } }, - "node_modules/ts-json-schema-generator/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/ts-json-schema-generator/node_modules/commander": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", @@ -44371,6 +45030,26 @@ "vscode": "^1.67.0" } }, + "node_modules/vscode-languageclient/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/vscode-languageclient/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/vscode-languageserver-protocol": { "version": "3.17.2", "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2.tgz", @@ -47324,6 +48003,16 @@ "uri-js": "^4.2.2" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -47345,6 +48034,15 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -47534,6 +48232,16 @@ "minimatch": "^3.0.4" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -47543,6 +48251,15 @@ "ms": "2.1.2" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -48498,6 +49215,16 @@ "picomatch": "^2.0.4" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -48652,6 +49379,15 @@ "picomatch": "^2.3.1" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -49476,6 +50212,16 @@ "rimraf": "^3.0.2" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -49490,6 +50236,15 @@ "path-is-absolute": "^1.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -51723,6 +52478,16 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "clean-css": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", @@ -51963,6 +52728,15 @@ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -52527,6 +53301,16 @@ "uri-js": "^4.2.2" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "css-loader": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", @@ -52588,6 +53372,15 @@ "json5": "^2.1.2" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", @@ -53319,6 +54112,16 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", @@ -53482,6 +54285,15 @@ "yallist": "^3.0.2" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -53931,6 +54743,16 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "cacache": { "version": "12.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", @@ -54054,6 +54876,15 @@ "yallist": "^3.0.2" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -54618,6 +55449,16 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -54872,6 +55713,15 @@ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -58699,6 +59549,16 @@ "unzipper": "^0.10.11" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -58713,6 +59573,15 @@ "path-is-absolute": "^1.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -58752,6 +59621,16 @@ "yazl": "^2.2.2" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -58790,6 +59669,15 @@ "yallist": "^4.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -59502,6 +60390,15 @@ "readable-stream": "^2.0.0" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -59514,6 +60411,14 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -60812,12 +61717,11 @@ } }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "braces": { @@ -61071,6 +61975,16 @@ "color-convert": "^2.0.1" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -61136,6 +62050,15 @@ "p-locate": "^5.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -61235,6 +62158,16 @@ "unique-filename": "^1.1.1" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -61264,6 +62197,15 @@ "yallist": "^4.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -62001,7 +62943,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "concat-stream": { "version": "1.6.2", @@ -62244,6 +63186,16 @@ "array-uniq": "^1.0.1" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "dir-glob": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", @@ -62318,6 +63270,15 @@ "slash": "^2.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "p-map": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", @@ -63213,6 +64174,16 @@ "slash": "^3.0.0" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -63227,6 +64198,15 @@ "path-is-absolute": "^1.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -64247,6 +65227,16 @@ "color-convert": "^2.0.1" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -64370,6 +65360,15 @@ "p-locate": "^5.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -64652,6 +65651,16 @@ "tsconfig-paths": "^3.14.1" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -64660,6 +65669,15 @@ "requires": { "esutils": "^2.0.2" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -64705,12 +65723,31 @@ "@babel/runtime-corejs3": "^7.10.2" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -64756,6 +65793,16 @@ "string.prototype.matchall": "^4.0.7" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -64771,6 +65818,15 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "resolve": { "version": "2.0.0-next.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", @@ -65902,6 +66958,16 @@ "rimraf": "^3.0.2" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -65916,6 +66982,15 @@ "path-is-absolute": "^1.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -66058,6 +67133,16 @@ "color-convert": "^2.0.1" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -66115,6 +67200,15 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -66411,15 +67505,6 @@ "path-scurry": "^1.6.1" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "minimatch": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", @@ -66472,6 +67557,16 @@ "unique-stream": "^2.0.2" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -66504,6 +67599,15 @@ "requires": { "is-extglob": "^2.1.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -68756,6 +69860,16 @@ "color-convert": "^2.0.1" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -68861,6 +69975,15 @@ "picomatch": "^2.3.1" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -70619,6 +71742,16 @@ "picomatch": "^2.0.4" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -70784,6 +71917,15 @@ "picomatch": "^2.3.1" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -72964,11 +74106,11 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } }, "minimist": { @@ -73414,6 +74556,27 @@ "dev": true, "requires": { "minimatch": "^3.0.2" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "node-fetch": { @@ -73554,6 +74717,16 @@ "string.prototype.padend": "^3.0.0" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -73579,6 +74752,15 @@ "strip-bom": "^3.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -75394,6 +76576,25 @@ "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", "requires": { "minimatch": "^3.0.4" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "readdirp": { @@ -75934,6 +77135,15 @@ "glob": "^7.1.3" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -75946,6 +77156,14 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -77484,6 +78702,16 @@ "minimatch": "^3.0.4" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -77497,6 +78725,15 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -77602,6 +78839,15 @@ "tmp": "^0.2.0" }, "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -77615,6 +78861,14 @@ "path-is-absolute": "^1.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -77926,15 +79180,6 @@ "typescript": "~4.8.3" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "commander": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", @@ -79111,6 +80356,25 @@ "minimatch": "^3.0.4", "semver": "^7.3.5", "vscode-languageserver-protocol": "3.17.2" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "vscode-languageserver-protocol": { diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index 819e78f9a..6e1d46d8b 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -1453,6 +1453,7 @@ "fs-extra": "^11.1.1", "immutable": "^4.0.0", "js-yaml": "^4.1.0", + "minimatch": "^9.0.0", "minimist": "~1.2.6", "msw": "^1.2.0", "nanoid": "^3.2.0", diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts index 2937ba771..543480eac 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts @@ -53,7 +53,7 @@ export class DataExtensionsEditorModule { return { "codeQL.openDataExtensionsEditor": async () => withProgress( - async (progress) => { + async (progress, token) => { const db = this.databaseManager.currentDatabaseItem; if (!db) { void showAndLogErrorMessage("No database selected"); @@ -78,6 +78,8 @@ export class DataExtensionsEditorModule { const modelFile = await pickModelFile( this.cliServer, progress, + token, + db, extensionPackPath, ); if (!modelFile) { diff --git a/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts b/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts index 4e44d189f..e45a1f77f 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts @@ -1,8 +1,13 @@ -import { relative } from "path"; -import { window } from "vscode"; +import { relative, resolve } from "path"; +import { pathExists, readFile } from "fs-extra"; +import { load as loadYaml } from "js-yaml"; +import { minimatch } from "minimatch"; +import { CancellationToken, window } from "vscode"; import { CodeQLCliServer } from "../cli"; import { getOnDiskWorkspaceFolders, showAndLogErrorMessage } from "../helpers"; import { ProgressCallback } from "../progress"; +import { DatabaseItem } from "../local-databases"; +import { getQlPackPath, QLPACK_FILENAMES } from "../pure/ql"; export async function pickExtensionPack( cliServer: CodeQLCliServer, @@ -49,6 +54,8 @@ export async function pickExtensionPack( export async function pickModelFile( cliServer: CodeQLCliServer, progress: ProgressCallback, + token: CancellationToken, + databaseItem: DatabaseItem, extensionPackPath: string, ): Promise { // Find the existing model files in the extension pack @@ -66,13 +73,17 @@ export async function pickModelFile( } } - const fileOptions: Array<{ label: string; file: string }> = []; + const fileOptions: Array<{ label: string; file: string | null }> = []; for (const file of modelFiles) { fileOptions.push({ label: relative(extensionPackPath, file), file, }); } + fileOptions.push({ + label: "Create new model file", + file: null, + }); progress({ message: "Choosing model file...", @@ -80,13 +91,89 @@ export async function pickModelFile( maxStep: 3, }); - const fileOption = await window.showQuickPick(fileOptions, { - title: "Select model file to use", - }); + const fileOption = await window.showQuickPick( + fileOptions, + { + title: "Select model file to use", + }, + token, + ); if (!fileOption) { - return; + return undefined; } - return fileOption.file; + if (fileOption.file) { + return fileOption.file; + } + + return pickNewModelFile(token, databaseItem, extensionPackPath); +} + +async function pickNewModelFile( + token: CancellationToken, + databaseItem: DatabaseItem, + extensionPackPath: string, +) { + const qlpackPath = await getQlPackPath(extensionPackPath); + if (!qlpackPath) { + void showAndLogErrorMessage( + `Could not find any of ${QLPACK_FILENAMES.join( + ", ", + )} in ${extensionPackPath}`, + ); + return undefined; + } + + const qlpack = await loadYaml(await readFile(qlpackPath, "utf8"), { + filename: qlpackPath, + }); + if (typeof qlpack !== "object" || qlpack === null) { + void showAndLogErrorMessage(`Could not parse ${qlpackPath}`); + return undefined; + } + + const dataExtensionPatterns = qlpack.dataExtensions ?? []; + if (!Array.isArray(dataExtensionPatterns)) { + void showAndLogErrorMessage( + `Expected 'dataExtensions' to be an array in ${qlpackPath}`, + ); + return undefined; + } + + const filename = await window.showInputBox( + { + title: "Enter the name of the new model file", + value: `models/${databaseItem.name.replaceAll("/", ".")}.model.yml`, + validateInput: async (value: string): Promise => { + const path = resolve(extensionPackPath, value); + + if (await pathExists(path)) { + return "File already exists"; + } + + const isInExtensionPack = !relative(extensionPackPath, path).startsWith( + "..", + ); + if (!isInExtensionPack) { + return "File must be in the extension pack"; + } + + const matchesPattern = dataExtensionPatterns.some((pattern) => + minimatch(value, pattern, { matchBase: true }), + ); + if (!matchesPattern) { + return `File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`; + } + + return undefined; + }, + }, + token, + ); + if (!filename) { + return undefined; + } + + return resolve(extensionPackPath, filename); } From 62619b23644a323880931f03aad2352d906bfdcf Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Tue, 11 Apr 2023 16:58:29 +0200 Subject: [PATCH 02/40] Switch to outputFile `outputFile` will create the parent directory if it doesn't exist yet, so this will allow users to specify a sub-directory for the model file. --- .../src/data-extensions-editor/data-extensions-editor-view.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts index 8370e45ca..f1d97149c 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts @@ -18,7 +18,7 @@ import { showAndLogWarningMessage, } from "../helpers"; import { extLogger } from "../common"; -import { readFile, writeFile } from "fs-extra"; +import { outputFile, readFile } from "fs-extra"; import { load as loadYaml } from "js-yaml"; import { DatabaseItem, DatabaseManager } from "../local-databases"; import { CodeQLCliServer } from "../cli"; @@ -150,7 +150,7 @@ export class DataExtensionsEditorView extends AbstractWebview< ): Promise { const yaml = createDataExtensionYaml(externalApiUsages, modeledMethods); - await writeFile(this.modelFilename, yaml); + await outputFile(this.modelFilename, yaml); void extLogger.log(`Saved data extension YAML to ${this.modelFilename}`); } From 656e7d6d8adab3a16f83903799eed87a30d11729 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Wed, 12 Apr 2023 08:54:51 +0200 Subject: [PATCH 03/40] Improve data extension pattern checking --- .../data-extensions-editor/extension-packs.ts | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts b/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts index e45a1f77f..e22862df0 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts @@ -133,14 +133,24 @@ async function pickNewModelFile( return undefined; } - const dataExtensionPatterns = qlpack.dataExtensions ?? []; - if (!Array.isArray(dataExtensionPatterns)) { + const dataExtensionPatternsValue = qlpack.dataExtensions ?? []; + if ( + !( + Array.isArray(dataExtensionPatternsValue) || + typeof dataExtensionPatternsValue === "string" + ) + ) { void showAndLogErrorMessage( - `Expected 'dataExtensions' to be an array in ${qlpackPath}`, + `Expected 'dataExtensions' to be a string or an array in ${qlpackPath}`, ); return undefined; } + // The YAML allows either a string or an array of strings + const dataExtensionPatterns = Array.isArray(dataExtensionPatternsValue) + ? dataExtensionPatternsValue + : [dataExtensionPatternsValue]; + const filename = await window.showInputBox( { title: "Enter the name of the new model file", @@ -152,10 +162,11 @@ async function pickNewModelFile( return "File already exists"; } - const isInExtensionPack = !relative(extensionPackPath, path).startsWith( - "..", - ); - if (!isInExtensionPack) { + const notInExtensionPack = !relative( + extensionPackPath, + path, + ).startsWith(".."); + if (notInExtensionPack) { return "File must be in the extension pack"; } From 59c0610122843951afba8271b8a170bf9c64f6aa Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Wed, 12 Apr 2023 08:55:24 +0200 Subject: [PATCH 04/40] Do not show quick pick when there are no model files --- .../ql-vscode/src/data-extensions-editor/extension-packs.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts b/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts index e22862df0..90e995f99 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/extension-packs.ts @@ -73,6 +73,10 @@ export async function pickModelFile( } } + if (modelFiles.size === 0) { + return pickNewModelFile(token, databaseItem, extensionPackPath); + } + const fileOptions: Array<{ label: string; file: string | null }> = []; for (const file of modelFiles) { fileOptions.push({ From 9bc53443c4c4094f428e0e7e9145f9b8763254d6 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Wed, 12 Apr 2023 12:26:30 +0200 Subject: [PATCH 05/40] Fix tests for creating new model file --- .../extension-pack-picker.test.ts | 75 +++++++++++++------ 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts index de8fcf5d3..504333761 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts @@ -1,4 +1,8 @@ import { CancellationTokenSource, QuickPickItem, window } from "vscode"; +import { dump as dumpYaml } from "js-yaml"; +import { outputFile } from "fs-extra"; +import { join } from "path"; +import { dir } from "tmp-promise"; import { pickExtensionPackModelFile } from "../../../../src/data-extensions-editor/extension-pack-picker"; import { QlpacksInfo, ResolveExtensionsResult } from "../../../../src/cli"; @@ -30,11 +34,15 @@ describe("pickExtensionPackModelFile", () => { const progress = jest.fn(); let showQuickPickSpy: jest.SpiedFunction; + let showInputBoxSpy: jest.SpiedFunction; beforeEach(() => { showQuickPickSpy = jest .spyOn(window, "showQuickPick") .mockRejectedValue(new Error("Unexpected call to showQuickPick")); + showInputBoxSpy = jest + .spyOn(window, "showInputBox") + .mockRejectedValue(new Error("Unexpected call to showInputBox")); }); it("allows choosing an existing extension pack and model file", async () => { @@ -72,6 +80,7 @@ describe("pickExtensionPackModelFile", () => { { title: expect.any(String), }, + token, ); expect(showQuickPickSpy).toHaveBeenCalledWith( [ @@ -79,10 +88,15 @@ describe("pickExtensionPackModelFile", () => { label: "models/model.yml", file: "/a/b/c/my-extension-pack/models/model.yml", }, + { + label: expect.stringMatching(/create/i), + file: null, + }, ], { title: expect.any(String), }, + token, ); expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1); expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true); @@ -110,7 +124,7 @@ describe("pickExtensionPackModelFile", () => { expect(cliServer.resolveExtensions).not.toHaveBeenCalled(); }); - it("does not show any options when there are no extension packs", async () => { + it("shows create option when there are no extension packs", async () => { const cliServer = mockCliServer({}, { models: [], data: {} }); showQuickPickSpy.mockResolvedValueOnce(undefined); @@ -124,9 +138,13 @@ describe("pickExtensionPackModelFile", () => { ), ).toEqual(undefined); expect(showQuickPickSpy).toHaveBeenCalledTimes(1); - expect(showQuickPickSpy).toHaveBeenCalledWith([], { - title: expect.any(String), - }); + expect(showQuickPickSpy).toHaveBeenCalledWith( + [], + { + title: expect.any(String), + }, + token, + ); expect(cliServer.resolveQlpacks).toHaveBeenCalled(); expect(cliServer.resolveExtensions).not.toHaveBeenCalled(); }); @@ -191,14 +209,37 @@ describe("pickExtensionPackModelFile", () => { expect(cliServer.resolveExtensions).toHaveBeenCalled(); }); - it("does not show any options when there are no model files", async () => { - const cliServer = mockCliServer(qlPacks, { models: [], data: {} }); + it("shows create input box when there are no model files", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + "my-extension-pack": [tmpDir.path], + }, + { models: [], data: {} }, + ); + + await outputFile( + join(tmpDir.path, "codeql-pack.yml"), + dumpYaml({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + dataExtensions: ["models/**/*.yml"], + }), + ); showQuickPickSpy.mockResolvedValueOnce({ label: "my-extension-pack", extensionPack: "my-extension-pack", } as QuickPickItem); showQuickPickSpy.mockResolvedValueOnce(undefined); + showInputBoxSpy.mockResolvedValue("models/my-model.yml"); expect( await pickExtensionPackModelFile( @@ -207,26 +248,16 @@ describe("pickExtensionPackModelFile", () => { progress, token, ), - ).toEqual(undefined); - expect(showQuickPickSpy).toHaveBeenCalledTimes(2); - expect(showQuickPickSpy).toHaveBeenCalledWith( - [ - { - label: "my-extension-pack", - extensionPack: "my-extension-pack", - }, - { - label: "another-extension-pack", - extensionPack: "another-extension-pack", - }, - ], + ).toEqual(join(tmpDir.path, "models/my-model.yml")); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).toHaveBeenCalledWith( { title: expect.any(String), + value: "models/github.vscode-codeql.model.yml", + validateInput: expect.any(Function), }, + token, ); - expect(showQuickPickSpy).toHaveBeenCalledWith([], { - title: expect.any(String), - }); expect(cliServer.resolveQlpacks).toHaveBeenCalled(); expect(cliServer.resolveExtensions).toHaveBeenCalled(); }); From 3664803b34b9e4236afa2e8bc4afaf3e2f1e1186 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Wed, 12 Apr 2023 12:50:31 +0200 Subject: [PATCH 06/40] Add tests for creating model file --- .../extension-pack-picker.ts | 13 +- .../extension-pack-picker.test.ts | 472 +++++++++++++++++- 2 files changed, 475 insertions(+), 10 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts b/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts index d04480734..db71eb42e 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts @@ -176,7 +176,7 @@ async function pickNewModelFile( return undefined; } - const dataExtensionPatternsValue = qlpack.dataExtensions ?? []; + const dataExtensionPatternsValue = qlpack.dataExtensions; if ( !( Array.isArray(dataExtensionPatternsValue) || @@ -199,16 +199,19 @@ async function pickNewModelFile( title: "Enter the name of the new model file", value: `models/${databaseItem.name.replaceAll("/", ".")}.model.yml`, validateInput: async (value: string): Promise => { + if (value === "") { + return "File name must not be empty"; + } + const path = resolve(extensionPackPath, value); if (await pathExists(path)) { return "File already exists"; } - const notInExtensionPack = !relative( - extensionPackPath, - path, - ).startsWith(".."); + const notInExtensionPack = relative(extensionPackPath, path).startsWith( + "..", + ); if (notInExtensionPack) { return "File must be in the extension pack"; } diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts index 504333761..4ca05c20d 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts @@ -35,6 +35,9 @@ describe("pickExtensionPackModelFile", () => { const progress = jest.fn(); let showQuickPickSpy: jest.SpiedFunction; let showInputBoxSpy: jest.SpiedFunction; + let showAndLogErrorMessageSpy: jest.SpiedFunction< + typeof helpers.showAndLogErrorMessage + >; beforeEach(() => { showQuickPickSpy = jest @@ -43,6 +46,11 @@ describe("pickExtensionPackModelFile", () => { showInputBoxSpy = jest .spyOn(window, "showInputBox") .mockRejectedValue(new Error("Unexpected call to showInputBox")); + showAndLogErrorMessageSpy = jest + .spyOn(helpers, "showAndLogErrorMessage") + .mockImplementation((msg) => { + throw new Error(`Unexpected call to showAndLogErrorMessage: ${msg}`); + }); }); it("allows choosing an existing extension pack and model file", async () => { @@ -107,6 +115,108 @@ describe("pickExtensionPackModelFile", () => { ); }); + it("allows choosing an existing extension pack and creating a new model file", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + ...qlPacks, + "my-extension-pack": [tmpDir.path], + }, + { + models: extensions.models, + data: { + [tmpDir.path]: [ + { + file: join(tmpDir.path, "models/model.yml"), + index: 0, + predicate: "sinkModel", + }, + ], + }, + }, + ); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "my-extension-pack", + extensionPack: "my-extension-pack", + } as QuickPickItem); + showQuickPickSpy.mockResolvedValueOnce({ + label: "create", + file: null, + } as QuickPickItem); + showInputBoxSpy.mockResolvedValue("models/my-model.yml"); + + await outputFile( + join(tmpDir.path, "codeql-pack.yml"), + dumpYaml({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + dataExtensions: ["models/**/*.yml"], + }), + ); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(join(tmpDir.path, "models/my-model.yml")); + expect(showQuickPickSpy).toHaveBeenCalledTimes(2); + expect(showQuickPickSpy).toHaveBeenCalledWith( + [ + { + label: "my-extension-pack", + extensionPack: "my-extension-pack", + }, + { + label: "another-extension-pack", + extensionPack: "another-extension-pack", + }, + ], + { + title: expect.any(String), + }, + token, + ); + expect(showQuickPickSpy).toHaveBeenCalledWith( + [ + { + label: "models/model.yml", + file: join(tmpDir.path, "models/model.yml"), + }, + { + label: expect.stringMatching(/create/i), + file: null, + }, + ], + { + title: expect.any(String), + }, + token, + ); + expect(showInputBoxSpy).toHaveBeenCalledWith( + { + title: expect.any(String), + value: "models/github.vscode-codeql.model.yml", + validateInput: expect.any(Function), + }, + token, + ); + expect(cliServer.resolveQlpacks).toHaveBeenCalledTimes(1); + expect(cliServer.resolveQlpacks).toHaveBeenCalledWith([], true); + expect(cliServer.resolveExtensions).toHaveBeenCalledTimes(1); + expect(cliServer.resolveExtensions).toHaveBeenCalledWith(tmpDir.path, []); + }); + it("allows cancelling the extension pack prompt", async () => { const cliServer = mockCliServer(qlPacks, extensions); @@ -124,7 +234,7 @@ describe("pickExtensionPackModelFile", () => { expect(cliServer.resolveExtensions).not.toHaveBeenCalled(); }); - it("shows create option when there are no extension packs", async () => { + it("does not show any options when there are no extension packs", async () => { const cliServer = mockCliServer({}, { models: [], data: {} }); showQuickPickSpy.mockResolvedValueOnce(undefined); @@ -150,10 +260,7 @@ describe("pickExtensionPackModelFile", () => { }); it("shows an error when an extension pack resolves to more than 1 location", async () => { - const showAndLogErrorMessageSpy = jest.spyOn( - helpers, - "showAndLogErrorMessage", - ); + showAndLogErrorMessageSpy.mockResolvedValue(undefined); const cliServer = mockCliServer( { @@ -261,6 +368,361 @@ describe("pickExtensionPackModelFile", () => { expect(cliServer.resolveQlpacks).toHaveBeenCalled(); expect(cliServer.resolveExtensions).toHaveBeenCalled(); }); + + it("shows an error when there is no pack YAML file", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + "my-extension-pack": [tmpDir.path], + }, + { models: [], data: {} }, + ); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "my-extension-pack", + extensionPack: "my-extension-pack", + } as QuickPickItem); + showQuickPickSpy.mockResolvedValueOnce(undefined); + showAndLogErrorMessageSpy.mockResolvedValue(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).not.toHaveBeenCalled(); + expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1); + expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith( + expect.stringMatching(/codeql-pack\.yml/), + ); + expect(cliServer.resolveQlpacks).toHaveBeenCalled(); + expect(cliServer.resolveExtensions).toHaveBeenCalled(); + }); + + it("shows an error when the pack YAML file is invalid", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + "my-extension-pack": [tmpDir.path], + }, + { models: [], data: {} }, + ); + + await outputFile(join(tmpDir.path, "codeql-pack.yml"), dumpYaml("java")); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "my-extension-pack", + extensionPack: "my-extension-pack", + } as QuickPickItem); + showQuickPickSpy.mockResolvedValueOnce(undefined); + showAndLogErrorMessageSpy.mockResolvedValue(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).not.toHaveBeenCalled(); + expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1); + expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith( + expect.stringMatching(/Could not parse/), + ); + expect(cliServer.resolveQlpacks).toHaveBeenCalled(); + expect(cliServer.resolveExtensions).toHaveBeenCalled(); + }); + + it("shows an error when the pack YAML does not contain dataExtensions", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + "my-extension-pack": [tmpDir.path], + }, + { models: [], data: {} }, + ); + + await outputFile( + join(tmpDir.path, "codeql-pack.yml"), + dumpYaml({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + }), + ); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "my-extension-pack", + extensionPack: "my-extension-pack", + } as QuickPickItem); + showQuickPickSpy.mockResolvedValueOnce(undefined); + showAndLogErrorMessageSpy.mockResolvedValue(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).not.toHaveBeenCalled(); + expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1); + expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith( + expect.stringMatching(/Expected 'dataExtensions' to be/), + ); + expect(cliServer.resolveQlpacks).toHaveBeenCalled(); + expect(cliServer.resolveExtensions).toHaveBeenCalled(); + }); + + it("shows an error when the pack YAML dataExtensions is invalid", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + "my-extension-pack": [tmpDir.path], + }, + { models: [], data: {} }, + ); + + await outputFile( + join(tmpDir.path, "codeql-pack.yml"), + dumpYaml({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + dataExtensions: { + "codeql/java-all": "invalid", + }, + }), + ); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "my-extension-pack", + extensionPack: "my-extension-pack", + } as QuickPickItem); + showQuickPickSpy.mockResolvedValueOnce(undefined); + showAndLogErrorMessageSpy.mockResolvedValue(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).not.toHaveBeenCalled(); + expect(showAndLogErrorMessageSpy).toHaveBeenCalledTimes(1); + expect(showAndLogErrorMessageSpy).toHaveBeenCalledWith( + expect.stringMatching(/Expected 'dataExtensions' to be/), + ); + expect(cliServer.resolveQlpacks).toHaveBeenCalled(); + expect(cliServer.resolveExtensions).toHaveBeenCalled(); + }); + + it("allows cancelling the new file input box", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + "my-extension-pack": [tmpDir.path], + }, + { models: [], data: {} }, + ); + + await outputFile( + join(tmpDir.path, "codeql-pack.yml"), + dumpYaml({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + dataExtensions: ["models/**/*.yml"], + }), + ); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "my-extension-pack", + extensionPack: "my-extension-pack", + } as QuickPickItem); + showQuickPickSpy.mockResolvedValueOnce(undefined); + showInputBoxSpy.mockResolvedValue(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).toHaveBeenCalledTimes(1); + expect(cliServer.resolveQlpacks).toHaveBeenCalled(); + expect(cliServer.resolveExtensions).toHaveBeenCalled(); + }); + + it("validates the file input", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + "my-extension-pack": [tmpDir.path], + }, + { models: [], data: {} }, + ); + + const qlpackPath = join(tmpDir.path, "codeql-pack.yml"); + await outputFile( + qlpackPath, + dumpYaml({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + dataExtensions: ["models/**/*.yml", "data/**/*.yml"], + }), + ); + await outputFile( + join(tmpDir.path, "models", "model.yml"), + dumpYaml({ + extensions: [], + }), + ); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "my-extension-pack", + extensionPack: "my-extension-pack", + } as QuickPickItem); + showQuickPickSpy.mockResolvedValueOnce(undefined); + showInputBoxSpy.mockResolvedValue(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + + const validateFile = showInputBoxSpy.mock.calls[0][0]?.validateInput; + expect(validateFile).toBeDefined(); + if (!validateFile) { + return; + } + + expect(await validateFile("")).toEqual("File name must not be empty"); + expect(await validateFile("models/model.yml")).toEqual( + "File already exists", + ); + expect(await validateFile("../model.yml")).toEqual( + "File must be in the extension pack", + ); + expect(await validateFile("/home/user/model.yml")).toEqual( + "File must be in the extension pack", + ); + expect(await validateFile("model.yml")).toEqual( + `File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`, + ); + expect(await validateFile("models/model.yaml")).toEqual( + `File must match one of the patterns in 'dataExtensions' in ${qlpackPath}`, + ); + expect(await validateFile("models/my-model.yml")).toBeUndefined(); + expect(await validateFile("models/nested/model.yml")).toBeUndefined(); + expect(await validateFile("data/model.yml")).toBeUndefined(); + }); + + it("allows the dataExtensions to be a string", async () => { + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + const cliServer = mockCliServer( + { + "my-extension-pack": [tmpDir.path], + }, + { models: [], data: {} }, + ); + + const qlpackPath = join(tmpDir.path, "codeql-pack.yml"); + await outputFile( + qlpackPath, + dumpYaml({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + dataExtensions: "models/**/*.yml", + }), + ); + await outputFile( + join(tmpDir.path, "models", "model.yml"), + dumpYaml({ + extensions: [], + }), + ); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "my-extension-pack", + extensionPack: "my-extension-pack", + } as QuickPickItem); + showQuickPickSpy.mockResolvedValueOnce(undefined); + showInputBoxSpy.mockResolvedValue(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + + const validateFile = showInputBoxSpy.mock.calls[0][0]?.validateInput; + expect(validateFile).toBeDefined(); + if (!validateFile) { + return; + } + + expect(await validateFile("models/my-model.yml")).toBeUndefined(); + }); }); function mockCliServer( From c1da623d43f5ff8b32a541af9f8b9b6ef8dbb712 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Wed, 12 Apr 2023 14:55:42 +0200 Subject: [PATCH 07/40] Add creating extension packs when opening the editor --- .../extension-pack-picker.ts | 131 +++++++++++++-- extensions/ql-vscode/src/helpers.ts | 12 +- .../extension-pack-picker.test.ts | 150 ++++++++++++++++-- 3 files changed, 271 insertions(+), 22 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts b/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts index db71eb42e..df47932a4 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts @@ -1,23 +1,38 @@ -import { relative, resolve, sep } from "path"; -import { pathExists, readFile } from "fs-extra"; -import { load as loadYaml } from "js-yaml"; +import { join, relative, resolve, sep } from "path"; +import { outputFile, pathExists, readFile } from "fs-extra"; +import { dump as dumpYaml, load as loadYaml } from "js-yaml"; import { minimatch } from "minimatch"; import { CancellationToken, window } from "vscode"; import { CodeQLCliServer } from "../cli"; -import { getOnDiskWorkspaceFolders, showAndLogErrorMessage } from "../helpers"; +import { + getOnDiskWorkspaceFolders, + getOnDiskWorkspaceFoldersObjects, + showAndLogErrorMessage, +} from "../helpers"; import { ProgressCallback } from "../progress"; import { DatabaseItem } from "../local-databases"; import { getQlPackPath, QLPACK_FILENAMES } from "../pure/ql"; const maxStep = 3; +const packNamePartRegex = /[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/; +const packNameRegex = new RegExp( + `^(?:(?${packNamePartRegex.source})/)?(?${packNamePartRegex.source})$`, +); +const packNameLength = 128; + export async function pickExtensionPackModelFile( cliServer: Pick, databaseItem: Pick, progress: ProgressCallback, token: CancellationToken, ): Promise { - const extensionPackPath = await pickExtensionPack(cliServer, progress, token); + const extensionPackPath = await pickExtensionPack( + cliServer, + databaseItem, + progress, + token, + ); if (!extensionPackPath) { return; } @@ -38,6 +53,7 @@ export async function pickExtensionPackModelFile( async function pickExtensionPack( cliServer: Pick, + databaseItem: Pick, progress: ProgressCallback, token: CancellationToken, ): Promise { @@ -50,10 +66,20 @@ async function pickExtensionPack( // Get all existing extension packs in the workspace const additionalPacks = getOnDiskWorkspaceFolders(); const extensionPacks = await cliServer.resolveQlpacks(additionalPacks, true); - const options = Object.keys(extensionPacks).map((pack) => ({ - label: pack, - extensionPack: pack, - })); + + if (Object.keys(extensionPacks).length === 0) { + return pickNewExtensionPack(databaseItem, token); + } + + const options: Array<{ label: string; extensionPack: string | null }> = + Object.keys(extensionPacks).map((pack) => ({ + label: pack, + extensionPack: pack, + })); + options.push({ + label: "Create new extension pack", + extensionPack: null, + }); progress({ message: "Choosing extension pack...", @@ -72,6 +98,10 @@ async function pickExtensionPack( return undefined; } + if (!extensionPackOption.extensionPack) { + return pickNewExtensionPack(databaseItem, token); + } + const extensionPackPaths = extensionPacks[extensionPackOption.extensionPack]; if (extensionPackPaths.length !== 1) { void showAndLogErrorMessage( @@ -153,6 +183,89 @@ async function pickModelFile( return pickNewModelFile(databaseItem, extensionPackPath, token); } +async function pickNewExtensionPack( + databaseItem: Pick, + token: CancellationToken, +): Promise { + const workspaceFolders = getOnDiskWorkspaceFoldersObjects(); + const workspaceFolderOptions = workspaceFolders.map((folder) => ({ + label: folder.name, + detail: folder.uri.fsPath, + path: folder.uri.fsPath, + })); + + // We're not using window.showWorkspaceFolderPick because that also includes the database source folders while + // we only want to include on-disk workspace folders. + const workspaceFolder = await window.showQuickPick(workspaceFolderOptions, { + title: "Select workspace folder to create extension pack in", + }); + if (!workspaceFolder) { + return undefined; + } + + const packName = await window.showInputBox( + { + title: "Create new extension pack", + prompt: "Enter name of extension pack", + placeHolder: `e.g. ${databaseItem.name}-extensions`, + validateInput: async (value: string): Promise => { + if (!value) { + return "Pack name must not be empty"; + } + + if (value.length > packNameLength) { + return `Pack name must be no longer than ${packNameLength} characters`; + } + + const matches = packNameRegex.exec(value); + if (!matches?.groups) { + return "Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens"; + } + + const packPath = join(workspaceFolder.path, matches.groups.name); + if (await pathExists(packPath)) { + return `A pack already exists at ${packPath}`; + } + + return undefined; + }, + }, + token, + ); + if (!packName) { + return undefined; + } + + const matches = packNameRegex.exec(packName); + if (!matches?.groups) { + return; + } + + const name = matches.groups.name; + const packPath = join(workspaceFolder.path, name); + + if (await pathExists(packPath)) { + return undefined; + } + + const packYamlPath = join(packPath, "codeql-pack.yml"); + + await outputFile( + packYamlPath, + dumpYaml({ + name, + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + dataExtensions: ["models/**/*.yml"], + }), + ); + + return packPath; +} + async function pickNewModelFile( databaseItem: Pick, extensionPackPath: string, diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index c8d1839e0..f3c56934e 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -16,6 +16,7 @@ import { window as Window, workspace, env, + WorkspaceFolder, } from "vscode"; import { CodeQLCliServer, QlpacksInfo } from "./cli"; import { UserCancellationException } from "./progress"; @@ -249,16 +250,21 @@ export async function showInformationMessageWithAction( } /** Gets all active workspace folders that are on the filesystem. */ -export function getOnDiskWorkspaceFolders() { +export function getOnDiskWorkspaceFoldersObjects() { const workspaceFolders = workspace.workspaceFolders || []; - const diskWorkspaceFolders: string[] = []; + const diskWorkspaceFolders: WorkspaceFolder[] = []; for (const workspaceFolder of workspaceFolders) { if (workspaceFolder.uri.scheme === "file") - diskWorkspaceFolders.push(workspaceFolder.uri.fsPath); + diskWorkspaceFolders.push(workspaceFolder); } return diskWorkspaceFolders; } +/** Gets all active workspace folders that are on the filesystem. */ +export function getOnDiskWorkspaceFolders() { + return getOnDiskWorkspaceFoldersObjects().map((folder) => folder.uri.fsPath); +} + /** Check if folder is already present in workspace */ export function isFolderAlreadyInWorkspace(folderName: string) { const workspaceFolders = workspace.workspaceFolders || []; diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts index 4ca05c20d..2846f6c88 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts @@ -1,6 +1,6 @@ import { CancellationTokenSource, QuickPickItem, window } from "vscode"; -import { dump as dumpYaml } from "js-yaml"; -import { outputFile } from "fs-extra"; +import { dump as dumpYaml, load as loadYaml } from "js-yaml"; +import { outputFile, readFile } from "fs-extra"; import { join } from "path"; import { dir } from "tmp-promise"; @@ -84,6 +84,10 @@ describe("pickExtensionPackModelFile", () => { label: "another-extension-pack", extensionPack: "another-extension-pack", }, + { + label: expect.stringMatching(/create/i), + extensionPack: null, + }, ], { title: expect.any(String), @@ -181,6 +185,10 @@ describe("pickExtensionPackModelFile", () => { label: "another-extension-pack", extensionPack: "another-extension-pack", }, + { + label: expect.stringMatching(/create/i), + extensionPack: null, + }, ], { title: expect.any(String), @@ -234,7 +242,69 @@ describe("pickExtensionPackModelFile", () => { expect(cliServer.resolveExtensions).not.toHaveBeenCalled(); }); - it("does not show any options when there are no extension packs", async () => { + it("allows user to create an extension pack when there are no extension packs", async () => { + const cliServer = mockCliServer({}, { models: [], data: {} }); + + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "codeql-custom-queries-java", + path: tmpDir.path, + } as QuickPickItem); + showInputBoxSpy.mockResolvedValueOnce("my-extension-pack"); + showInputBoxSpy.mockResolvedValue("models/my-model.yml"); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(join(tmpDir.path, "my-extension-pack", "models", "my-model.yml")); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).toHaveBeenCalledTimes(2); + expect(showInputBoxSpy).toHaveBeenCalledWith( + { + title: expect.stringMatching(/extension pack/i), + prompt: expect.stringMatching(/extension pack/i), + placeHolder: expect.stringMatching(/github\/vscode-codeql-extensions/), + validateInput: expect.any(Function), + }, + token, + ); + expect(showInputBoxSpy).toHaveBeenCalledWith( + { + title: expect.stringMatching(/model file/), + value: "models/github.vscode-codeql.model.yml", + validateInput: expect.any(Function), + }, + token, + ); + expect(cliServer.resolveQlpacks).toHaveBeenCalled(); + expect(cliServer.resolveExtensions).toHaveBeenCalled(); + + expect( + loadYaml( + await readFile( + join(tmpDir.path, "my-extension-pack", "codeql-pack.yml"), + "utf8", + ), + ), + ).toEqual({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/java-all": "*", + }, + dataExtensions: ["models/**/*.yml"], + }); + }); + + it("allows cancelling the workspace folder selection", async () => { const cliServer = mockCliServer({}, { models: [], data: {} }); showQuickPickSpy.mockResolvedValueOnce(undefined); @@ -248,13 +318,30 @@ describe("pickExtensionPackModelFile", () => { ), ).toEqual(undefined); expect(showQuickPickSpy).toHaveBeenCalledTimes(1); - expect(showQuickPickSpy).toHaveBeenCalledWith( - [], - { - title: expect.any(String), - }, - token, - ); + expect(showInputBoxSpy).toHaveBeenCalledTimes(0); + expect(cliServer.resolveQlpacks).toHaveBeenCalled(); + expect(cliServer.resolveExtensions).not.toHaveBeenCalled(); + }); + + it("allows cancelling the extension pack name input", async () => { + const cliServer = mockCliServer({}, { models: [], data: {} }); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "codeql-custom-queries-java", + path: "/a/b/c", + } as QuickPickItem); + showInputBoxSpy.mockResolvedValueOnce(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).toHaveBeenCalledTimes(1); expect(cliServer.resolveQlpacks).toHaveBeenCalled(); expect(cliServer.resolveExtensions).not.toHaveBeenCalled(); }); @@ -592,6 +679,49 @@ describe("pickExtensionPackModelFile", () => { expect(cliServer.resolveExtensions).toHaveBeenCalled(); }); + it("validates the pack name input", async () => { + const cliServer = mockCliServer({}, { models: [], data: {} }); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "a", + path: "/a/b/c", + } as QuickPickItem); + showInputBoxSpy.mockResolvedValue(undefined); + + expect( + await pickExtensionPackModelFile( + cliServer, + databaseItem, + progress, + token, + ), + ).toEqual(undefined); + + const validateFile = showInputBoxSpy.mock.calls[0][0]?.validateInput; + expect(validateFile).toBeDefined(); + if (!validateFile) { + return; + } + + expect(await validateFile("")).toEqual("Pack name must not be empty"); + expect(await validateFile("a".repeat(129))).toEqual( + "Pack name must be no longer than 128 characters", + ); + expect(await validateFile("github/vscode-codeql/extensions")).toEqual( + "Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens", + ); + expect(await validateFile("VSCODE")).toEqual( + "Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens", + ); + expect(await validateFile("github/vscode-codeql-")).toEqual( + "Invalid package name: a pack name must contain only lowercase ASCII letters, ASCII digits, and hyphens", + ); + expect( + await validateFile("github/vscode-codeql-extensions"), + ).toBeUndefined(); + expect(await validateFile("vscode-codeql-extensions")).toBeUndefined(); + }); + it("validates the file input", async () => { const tmpDir = await dir({ unsafeCleanup: true, From f0fbaabd69714c4586552835d731d829ad351a3f Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Wed, 12 Apr 2023 15:43:08 +0200 Subject: [PATCH 08/40] Change kind input to a dropdown This changes the kind input from a text field to a dropdown in the data extensions editor. The supported values for each extensible predicate are based on what is currently in-scope for the documentation. Other kinds are not supported. The supported kinds are now stored on the `extensiblePredicateDefinitions` to make it easier to add new kinds in the future. --- .../generate-flow-model.ts | 2 +- .../src/data-extensions-editor/predicates.ts | 136 ++++++++++++++++++ .../src/data-extensions-editor/yaml.ts | 115 +-------------- .../view/data-extensions-editor/KindInput.tsx | 48 +++++++ .../view/data-extensions-editor/MethodRow.tsx | 31 ++-- 5 files changed, 203 insertions(+), 129 deletions(-) create mode 100644 extensions/ql-vscode/src/data-extensions-editor/predicates.ts create mode 100644 extensions/ql-vscode/src/view/data-extensions-editor/KindInput.tsx diff --git a/extensions/ql-vscode/src/data-extensions-editor/generate-flow-model.ts b/extensions/ql-vscode/src/data-extensions-editor/generate-flow-model.ts index f6125d0f3..e5514029a 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/generate-flow-model.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/generate-flow-model.ts @@ -4,7 +4,7 @@ import { join } from "path"; import { QueryRunner } from "../queryRunner"; import { CodeQLCliServer } from "../cli"; import { TeeLogger } from "../common"; -import { extensiblePredicateDefinitions } from "./yaml"; +import { extensiblePredicateDefinitions } from "./predicates"; import { ProgressCallback } from "../progress"; import { getOnDiskWorkspaceFolders } from "../helpers"; import { diff --git a/extensions/ql-vscode/src/data-extensions-editor/predicates.ts b/extensions/ql-vscode/src/data-extensions-editor/predicates.ts new file mode 100644 index 000000000..e0d7ea74f --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/predicates.ts @@ -0,0 +1,136 @@ +import { ExternalApiUsage } from "./external-api-usage"; +import { + ModeledMethod, + ModeledMethodType, + ModeledMethodWithSignature, +} from "./modeled-method"; + +export type ExternalApiUsageByType = { + externalApiUsage: ExternalApiUsage; + modeledMethod: ModeledMethod; +}; + +export type ExtensiblePredicateDefinition = { + extensiblePredicate: string; + generateMethodDefinition: (method: ExternalApiUsageByType) => any[]; + readModeledMethod: (row: any[]) => ModeledMethodWithSignature; + + supportedKinds?: string[]; +}; + +function readRowToMethod(row: any[]): string { + return `${row[0]}.${row[1]}#${row[3]}${row[4]}`; +} + +export const extensiblePredicateDefinitions: Record< + Exclude, + ExtensiblePredicateDefinition +> = { + source: { + extensiblePredicate: "sourceModel", + // extensible predicate sourceModel( + // string package, string type, boolean subtypes, string name, string signature, string ext, + // string output, string kind, string provenance + // ); + generateMethodDefinition: (method) => [ + method.externalApiUsage.packageName, + method.externalApiUsage.typeName, + true, + method.externalApiUsage.methodName, + method.externalApiUsage.methodParameters, + "", + method.modeledMethod.output, + method.modeledMethod.kind, + "manual", + ], + readModeledMethod: (row) => ({ + signature: readRowToMethod(row), + modeledMethod: { + type: "source", + input: "", + output: row[6], + kind: row[7], + }, + }), + supportedKinds: ["remote"], + }, + sink: { + extensiblePredicate: "sinkModel", + // extensible predicate sinkModel( + // string package, string type, boolean subtypes, string name, string signature, string ext, + // string input, string kind, string provenance + // ); + generateMethodDefinition: (method) => [ + method.externalApiUsage.packageName, + method.externalApiUsage.typeName, + true, + method.externalApiUsage.methodName, + method.externalApiUsage.methodParameters, + "", + method.modeledMethod.input, + method.modeledMethod.kind, + "manual", + ], + readModeledMethod: (row) => ({ + signature: readRowToMethod(row), + modeledMethod: { + type: "sink", + input: row[6], + output: "", + kind: row[7], + }, + }), + supportedKinds: ["sql", "xss", "logging"], + }, + summary: { + extensiblePredicate: "summaryModel", + // extensible predicate summaryModel( + // string package, string type, boolean subtypes, string name, string signature, string ext, + // string input, string output, string kind, string provenance + // ); + generateMethodDefinition: (method) => [ + method.externalApiUsage.packageName, + method.externalApiUsage.typeName, + true, + method.externalApiUsage.methodName, + method.externalApiUsage.methodParameters, + "", + method.modeledMethod.input, + method.modeledMethod.output, + method.modeledMethod.kind, + "manual", + ], + readModeledMethod: (row) => ({ + signature: readRowToMethod(row), + modeledMethod: { + type: "summary", + input: row[6], + output: row[7], + kind: row[8], + }, + }), + supportedKinds: ["taint", "value"], + }, + neutral: { + extensiblePredicate: "neutralModel", + // extensible predicate neutralModel( + // string package, string type, string name, string signature, string provenance + // ); + generateMethodDefinition: (method) => [ + method.externalApiUsage.packageName, + method.externalApiUsage.typeName, + method.externalApiUsage.methodName, + method.externalApiUsage.methodParameters, + "manual", + ], + readModeledMethod: (row) => ({ + signature: `${row[0]}.${row[1]}#${row[2]}${row[3]}`, + modeledMethod: { + type: "neutral", + input: "", + output: "", + kind: "", + }, + }), + }, +}; diff --git a/extensions/ql-vscode/src/data-extensions-editor/yaml.ts b/extensions/ql-vscode/src/data-extensions-editor/yaml.ts index 95383173d..819f03186 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/yaml.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/yaml.ts @@ -4,6 +4,7 @@ import { ModeledMethodType, ModeledMethodWithSignature, } from "./modeled-method"; +import { extensiblePredicateDefinitions } from "./predicates"; type ExternalApiUsageByType = { externalApiUsage: ExternalApiUsage; @@ -16,120 +17,6 @@ type ExtensiblePredicateDefinition = { readModeledMethod: (row: any[]) => ModeledMethodWithSignature; }; -function readRowToMethod(row: any[]): string { - return `${row[0]}.${row[1]}#${row[3]}${row[4]}`; -} - -export const extensiblePredicateDefinitions: Record< - Exclude, - ExtensiblePredicateDefinition -> = { - source: { - extensiblePredicate: "sourceModel", - // extensible predicate sourceModel( - // string package, string type, boolean subtypes, string name, string signature, string ext, - // string output, string kind, string provenance - // ); - generateMethodDefinition: (method) => [ - method.externalApiUsage.packageName, - method.externalApiUsage.typeName, - true, - method.externalApiUsage.methodName, - method.externalApiUsage.methodParameters, - "", - method.modeledMethod.output, - method.modeledMethod.kind, - "manual", - ], - readModeledMethod: (row) => ({ - signature: readRowToMethod(row), - modeledMethod: { - type: "source", - input: "", - output: row[6], - kind: row[7], - }, - }), - }, - sink: { - extensiblePredicate: "sinkModel", - // extensible predicate sinkModel( - // string package, string type, boolean subtypes, string name, string signature, string ext, - // string input, string kind, string provenance - // ); - generateMethodDefinition: (method) => [ - method.externalApiUsage.packageName, - method.externalApiUsage.typeName, - true, - method.externalApiUsage.methodName, - method.externalApiUsage.methodParameters, - "", - method.modeledMethod.input, - method.modeledMethod.kind, - "manual", - ], - readModeledMethod: (row) => ({ - signature: readRowToMethod(row), - modeledMethod: { - type: "sink", - input: row[6], - output: "", - kind: row[7], - }, - }), - }, - summary: { - extensiblePredicate: "summaryModel", - // extensible predicate summaryModel( - // string package, string type, boolean subtypes, string name, string signature, string ext, - // string input, string output, string kind, string provenance - // ); - generateMethodDefinition: (method) => [ - method.externalApiUsage.packageName, - method.externalApiUsage.typeName, - true, - method.externalApiUsage.methodName, - method.externalApiUsage.methodParameters, - "", - method.modeledMethod.input, - method.modeledMethod.output, - method.modeledMethod.kind, - "manual", - ], - readModeledMethod: (row) => ({ - signature: readRowToMethod(row), - modeledMethod: { - type: "summary", - input: row[6], - output: row[7], - kind: row[8], - }, - }), - }, - neutral: { - extensiblePredicate: "neutralModel", - // extensible predicate neutralModel( - // string package, string type, string name, string signature, string provenance - // ); - generateMethodDefinition: (method) => [ - method.externalApiUsage.packageName, - method.externalApiUsage.typeName, - method.externalApiUsage.methodName, - method.externalApiUsage.methodParameters, - "manual", - ], - readModeledMethod: (row) => ({ - signature: `${row[0]}.${row[1]}#${row[2]}${row[3]}`, - modeledMethod: { - type: "neutral", - input: "", - output: "", - kind: "", - }, - }), - }, -}; - function createDataProperty( methods: ExternalApiUsageByType[], definition: ExtensiblePredicateDefinition, diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/KindInput.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/KindInput.tsx new file mode 100644 index 000000000..52f49f6b6 --- /dev/null +++ b/extensions/ql-vscode/src/view/data-extensions-editor/KindInput.tsx @@ -0,0 +1,48 @@ +import * as React from "react"; +import { useCallback, useEffect } from "react"; +import styled from "styled-components"; +import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react"; + +import type { ModeledMethod } from "../../data-extensions-editor/modeled-method"; + +const Dropdown = styled(VSCodeDropdown)` + width: 100%; +`; + +type Props = { + kinds: Array; + + value: ModeledMethod["kind"] | undefined; + onChange: (value: ModeledMethod["kind"]) => void; +}; + +export const KindInput = ({ kinds, value, onChange }: Props) => { + const handleInput = useCallback( + (e: InputEvent) => { + const target = e.target as HTMLSelectElement; + + onChange(target.value as ModeledMethod["kind"]); + }, + [onChange], + ); + + useEffect(() => { + if (value === undefined && kinds.length > 0) { + onChange(kinds[0]); + } + + if (value !== undefined && !kinds.includes(value)) { + onChange(kinds[0]); + } + }, [value, kinds, onChange]); + + return ( + + {kinds.map((kind) => ( + + {kind} + + ))} + + ); +}; diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx index 1d97b0ced..47cb04f0e 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx +++ b/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx @@ -3,7 +3,6 @@ import { VSCodeDataGridRow, VSCodeDropdown, VSCodeOption, - VSCodeTextField, } from "@vscode/webview-ui-toolkit/react"; import * as React from "react"; import { useCallback, useMemo } from "react"; @@ -15,15 +14,13 @@ import { ModeledMethod, ModeledMethodType, } from "../../data-extensions-editor/modeled-method"; +import { KindInput } from "./KindInput"; +import { extensiblePredicateDefinitions } from "../../data-extensions-editor/predicates"; const Dropdown = styled(VSCodeDropdown)` width: 100%; `; -const TextField = styled(VSCodeTextField)` - width: 100%; -`; - type SupportedUnsupportedSpanProps = { supported: boolean; }; @@ -107,17 +104,15 @@ export const MethodRow = ({ }, [onChange, externalApiUsage, modeledMethod], ); - const handleKindInput = useCallback( - (e: InputEvent) => { + const handleKindChange = useCallback( + (kind: string) => { if (!modeledMethod) { return; } - const target = e.target as HTMLSelectElement; - onChange(externalApiUsage, { ...modeledMethod, - kind: target.value as ModeledMethod["kind"], + kind, }); }, [onChange, externalApiUsage, modeledMethod], @@ -130,6 +125,11 @@ export const MethodRow = ({ }); }, [externalApiUsage]); + const predicate = + modeledMethod?.type && modeledMethod.type !== "none" + ? extensiblePredicateDefinitions[modeledMethod.type] + : undefined; + return ( @@ -195,10 +195,13 @@ export const MethodRow = ({ )} - {modeledMethod?.type && - ["source", "sink", "summary"].includes(modeledMethod?.type) && ( - - )} + {predicate?.supportedKinds && ( + + )} ); From 9a2de398f8f9bd7f62f1a0be38bad34763320c0b Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Thu, 13 Apr 2023 09:07:45 +0000 Subject: [PATCH 09/40] Move `getFirstStoragePath` method into helpers So that we can re-use it in the QLPackGenerator. --- extensions/ql-vscode/src/helpers.ts | 23 ++++++- .../ql-vscode/src/skeleton-query-wizard.ts | 31 +++------- .../skeleton-query-wizard.test.ts | 60 ------------------- .../vscode-tests/no-workspace/helpers.test.ts | 38 ++++++++++++ 4 files changed, 67 insertions(+), 85 deletions(-) diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index c8d1839e0..06989e8fd 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -8,7 +8,7 @@ import { } from "fs-extra"; import { glob } from "glob"; import { load } from "js-yaml"; -import { join, basename } from "path"; +import { join, basename, dirname } from "path"; import { dirSync } from "tmp-promise"; import { ExtensionContext, @@ -785,3 +785,24 @@ export async function* walkDirectory( } } } + +export function getFirstStoragePath() { + const workspaceFolders = workspace.workspaceFolders; + + if (!workspaceFolders || workspaceFolders.length === 0) { + throw new Error("No workspace folders found"); + } + + const firstFolder = workspaceFolders[0]; + const firstFolderFsPath = firstFolder.uri.fsPath; + + // For the vscode-codeql-starter repo, the first folder will be a ql pack + // so we need to get the parent folder + if (firstFolderFsPath.includes("codeql-custom-queries")) { + // return the parent folder + return dirname(firstFolderFsPath); + } else { + // if the first folder is not a ql pack, then we are in a normal workspace + return firstFolderFsPath; + } +} diff --git a/extensions/ql-vscode/src/skeleton-query-wizard.ts b/extensions/ql-vscode/src/skeleton-query-wizard.ts index f42346faf..97eb90602 100644 --- a/extensions/ql-vscode/src/skeleton-query-wizard.ts +++ b/extensions/ql-vscode/src/skeleton-query-wizard.ts @@ -1,10 +1,14 @@ -import { join, dirname } from "path"; +import { join } from "path"; import { CancellationToken, Uri, workspace, window as Window } from "vscode"; import { CodeQLCliServer } from "./cli"; import { OutputChannelLogger } from "./common"; import { Credentials } from "./common/authentication"; import { QueryLanguage } from "./common/query-language"; -import { askForLanguage, isFolderAlreadyInWorkspace } from "./helpers"; +import { + askForLanguage, + getFirstStoragePath, + isFolderAlreadyInWorkspace, +} from "./helpers"; import { getErrorMessage } from "./pure/helpers-pure"; import { QlPackGenerator } from "./qlpack-generator"; import { DatabaseItem, DatabaseManager } from "./local-databases"; @@ -50,7 +54,7 @@ export class SkeletonQueryWizard { return; } - this.qlPackStoragePath = this.getFirstStoragePath(); + this.qlPackStoragePath = getFirstStoragePath(); const skeletonPackAlreadyExists = isFolderAlreadyInWorkspace( this.folderName, @@ -93,27 +97,6 @@ export class SkeletonQueryWizard { }); } - public getFirstStoragePath() { - const workspaceFolders = workspace.workspaceFolders; - - if (!workspaceFolders || workspaceFolders.length === 0) { - throw new Error("No workspace folders found"); - } - - const firstFolder = workspaceFolders[0]; - const firstFolderFsPath = firstFolder.uri.fsPath; - - // For the vscode-codeql-starter repo, the first folder will be a ql pack - // so we need to get the parent folder - if (firstFolderFsPath.includes("codeql-custom-queries")) { - // return the parent folder - return dirname(firstFolderFsPath); - } else { - // if the first folder is not a ql pack, then we are in a normal workspace - return firstFolderFsPath; - } - } - private async chooseLanguage() { this.progress({ message: "Choose language", diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts index a20b71fb3..2facce402 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts @@ -303,66 +303,6 @@ describe("SkeletonQueryWizard", () => { }); }); - describe("getFirstStoragePath", () => { - it("should return the first workspace folder", async () => { - jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([ - { - name: "codespaces-codeql", - uri: { fsPath: "codespaces-codeql" }, - }, - ] as WorkspaceFolder[]); - - wizard = new SkeletonQueryWizard( - mockCli, - jest.fn(), - credentials, - extLogger, - mockDatabaseManager, - token, - storagePath, - ); - - expect(wizard.getFirstStoragePath()).toEqual("codespaces-codeql"); - }); - - describe("if user is in vscode-codeql-starter workspace", () => { - it("should set storage path to parent folder", async () => { - jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([ - { - name: "codeql-custom-queries-cpp", - uri: { - fsPath: join( - "vscode-codeql-starter", - "codeql-custom-queries-cpp", - ), - }, - }, - { - name: "codeql-custom-queries-csharp", - uri: { - fsPath: join( - "vscode-codeql-starter", - "codeql-custom-queries-csharp", - ), - }, - }, - ] as WorkspaceFolder[]); - - wizard = new SkeletonQueryWizard( - mockCli, - jest.fn(), - credentials, - extLogger, - mockDatabaseManager, - token, - storagePath, - ); - - expect(wizard.getFirstStoragePath()).toEqual("vscode-codeql-starter"); - }); - }); - }); - describe("findDatabaseItemByNwo", () => { describe("when the item exists", () => { it("should return the database item", async () => { diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts index 98919219d..d5afb122c 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts @@ -26,6 +26,7 @@ import { import { DirResult } from "tmp"; import { + getFirstStoragePath, getInitialQueryContents, InvocationRateLimiter, isFolderAlreadyInWorkspace, @@ -671,3 +672,40 @@ describe("prepareCodeTour", () => { }); }); }); + +describe("getFirstStoragePath", () => { + it("should return the first workspace folder", async () => { + jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([ + { + name: "codespaces-codeql", + uri: { fsPath: "codespaces-codeql" }, + }, + ] as WorkspaceFolder[]); + + expect(getFirstStoragePath()).toEqual("codespaces-codeql"); + }); + + describe("if user is in vscode-codeql-starter workspace", () => { + it("should set storage path to parent folder", async () => { + jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([ + { + name: "codeql-custom-queries-cpp", + uri: { + fsPath: join("vscode-codeql-starter", "codeql-custom-queries-cpp"), + }, + }, + { + name: "codeql-custom-queries-csharp", + uri: { + fsPath: join( + "vscode-codeql-starter", + "codeql-custom-queries-csharp", + ), + }, + }, + ] as WorkspaceFolder[]); + + expect(getFirstStoragePath()).toEqual("vscode-codeql-starter"); + }); + }); +}); From 6000e72ee57ceea9e8bfb9458b5bf94a56350aff Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Thu, 13 Apr 2023 09:25:36 +0000 Subject: [PATCH 10/40] Stop pushing QL pack as top level folder to avoid confusing the user In the original flow for creating skeleton packs, we were starting out by choosing a database (e.g. github/codeql) and having the extension create the QL pack for us. At that point, we were storing the QL pack together with the database in the extension storage because we weren't interested in committing it to the repo. This means we weren't able to see it in the file explorer so in order to make it visible, we decided to push it as a top-level folder in the workspace. Hindsight is 20/20. Let's change this original flow by just creating the folder in the workspace storage instead of the extension storage (which will make it visible in the file explorer) and stop pushing it as an extra top level folder in the workspace. NB: For this flow, we exit early in the `createSkeletonPacks` method if the folder already exists so we don't need to check this again. --- extensions/ql-vscode/src/local-databases.ts | 5 ++++- extensions/ql-vscode/src/qlpack-generator.ts | 13 +++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/extensions/ql-vscode/src/local-databases.ts b/extensions/ql-vscode/src/local-databases.ts index a4e32a5d0..7dbdf3cf2 100644 --- a/extensions/ql-vscode/src/local-databases.ts +++ b/extensions/ql-vscode/src/local-databases.ts @@ -11,6 +11,7 @@ import { showAndLogExceptionWithTelemetry, isFolderAlreadyInWorkspace, showBinaryChoiceDialog, + getFirstStoragePath, } from "./helpers"; import { ProgressCallback, withProgress } from "./progress"; import { @@ -676,11 +677,13 @@ export class DatabaseManager extends DisposableObject { } try { + const workspaceStorage = getFirstStoragePath(); + const qlPackGenerator = new QlPackGenerator( folderName, databaseItem.language as QueryLanguage, this.cli, - this.ctx.storageUri?.fsPath, + workspaceStorage, ); await qlPackGenerator.generate(); } catch (e: unknown) { diff --git a/extensions/ql-vscode/src/qlpack-generator.ts b/extensions/ql-vscode/src/qlpack-generator.ts index a7efc3f3d..b87cf1351 100644 --- a/extensions/ql-vscode/src/qlpack-generator.ts +++ b/extensions/ql-vscode/src/qlpack-generator.ts @@ -1,7 +1,7 @@ -import { writeFile } from "fs-extra"; +import { mkdir, writeFile } from "fs-extra"; import { dump } from "js-yaml"; import { join } from "path"; -import { Uri, workspace } from "vscode"; +import { Uri } from "vscode"; import { CodeQLCliServer } from "./cli"; import { QueryLanguage } from "./common/query-language"; @@ -44,14 +44,7 @@ export class QlPackGenerator { } private async createWorkspaceFolder() { - await workspace.fs.createDirectory(this.folderUri); - - const end = (workspace.workspaceFolders || []).length; - - workspace.updateWorkspaceFolders(end, 0, { - name: this.folderName, - uri: this.folderUri, - }); + await mkdir(this.folderUri.fsPath); } private async createQlPackYaml() { From 5200871989127b3d994bf54455eb3dbbe7f8760b Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Thu, 13 Apr 2023 13:26:32 +0200 Subject: [PATCH 11/40] Add external APIs query in extension This adds the external API query text to the extension directly to avoid users having to copy the query to their local `codeql` submodule or having to checkout a specific branch. This is a temporary solution until the queries are stabilized. Once they are, we will upstream these to `github/codeql` and use them like other contextual queries. Since this is just a temporary solution, this is not the prettiest code and is not intended to be a long-term solution. It does not do proper caching and will create a new temporary directory for every query run. The performance hit of this is acceptable and expected. --- .../data-extensions-editor-view.ts | 1 - .../external-api-usage-query.ts | 93 +++++---- .../data-extensions-editor/queries/index.ts | 6 + .../data-extensions-editor/queries/java.ts | 183 ++++++++++++++++++ .../data-extensions-editor/queries/query.ts | 6 + .../external-api-usage-query.test.ts | 71 +++---- 6 files changed, 287 insertions(+), 73 deletions(-) create mode 100644 extensions/ql-vscode/src/data-extensions-editor/queries/index.ts create mode 100644 extensions/ql-vscode/src/data-extensions-editor/queries/java.ts create mode 100644 extensions/ql-vscode/src/data-extensions-editor/queries/query.ts diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts index 8370e45ca..a9242a731 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts @@ -188,7 +188,6 @@ export class DataExtensionsEditorView extends AbstractWebview< queryRunner: this.queryRunner, databaseItem: this.databaseItem, queryStorageDir: this.queryStorageDir, - logger: extLogger, progress: (progressUpdate: ProgressUpdate) => { void this.showProgress(progressUpdate, 1500); }, diff --git a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts index a5cd3c052..7ad472d31 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts @@ -1,21 +1,26 @@ import { CoreCompletedQuery, QueryRunner } from "../queryRunner"; -import { qlpackOfDatabase } from "../contextual/queryResolver"; -import { file } from "tmp-promise"; +import { dir } from "tmp-promise"; import { writeFile } from "fs-extra"; import { dump as dumpYaml } from "js-yaml"; -import { getOnDiskWorkspaceFolders } from "../helpers"; +import { + getOnDiskWorkspaceFolders, + showAndLogExceptionWithTelemetry, +} from "../helpers"; import { Logger, TeeLogger } from "../common"; import { CancellationToken } from "vscode"; import { CodeQLCliServer } from "../cli"; import { DatabaseItem } from "../local-databases"; import { ProgressCallback } from "../progress"; +import { fetchExternalApiQueries } from "./queries"; +import { QueryResultType } from "../pure/new-messages"; +import { join } from "path"; +import { redactableError } from "../pure/errors"; export type RunQueryOptions = { - cliServer: Pick; + cliServer: Pick; queryRunner: Pick; databaseItem: Pick; queryStorageDir: string; - logger: Logger; progress: ProgressCallback; token: CancellationToken; @@ -26,54 +31,53 @@ export async function runQuery({ queryRunner, databaseItem, queryStorageDir, - logger, progress, token, }: RunQueryOptions): Promise { - const qlpacks = await qlpackOfDatabase(cliServer, databaseItem); + // The below code is temporary to allow for rapid prototyping of the queries. Once the queries are stabilized, we will + // move these queries into the `github/codeql` repository and use them like any other contextual (e.g. AST) queries. + // This is intentionally not pretty code, as it will be removed soon. + // For a reference of what this should do in the future, see the previous implementation in + // https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72 - const packsToSearch = [qlpacks.dbschemePack]; - if (qlpacks.queryPack) { - packsToSearch.push(qlpacks.queryPack); + const query = fetchExternalApiQueries[databaseItem.language]; + if (!query) { + void showAndLogExceptionWithTelemetry( + redactableError`No external API usage query found for language ${databaseItem.language}`, + ); + return; } - const suiteFile = ( - await file({ - postfix: ".qls", - }) - ).path; - const suiteYaml = []; - for (const qlpack of packsToSearch) { - suiteYaml.push({ - from: qlpack, - queries: ".", - include: { - id: `${databaseItem.language}/telemetry/fetch-external-apis`, - }, - }); + const queryDir = (await dir({ unsafeCleanup: true })).path; + const queryFile = join(queryDir, "FetchExternalApis.ql"); + await writeFile(queryFile, query.mainQuery, "utf8"); + + if (query.dependencies) { + for (const [filename, contents] of Object.entries(query.dependencies)) { + const dependencyFile = join(queryDir, filename); + await writeFile(dependencyFile, contents, "utf8"); + } } - await writeFile(suiteFile, dumpYaml(suiteYaml), "utf8"); + + const syntheticQueryPack = { + name: "codeql/external-api-usage", + version: "0.0.0", + dependencies: { + [`codeql/${databaseItem.language}-all`]: "*", + }, + }; + + const qlpackFile = join(queryDir, "codeql-pack.yml"); + await writeFile(qlpackFile, dumpYaml(syntheticQueryPack), "utf8"); const additionalPacks = getOnDiskWorkspaceFolders(); const extensionPacks = Object.keys( await cliServer.resolveQlpacks(additionalPacks, true), ); - const queries = await cliServer.resolveQueriesInSuite( - suiteFile, - getOnDiskWorkspaceFolders(), - ); - - if (queries.length !== 1) { - void logger.log(`Expected exactly one query, got ${queries.length}`); - return; - } - - const query = queries[0]; - const queryRun = queryRunner.createQueryRun( databaseItem.databaseUri.fsPath, - { queryPath: query, quickEvalPosition: undefined }, + { queryPath: queryFile, quickEvalPosition: undefined }, false, getOnDiskWorkspaceFolders(), extensionPacks, @@ -82,11 +86,22 @@ export async function runQuery({ undefined, ); - return queryRun.evaluate( + const completedQuery = await queryRun.evaluate( progress, token, new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath), ); + + if (completedQuery.resultType !== QueryResultType.SUCCESS) { + void showAndLogExceptionWithTelemetry( + redactableError`External API usage query failed: ${ + completedQuery.message ?? "No message" + }`, + ); + return; + } + + return completedQuery; } export type GetResultsOptions = { diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts new file mode 100644 index 000000000..7f5864bb9 --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts @@ -0,0 +1,6 @@ +import { fetchExternalApisQuery as javaFetchExternalApisQuery } from "./java"; +import { Query } from "./query"; + +export const fetchExternalApiQueries: Record = { + java: javaFetchExternalApisQuery, +}; diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts new file mode 100644 index 000000000..5d164f164 --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts @@ -0,0 +1,183 @@ +import { Query } from "./query"; + +export const fetchExternalApisQuery: Query = { + mainQuery: `/** + * @name Usage of APIs coming from external libraries + * @description A list of 3rd party APIs used in the codebase. Excludes test and generated code. + * @tags telemetry + * @id java/telemetry/fetch-external-apis + */ + +import java +import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +import ExternalApi + +private Call aUsage(ExternalApi api) { + result.getCallee().getSourceDeclaration() = api and + not result.getFile() instanceof GeneratedFile +} + +private boolean isSupported(ExternalApi api) { + api.isSupported() and result = true + or + api = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() and result = true + or + not api.isSupported() and + not api = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() and + result = false +} + +from ExternalApi api, string apiName, boolean supported, Call usage +where + apiName = api.getApiName() and + supported = isSupported(api) and + usage = aUsage(api) +select apiName, supported, usage +`, + dependencies: { + "ExternalApi.qll": `/** Provides classes and predicates related to handling APIs from external libraries. */ + +private import java +private import semmle.code.java.dataflow.DataFlow +private import semmle.code.java.dataflow.ExternalFlow +private import semmle.code.java.dataflow.FlowSources +private import semmle.code.java.dataflow.FlowSummary +private import semmle.code.java.dataflow.internal.DataFlowPrivate +private import semmle.code.java.dataflow.TaintTracking + +pragma[nomagic] +private predicate isTestPackage(Package p) { + p.getName() + .matches([ + "org.junit%", "junit.%", "org.mockito%", "org.assertj%", + "com.github.tomakehurst.wiremock%", "org.hamcrest%", "org.springframework.test.%", + "org.springframework.mock.%", "org.springframework.boot.test.%", "reactor.test%", + "org.xmlunit%", "org.testcontainers.%", "org.opentest4j%", "org.mockserver%", + "org.powermock%", "org.skyscreamer.jsonassert%", "org.rnorth.visibleassertions", + "org.openqa.selenium%", "com.gargoylesoftware.htmlunit%", "org.jboss.arquillian.testng%", + "org.testng%" + ]) +} + +/** + * A test library. + */ +private class TestLibrary extends RefType { + TestLibrary() { isTestPackage(this.getPackage()) } +} + +private string containerAsJar(Container container) { + if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar" +} + +/** Holds if the given callable is not worth supporting. */ +private predicate isUninteresting(Callable c) { + c.getDeclaringType() instanceof TestLibrary or + c.(Constructor).isParameterless() +} + +/** + * An external API from either the Standard Library or a 3rd party library. + */ +class ExternalApi extends Callable { + ExternalApi() { not this.fromSource() and not isUninteresting(this) } + + /** + * Gets information about the external API in the form expected by the MaD modeling framework. + */ + string getApiName() { + result = + this.getDeclaringType().getPackage() + "." + this.getDeclaringType().getSourceDeclaration() + + "#" + this.getName() + paramsString(this) + } + + /** + * Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules. + */ + string jarContainer() { result = containerAsJar(this.getCompilationUnit().getParentContainer*()) } + + /** Gets a node that is an input to a call to this API. */ + private DataFlow::Node getAnInput() { + exists(Call call | call.getCallee().getSourceDeclaration() = this | + result.asExpr().(Argument).getCall() = call or + result.(ArgumentNode).getCall().asCall() = call + ) + } + + /** Gets a node that is an output from a call to this API. */ + private DataFlow::Node getAnOutput() { + exists(Call call | call.getCallee().getSourceDeclaration() = this | + result.asExpr() = call or + result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call + ) + } + + /** Holds if this API has a supported summary. */ + pragma[nomagic] + predicate hasSummary() { + this = any(SummarizedCallable sc).asCallable() or + TaintTracking::localAdditionalTaintStep(this.getAnInput(), _) + } + + pragma[nomagic] + predicate isSource() { + this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _) + } + + /** Holds if this API is a known sink. */ + pragma[nomagic] + predicate isSink() { sinkNode(this.getAnInput(), _) } + + /** Holds if this API is supported by existing CodeQL libraries, that is, it is either a recognized source or sink or has a flow summary. */ + predicate isSupported() { this.hasSummary() or this.isSource() or this.isSink() } +} + +/** DEPRECATED: Alias for ExternalApi */ +deprecated class ExternalAPI = ExternalApi; + +/** + * Gets the limit for the number of results produced by a telemetry query. + */ +int resultLimit() { result = 1000 } + +/** + * Holds if it is relevant to count usages of \`api\`. + */ +signature predicate relevantApi(ExternalApi api); + +/** + * Given a predicate to count relevant API usages, this module provides a predicate + * for restricting the number or returned results based on a certain limit. + */ +module Results { + private int getUsages(string apiName) { + result = + strictcount(Call c, ExternalApi api | + c.getCallee().getSourceDeclaration() = api and + not c.getFile() instanceof GeneratedFile and + apiName = api.getApiName() and + getRelevantUsages(api) + ) + } + + private int getOrder(string apiInfo) { + apiInfo = + rank[result](string info, int usages | + usages = getUsages(info) + | + info order by usages desc, info + ) + } + + /** + * Holds if there exists an API with \`apiName\` that is being used \`usages\` times + * and if it is in the top results (guarded by resultLimit). + */ + predicate restrict(string apiName, int usages) { + usages = getUsages(apiName) and + getOrder(apiName) <= resultLimit() + } +} +`, + }, +}; diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/query.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/query.ts new file mode 100644 index 000000000..72f239529 --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/query.ts @@ -0,0 +1,6 @@ +export type Query = { + mainQuery: string; + dependencies?: { + [filename: string]: string; + }; +}; diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts index 43a68cb15..21a756e00 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts @@ -8,8 +8,10 @@ import { DatabaseKind } from "../../../../src/local-databases"; import * as queryResolver from "../../../../src/contextual/queryResolver"; import { file } from "tmp-promise"; import { QueryResultType } from "../../../../src/pure/new-messages"; -import { readFile } from "fs-extra"; +import { readdir, readFile } from "fs-extra"; import { load } from "js-yaml"; +import { dirname, join } from "path"; +import { fetchExternalApisQuery } from "../../../../src/data-extensions-editor/queries/java"; function createMockUri(path = "/a/b/c/foo"): Uri { return { @@ -39,11 +41,6 @@ describe("runQuery", () => { resolveQlpacks: jest.fn().mockResolvedValue({ "my/java-extensions": "/a/b/c/", }), - resolveQueriesInSuite: jest - .fn() - .mockResolvedValue([ - "/home/github/codeql/java/ql/src/Telemetry/FetchExternalAPIs.ql", - ]), }, queryRunner: { createQueryRun: jest.fn().mockReturnValue({ @@ -56,7 +53,6 @@ describe("runQuery", () => { }), logger: createMockLogger(), }, - logger: createMockLogger(), databaseItem: { databaseUri: createMockUri("/a/b/c/src.zip"), contents: { @@ -77,37 +73,12 @@ describe("runQuery", () => { expect(result?.resultType).toEqual(QueryResultType.SUCCESS); - expect(options.cliServer.resolveQueriesInSuite).toHaveBeenCalledWith( - expect.anything(), - [], - ); - const suiteFile = options.cliServer.resolveQueriesInSuite.mock.calls[0][0]; - const suiteFileContents = await readFile(suiteFile, "utf8"); - const suiteYaml = load(suiteFileContents); - expect(suiteYaml).toEqual([ - { - from: "codeql/java-all", - queries: ".", - include: { - id: "java/telemetry/fetch-external-apis", - }, - }, - { - from: "codeql/java-queries", - queries: ".", - include: { - id: "java/telemetry/fetch-external-apis", - }, - }, - ]); - expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1); expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true); expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith( "/a/b/c/src.zip", { - queryPath: - "/home/github/codeql/java/ql/src/Telemetry/FetchExternalAPIs.ql", + queryPath: expect.stringMatching(/FetchExternalApis\.ql/), quickEvalPosition: undefined, }, false, @@ -117,6 +88,40 @@ describe("runQuery", () => { undefined, undefined, ); + + const queryPath = + options.queryRunner.createQueryRun.mock.calls[0][1].queryPath; + const queryDirectory = dirname(queryPath); + + const queryFiles = await readdir(queryDirectory); + expect(queryFiles.sort()).toEqual( + ["codeql-pack.yml", "FetchExternalApis.ql", "ExternalApi.qll"].sort(), + ); + + const suiteFileContents = await readFile( + join(queryDirectory, "codeql-pack.yml"), + "utf8", + ); + const suiteYaml = load(suiteFileContents); + expect(suiteYaml).toEqual({ + name: "codeql/external-api-usage", + version: "0.0.0", + dependencies: { + "codeql/java-all": "*", + }, + }); + + expect( + await readFile(join(queryDirectory, "FetchExternalApis.ql"), "utf8"), + ).toEqual(fetchExternalApisQuery.mainQuery); + + for (const [filename, contents] of Object.entries( + fetchExternalApisQuery.dependencies ?? {}, + )) { + expect(await readFile(join(queryDirectory, filename), "utf8")).toEqual( + contents, + ); + } }); }); From 7e08bb9efebf524cceed642b4f26dfec035a1251 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Thu, 13 Apr 2023 13:11:35 +0100 Subject: [PATCH 12/40] Introduce mapping layer for variant analysis history items --- .../store/query-history-domain-mapper.ts | 5 +- .../store/query-history-dto-mapper.ts | 5 +- .../query-history-variant-analysis-dto.ts | 63 +++-- .../store/variant-analysis-domain-mapper.ts | 235 ++++++++++++++++ .../store/variant-analysis-dto-mapper.ts | 253 ++++++++++++++++++ 5 files changed, 538 insertions(+), 23 deletions(-) create mode 100644 extensions/ql-vscode/src/query-history/store/variant-analysis-domain-mapper.ts create mode 100644 extensions/ql-vscode/src/query-history/store/variant-analysis-dto-mapper.ts diff --git a/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts index 05579f224..55a54b313 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts @@ -15,20 +15,19 @@ import { SortDirectionDto, } from "./query-history-local-query-dto"; import { QueryHistoryItemDto } from "./query-history-dto"; -import { QueryHistoryVariantAnalysisDto } from "./query-history-variant-analysis-dto"; import { RawResultsSortState, SortDirection, SortedResultSetInfo, } from "../../pure/interface-types"; +import { mapQueryHistoryVariantAnalysisToDto } from "./variant-analysis-domain-mapper"; export function mapQueryHistoryToDto( queries: QueryHistoryInfo[], ): QueryHistoryItemDto[] { return queries.map((q) => { if (q.t === "variant-analysis") { - const query: QueryHistoryVariantAnalysisDto = q; - return query; + return mapQueryHistoryVariantAnalysisToDto(q); } else if (q.t === "local") { return mapLocalQueryInfoToDto(q); } else { diff --git a/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts index 22bbdf6ba..fc91b1114 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts @@ -5,7 +5,6 @@ import { } from "../../query-results"; import { QueryEvaluationInfo } from "../../run-queries-shared"; import { QueryHistoryInfo } from "../query-history-info"; -import { VariantAnalysisHistoryItem } from "../variant-analysis-history-item"; import { CompletedQueryInfoDto, QueryEvaluationInfoDto, @@ -23,14 +22,14 @@ import { SortDirection, SortedResultSetInfo, } from "../../pure/interface-types"; +import { mapQueryHistoryVariantAnalysisToDomainModel } from "./variant-analysis-dto-mapper"; export function mapQueryHistoryToDomainModel( queries: QueryHistoryItemDto[], ): QueryHistoryInfo[] { return queries.map((d) => { if (d.t === "variant-analysis") { - const query: VariantAnalysisHistoryItem = d; - return query; + return mapQueryHistoryVariantAnalysisToDomainModel(d); } else if (d.t === "local") { return mapLocalQueryItemToDomainModel(d); } diff --git a/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto.ts b/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto.ts index 06c79b542..786545100 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto.ts @@ -1,27 +1,17 @@ // Contains models and consts for the data we want to store in the query history store. // Changes to these models should be done carefully and account for backwards compatibility of data. -import { QueryLanguage } from "../../common/query-language"; -import { QueryStatus } from "../../query-status"; -import { - VariantAnalysisFailureReason, - VariantAnalysisRepoStatus, - VariantAnalysisStatus, -} from "../../variant-analysis/shared/variant-analysis"; - -// All data points are modelled, except enums. - export interface QueryHistoryVariantAnalysisDto { readonly t: "variant-analysis"; failureReason?: string; resultCount?: number; - status: QueryStatus; + status: QueryStatusDto; completed: boolean; - variantAnalysis: VariantAnalysisQueryHistoryDto; + variantAnalysis: VariantAnalysisDto; userSpecifiedLabel?: string; } -export interface VariantAnalysisQueryHistoryDto { +export interface VariantAnalysisDto { id: number; controllerRepo: { id: number; @@ -31,7 +21,7 @@ export interface VariantAnalysisQueryHistoryDto { query: { name: string; filePath: string; - language: QueryLanguage; + language: QueryLanguageDto; text: string; }; databases: { @@ -42,10 +32,10 @@ export interface VariantAnalysisQueryHistoryDto { createdAt: string; updatedAt: string; executionStartTime: number; - status: VariantAnalysisStatus; + status: VariantAnalysisStatusDto; completedAt?: string; actionsWorkflowRunId?: number; - failureReason?: VariantAnalysisFailureReason; + failureReason?: VariantAnalysisFailureReasonDto; scannedRepos?: VariantAnalysisScannedRepositoryDto[]; skippedRepos?: VariantAnalysisSkippedRepositoriesDto; } @@ -58,7 +48,7 @@ export interface VariantAnalysisScannedRepositoryDto { stargazersCount: number; updatedAt: string | null; }; - analysisStatus: VariantAnalysisRepoStatus; + analysisStatus: VariantAnalysisRepoStatusDto; resultCount?: number; artifactSizeInBytes?: number; failureMessage?: string; @@ -83,3 +73,42 @@ export interface VariantAnalysisSkippedRepositoryDto { stargazersCount?: number; updatedAt?: string | null; } + +export enum VariantAnalysisFailureReasonDto { + NoReposQueried = "noReposQueried", + ActionsWorkflowRunFailed = "actionsWorkflowRunFailed", + InternalError = "internalError", +} + +export enum VariantAnalysisRepoStatusDto { + Pending = "pending", + InProgress = "inProgress", + Succeeded = "succeeded", + Failed = "failed", + Canceled = "canceled", + TimedOut = "timedOut", +} + +export enum VariantAnalysisStatusDto { + InProgress = "inProgress", + Succeeded = "succeeded", + Failed = "failed", + Canceled = "canceled", +} + +export enum QueryLanguageDto { + CSharp = "csharp", + Cpp = "cpp", + Go = "go", + Java = "java", + Javascript = "javascript", + Python = "python", + Ruby = "ruby", + Swift = "swift", +} + +export enum QueryStatusDto { + InProgress = "InProgress", + Completed = "Completed", + Failed = "Failed", +} diff --git a/extensions/ql-vscode/src/query-history/store/variant-analysis-domain-mapper.ts b/extensions/ql-vscode/src/query-history/store/variant-analysis-domain-mapper.ts new file mode 100644 index 000000000..24b22582b --- /dev/null +++ b/extensions/ql-vscode/src/query-history/store/variant-analysis-domain-mapper.ts @@ -0,0 +1,235 @@ +import { + QueryHistoryVariantAnalysisDto, + QueryLanguageDto, + QueryStatusDto, + VariantAnalysisDto, + VariantAnalysisFailureReasonDto, + VariantAnalysisRepoStatusDto, + VariantAnalysisScannedRepositoryDto, + VariantAnalysisSkippedRepositoriesDto, + VariantAnalysisSkippedRepositoryDto, + VariantAnalysisSkippedRepositoryGroupDto, + VariantAnalysisStatusDto, +} from "./query-history-variant-analysis-dto"; +import { + VariantAnalysis, + VariantAnalysisFailureReason, + VariantAnalysisRepoStatus, + VariantAnalysisScannedRepository, + VariantAnalysisSkippedRepositories, + VariantAnalysisSkippedRepository, + VariantAnalysisSkippedRepositoryGroup, + VariantAnalysisStatus, +} from "../../variant-analysis/shared/variant-analysis"; +import { assertNever } from "../../pure/helpers-pure"; +import { QueryLanguage } from "../../common/query-language"; +import { QueryStatus } from "../../query-status"; +import { VariantAnalysisHistoryItem } from "../variant-analysis-history-item"; + +export function mapQueryHistoryVariantAnalysisToDto( + item: VariantAnalysisHistoryItem, +): QueryHistoryVariantAnalysisDto { + return { + t: "variant-analysis", + failureReason: item.failureReason, + resultCount: item.resultCount, + status: mapQueryStatusToDto(item.status), + completed: item.completed, + variantAnalysis: mapVariantAnalysisDtoToDto(item.variantAnalysis), + userSpecifiedLabel: item.userSpecifiedLabel, + }; +} + +function mapVariantAnalysisDtoToDto( + variantAnalysis: VariantAnalysis, +): VariantAnalysisDto { + return { + id: variantAnalysis.id, + controllerRepo: { + id: variantAnalysis.controllerRepo.id, + fullName: variantAnalysis.controllerRepo.fullName, + private: variantAnalysis.controllerRepo.private, + }, + query: { + name: variantAnalysis.query.name, + filePath: variantAnalysis.query.filePath, + language: mapQueryLanguageToDto(variantAnalysis.query.language), + text: variantAnalysis.query.text, + }, + databases: { + repositories: variantAnalysis.databases.repositories, + repositoryLists: variantAnalysis.databases.repositoryLists, + repositoryOwners: variantAnalysis.databases.repositoryOwners, + }, + createdAt: variantAnalysis.createdAt, + updatedAt: variantAnalysis.updatedAt, + executionStartTime: variantAnalysis.executionStartTime, + status: mapVariantAnalysisStatusToDto(variantAnalysis.status), + completedAt: variantAnalysis.completedAt, + actionsWorkflowRunId: variantAnalysis.actionsWorkflowRunId, + failureReason: + variantAnalysis.failureReason && + mapVariantAnalysisFailureReasonToDto(variantAnalysis.failureReason), + scannedRepos: + variantAnalysis.scannedRepos && + mapVariantAnalysisScannedRepositoriesToDto(variantAnalysis.scannedRepos), + skippedRepos: + variantAnalysis.skippedRepos && + mapVariantAnalysisSkippedRepositoriesToDto(variantAnalysis.skippedRepos), + }; +} + +function mapVariantAnalysisScannedRepositoriesToDto( + repos: VariantAnalysisScannedRepository[], +): VariantAnalysisScannedRepositoryDto[] { + return repos.map(mapVariantAnalysisScannedRepositoryToDto); +} + +function mapVariantAnalysisScannedRepositoryToDto( + repo: VariantAnalysisScannedRepository, +): VariantAnalysisScannedRepositoryDto { + return { + repository: { + id: repo.repository.id, + fullName: repo.repository.fullName, + private: repo.repository.private, + stargazersCount: repo.repository.stargazersCount, + updatedAt: repo.repository.updatedAt, + }, + analysisStatus: mapVariantAnalysisRepoStatusToDto(repo.analysisStatus), + resultCount: repo.resultCount, + artifactSizeInBytes: repo.artifactSizeInBytes, + failureMessage: repo.failureMessage, + }; +} + +function mapVariantAnalysisSkippedRepositoriesToDto( + repos: VariantAnalysisSkippedRepositories, +): VariantAnalysisSkippedRepositoriesDto { + return { + accessMismatchRepos: + repos.accessMismatchRepos && + mapVariantAnalysisSkippedRepositoryGroupToDto(repos.accessMismatchRepos), + notFoundRepos: + repos.notFoundRepos && + mapVariantAnalysisSkippedRepositoryGroupToDto(repos.notFoundRepos), + noCodeqlDbRepos: + repos.noCodeqlDbRepos && + mapVariantAnalysisSkippedRepositoryGroupToDto(repos.noCodeqlDbRepos), + overLimitRepos: + repos.overLimitRepos && + mapVariantAnalysisSkippedRepositoryGroupToDto(repos.overLimitRepos), + }; +} + +function mapVariantAnalysisSkippedRepositoryGroupToDto( + repoGroup: VariantAnalysisSkippedRepositoryGroup, +): VariantAnalysisSkippedRepositoryGroupDto { + return { + repositoryCount: repoGroup.repositoryCount, + repositories: repoGroup.repositories.map( + mapVariantAnalysisSkippedRepositoryToDto, + ), + }; +} + +function mapVariantAnalysisSkippedRepositoryToDto( + repo: VariantAnalysisSkippedRepository, +): VariantAnalysisSkippedRepositoryDto { + return { + id: repo.id, + fullName: repo.fullName, + private: repo.private, + stargazersCount: repo.stargazersCount, + updatedAt: repo.updatedAt, + }; +} + +function mapVariantAnalysisFailureReasonToDto( + failureReason: VariantAnalysisFailureReason, +): VariantAnalysisFailureReasonDto { + switch (failureReason) { + case VariantAnalysisFailureReason.NoReposQueried: + return VariantAnalysisFailureReasonDto.NoReposQueried; + case VariantAnalysisFailureReason.ActionsWorkflowRunFailed: + return VariantAnalysisFailureReasonDto.ActionsWorkflowRunFailed; + case VariantAnalysisFailureReason.InternalError: + return VariantAnalysisFailureReasonDto.InternalError; + default: + assertNever(failureReason); + } +} + +function mapVariantAnalysisRepoStatusToDto( + status: VariantAnalysisRepoStatus, +): VariantAnalysisRepoStatusDto { + switch (status) { + case VariantAnalysisRepoStatus.Pending: + return VariantAnalysisRepoStatusDto.Pending; + case VariantAnalysisRepoStatus.InProgress: + return VariantAnalysisRepoStatusDto.InProgress; + case VariantAnalysisRepoStatus.Succeeded: + return VariantAnalysisRepoStatusDto.Succeeded; + case VariantAnalysisRepoStatus.Failed: + return VariantAnalysisRepoStatusDto.Failed; + case VariantAnalysisRepoStatus.Canceled: + return VariantAnalysisRepoStatusDto.Canceled; + case VariantAnalysisRepoStatus.TimedOut: + return VariantAnalysisRepoStatusDto.TimedOut; + default: + assertNever(status); + } +} + +function mapVariantAnalysisStatusToDto( + status: VariantAnalysisStatus, +): VariantAnalysisStatusDto { + switch (status) { + case VariantAnalysisStatus.InProgress: + return VariantAnalysisStatusDto.InProgress; + case VariantAnalysisStatus.Succeeded: + return VariantAnalysisStatusDto.Succeeded; + case VariantAnalysisStatus.Failed: + return VariantAnalysisStatusDto.Failed; + case VariantAnalysisStatus.Canceled: + return VariantAnalysisStatusDto.Canceled; + default: + assertNever(status); + } +} + +function mapQueryLanguageToDto(language: QueryLanguage): QueryLanguageDto { + switch (language) { + case QueryLanguage.CSharp: + return QueryLanguageDto.CSharp; + case QueryLanguage.Cpp: + return QueryLanguageDto.Cpp; + case QueryLanguage.Go: + return QueryLanguageDto.Go; + case QueryLanguage.Java: + return QueryLanguageDto.Java; + case QueryLanguage.Javascript: + return QueryLanguageDto.Javascript; + case QueryLanguage.Python: + return QueryLanguageDto.Python; + case QueryLanguage.Ruby: + return QueryLanguageDto.Ruby; + case QueryLanguage.Swift: + return QueryLanguageDto.Swift; + default: + assertNever(language); + } +} + +function mapQueryStatusToDto(status: QueryStatus): QueryStatusDto { + switch (status) { + case QueryStatus.InProgress: + return QueryStatusDto.InProgress; + case QueryStatus.Completed: + return QueryStatusDto.Completed; + case QueryStatus.Failed: + return QueryStatusDto.Failed; + default: + assertNever(status); + } +} diff --git a/extensions/ql-vscode/src/query-history/store/variant-analysis-dto-mapper.ts b/extensions/ql-vscode/src/query-history/store/variant-analysis-dto-mapper.ts new file mode 100644 index 000000000..4f6931f21 --- /dev/null +++ b/extensions/ql-vscode/src/query-history/store/variant-analysis-dto-mapper.ts @@ -0,0 +1,253 @@ +import { + QueryHistoryVariantAnalysisDto, + QueryLanguageDto, + QueryStatusDto, + VariantAnalysisDto, + VariantAnalysisFailureReasonDto, + VariantAnalysisRepoStatusDto, + VariantAnalysisScannedRepositoryDto, + VariantAnalysisSkippedRepositoriesDto, + VariantAnalysisSkippedRepositoryDto, + VariantAnalysisSkippedRepositoryGroupDto, + VariantAnalysisStatusDto, +} from "./query-history-variant-analysis-dto"; +import { + VariantAnalysis, + VariantAnalysisFailureReason, + VariantAnalysisRepoStatus, + VariantAnalysisScannedRepository, + VariantAnalysisSkippedRepositories, + VariantAnalysisSkippedRepository, + VariantAnalysisSkippedRepositoryGroup, + VariantAnalysisStatus, +} from "../../variant-analysis/shared/variant-analysis"; +import { assertNever } from "../../pure/helpers-pure"; +import { QueryLanguage } from "../../common/query-language"; +import { QueryStatus } from "../../query-status"; +import { VariantAnalysisHistoryItem } from "../variant-analysis-history-item"; + +export function mapQueryHistoryVariantAnalysisToDomainModel( + item: QueryHistoryVariantAnalysisDto, +): VariantAnalysisHistoryItem { + return { + t: "variant-analysis", + failureReason: item.failureReason, + resultCount: item.resultCount, + status: mapQueryStatusToDomainModel(item.status), + completed: item.completed, + variantAnalysis: mapVariantAnalysisDtoToDomainModel(item.variantAnalysis), + userSpecifiedLabel: item.userSpecifiedLabel, + }; +} + +function mapVariantAnalysisDtoToDomainModel( + variantAnalysis: VariantAnalysisDto, +): VariantAnalysis { + return { + id: variantAnalysis.id, + controllerRepo: { + id: variantAnalysis.controllerRepo.id, + fullName: variantAnalysis.controllerRepo.fullName, + private: variantAnalysis.controllerRepo.private, + }, + query: { + name: variantAnalysis.query.name, + filePath: variantAnalysis.query.filePath, + language: mapQueryLanguageToDomainModel(variantAnalysis.query.language), + text: variantAnalysis.query.text, + }, + databases: { + repositories: variantAnalysis.databases.repositories, + repositoryLists: variantAnalysis.databases.repositoryLists, + repositoryOwners: variantAnalysis.databases.repositoryOwners, + }, + createdAt: variantAnalysis.createdAt, + updatedAt: variantAnalysis.updatedAt, + executionStartTime: variantAnalysis.executionStartTime, + status: mapVariantAnalysisStatusToDomainModel(variantAnalysis.status), + completedAt: variantAnalysis.completedAt, + actionsWorkflowRunId: variantAnalysis.actionsWorkflowRunId, + failureReason: + variantAnalysis.failureReason && + mapVariantAnalysisFailureReasonToDomainModel( + variantAnalysis.failureReason, + ), + scannedRepos: + variantAnalysis.scannedRepos && + mapVariantAnalysisScannedRepositoriesToDomainModel( + variantAnalysis.scannedRepos, + ), + skippedRepos: + variantAnalysis.skippedRepos && + mapVariantAnalysisSkippedRepositoriesToDomainModel( + variantAnalysis.skippedRepos, + ), + }; +} + +function mapVariantAnalysisScannedRepositoriesToDomainModel( + repos: VariantAnalysisScannedRepositoryDto[], +): VariantAnalysisScannedRepository[] { + return repos.map(mapVariantAnalysisScannedRepositoryToDomainModel); +} + +function mapVariantAnalysisScannedRepositoryToDomainModel( + repo: VariantAnalysisScannedRepositoryDto, +): VariantAnalysisScannedRepository { + return { + repository: { + id: repo.repository.id, + fullName: repo.repository.fullName, + private: repo.repository.private, + stargazersCount: repo.repository.stargazersCount, + updatedAt: repo.repository.updatedAt, + }, + analysisStatus: mapVariantAnalysisRepoStatusToDomainModel( + repo.analysisStatus, + ), + resultCount: repo.resultCount, + artifactSizeInBytes: repo.artifactSizeInBytes, + failureMessage: repo.failureMessage, + }; +} + +function mapVariantAnalysisSkippedRepositoriesToDomainModel( + repos: VariantAnalysisSkippedRepositoriesDto, +): VariantAnalysisSkippedRepositories { + return { + accessMismatchRepos: + repos.accessMismatchRepos && + mapVariantAnalysisSkippedRepositoryGroupToDomainModel( + repos.accessMismatchRepos, + ), + notFoundRepos: + repos.notFoundRepos && + mapVariantAnalysisSkippedRepositoryGroupToDomainModel( + repos.notFoundRepos, + ), + noCodeqlDbRepos: + repos.noCodeqlDbRepos && + mapVariantAnalysisSkippedRepositoryGroupToDomainModel( + repos.noCodeqlDbRepos, + ), + overLimitRepos: + repos.overLimitRepos && + mapVariantAnalysisSkippedRepositoryGroupToDomainModel( + repos.overLimitRepos, + ), + }; +} + +function mapVariantAnalysisSkippedRepositoryGroupToDomainModel( + repoGroup: VariantAnalysisSkippedRepositoryGroupDto, +): VariantAnalysisSkippedRepositoryGroup { + return { + repositoryCount: repoGroup.repositoryCount, + repositories: repoGroup.repositories.map( + mapVariantAnalysisSkippedRepositoryToDomainModel, + ), + }; +} + +function mapVariantAnalysisSkippedRepositoryToDomainModel( + repo: VariantAnalysisSkippedRepositoryDto, +): VariantAnalysisSkippedRepository { + return { + id: repo.id, + fullName: repo.fullName, + private: repo.private, + stargazersCount: repo.stargazersCount, + updatedAt: repo.updatedAt, + }; +} + +function mapVariantAnalysisFailureReasonToDomainModel( + failureReason: VariantAnalysisFailureReasonDto, +): VariantAnalysisFailureReason { + switch (failureReason) { + case VariantAnalysisFailureReasonDto.NoReposQueried: + return VariantAnalysisFailureReason.NoReposQueried; + case VariantAnalysisFailureReasonDto.ActionsWorkflowRunFailed: + return VariantAnalysisFailureReason.ActionsWorkflowRunFailed; + case VariantAnalysisFailureReasonDto.InternalError: + return VariantAnalysisFailureReason.InternalError; + default: + assertNever(failureReason); + } +} + +function mapVariantAnalysisRepoStatusToDomainModel( + status: VariantAnalysisRepoStatusDto, +): VariantAnalysisRepoStatus { + switch (status) { + case VariantAnalysisRepoStatusDto.Pending: + return VariantAnalysisRepoStatus.Pending; + case VariantAnalysisRepoStatusDto.InProgress: + return VariantAnalysisRepoStatus.InProgress; + case VariantAnalysisRepoStatusDto.Succeeded: + return VariantAnalysisRepoStatus.Succeeded; + case VariantAnalysisRepoStatusDto.Failed: + return VariantAnalysisRepoStatus.Failed; + case VariantAnalysisRepoStatusDto.Canceled: + return VariantAnalysisRepoStatus.Canceled; + case VariantAnalysisRepoStatusDto.TimedOut: + return VariantAnalysisRepoStatus.TimedOut; + default: + assertNever(status); + } +} + +function mapVariantAnalysisStatusToDomainModel( + status: VariantAnalysisStatusDto, +): VariantAnalysisStatus { + switch (status) { + case VariantAnalysisStatusDto.InProgress: + return VariantAnalysisStatus.InProgress; + case VariantAnalysisStatusDto.Succeeded: + return VariantAnalysisStatus.Succeeded; + case VariantAnalysisStatusDto.Failed: + return VariantAnalysisStatus.Failed; + case VariantAnalysisStatusDto.Canceled: + return VariantAnalysisStatus.Canceled; + default: + assertNever(status); + } +} + +function mapQueryLanguageToDomainModel( + language: QueryLanguageDto, +): QueryLanguage { + switch (language) { + case QueryLanguageDto.CSharp: + return QueryLanguage.CSharp; + case QueryLanguageDto.Cpp: + return QueryLanguage.Cpp; + case QueryLanguageDto.Go: + return QueryLanguage.Go; + case QueryLanguageDto.Java: + return QueryLanguage.Java; + case QueryLanguageDto.Javascript: + return QueryLanguage.Javascript; + case QueryLanguageDto.Python: + return QueryLanguage.Python; + case QueryLanguageDto.Ruby: + return QueryLanguage.Ruby; + case QueryLanguageDto.Swift: + return QueryLanguage.Swift; + default: + assertNever(language); + } +} + +function mapQueryStatusToDomainModel(status: QueryStatusDto): QueryStatus { + switch (status) { + case QueryStatusDto.InProgress: + return QueryStatus.InProgress; + case QueryStatusDto.Completed: + return QueryStatus.Completed; + case QueryStatusDto.Failed: + return QueryStatus.Failed; + default: + assertNever(status); + } +} From 0a304f633a7f2be35182f962e434eb5c6a1540b8 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Thu, 13 Apr 2023 15:01:43 +0200 Subject: [PATCH 13/40] Use enum for query texts --- .../src/data-extensions-editor/external-api-usage-query.ts | 3 ++- .../ql-vscode/src/data-extensions-editor/queries/index.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts index 7ad472d31..2f0b28561 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts @@ -15,6 +15,7 @@ import { fetchExternalApiQueries } from "./queries"; import { QueryResultType } from "../pure/new-messages"; import { join } from "path"; import { redactableError } from "../pure/errors"; +import { QueryLanguage } from "../common/query-language"; export type RunQueryOptions = { cliServer: Pick; @@ -40,7 +41,7 @@ export async function runQuery({ // For a reference of what this should do in the future, see the previous implementation in // https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72 - const query = fetchExternalApiQueries[databaseItem.language]; + const query = fetchExternalApiQueries[databaseItem.language as QueryLanguage]; if (!query) { void showAndLogExceptionWithTelemetry( redactableError`No external API usage query found for language ${databaseItem.language}`, diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts index 7f5864bb9..5f864ff03 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts @@ -1,6 +1,7 @@ import { fetchExternalApisQuery as javaFetchExternalApisQuery } from "./java"; import { Query } from "./query"; +import { QueryLanguage } from "../../common/query-language"; -export const fetchExternalApiQueries: Record = { - java: javaFetchExternalApisQuery, +export const fetchExternalApiQueries: Partial> = { + [QueryLanguage.Java]: javaFetchExternalApisQuery, }; From 64af86c6e340d62ccf511acb2108c25f214e92f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 13:58:06 +0000 Subject: [PATCH 14/40] Bump glob from 9.3.2 to 10.0.0 in /extensions/ql-vscode Bumps [glob](https://github.com/isaacs/node-glob) from 9.3.2 to 10.0.0. - [Release notes](https://github.com/isaacs/node-glob/releases) - [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md) - [Commits](https://github.com/isaacs/node-glob/compare/v9.3.2...v10.0.0) --- updated-dependencies: - dependency-name: glob dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- extensions/ql-vscode/package-lock.json | 98 +++++++++++++------------- extensions/ql-vscode/package.json | 2 +- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/extensions/ql-vscode/package-lock.json b/extensions/ql-vscode/package-lock.json index d2fcb8576..27ddbc827 100644 --- a/extensions/ql-vscode/package-lock.json +++ b/extensions/ql-vscode/package-lock.json @@ -117,7 +117,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.6.4", "file-loader": "^6.2.0", - "glob": "^9.3.2", + "glob": "^10.0.0", "gulp": "^4.0.2", "gulp-esbuild": "^0.10.5", "gulp-replace": "^1.1.3", @@ -27769,15 +27769,15 @@ "dev": true }, "node_modules/glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.0.0.tgz", + "integrity": "sha512-zmp9ZDC6NpDNLujV2W2n+3lH+BafIVZ4/ct+Yj3BMZTH/+bgm/eVjHzeFLwxJrrIGgjjS2eiQLlpurHsNlEAtQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" + "minimatch": "^9.0.0", + "minipass": "^5.0.0", + "path-scurry": "^1.6.4" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -28008,24 +28008,24 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob/node_modules/minipass": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", - "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, "engines": { "node": ">=8" @@ -38337,13 +38337,13 @@ } }, "node_modules/path-scurry": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.3.tgz", - "integrity": "sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.4.tgz", + "integrity": "sha512-Qp/9IHkdNiXJ3/Kon++At2nVpnhRiPq/aSvQN+H3U1WZbvNRK0RIQK/o4HMqPoXjpuGJUEWpHSs6Mnjxqh3TQg==", "dev": true, "dependencies": { - "lru-cache": "^7.14.1", - "minipass": "^4.0.2" + "lru-cache": "^9.0.0", + "minipass": "^5.0.0" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -38353,18 +38353,18 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.0.1.tgz", + "integrity": "sha512-C8QsKIN1UIXeOs3iWmiZ1lQY+EnKDojWd37fXy1aSbJvH4iSma1uy2OWuoB3m4SYRli5+CUjDv3Dij5DVoetmg==", "dev": true, "engines": { - "node": ">=12" + "node": "14 || >=16.14" } }, "node_modules/path-scurry/node_modules/minipass": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", - "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, "engines": { "node": ">=8" @@ -66400,15 +66400,15 @@ "dev": true }, "glob": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.2.tgz", - "integrity": "sha512-BTv/JhKXFEHsErMte/AnfiSv8yYOLLiyH2lTg8vn02O21zWFgHPTfxtgn1QRe7NRgggUhC8hacR2Re94svHqeA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.0.0.tgz", + "integrity": "sha512-zmp9ZDC6NpDNLujV2W2n+3lH+BafIVZ4/ct+Yj3BMZTH/+bgm/eVjHzeFLwxJrrIGgjjS2eiQLlpurHsNlEAtQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", - "minimatch": "^7.4.1", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" + "minimatch": "^9.0.0", + "minipass": "^5.0.0", + "path-scurry": "^1.6.4" }, "dependencies": { "brace-expansion": { @@ -66421,18 +66421,18 @@ } }, "minimatch": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.3.tgz", - "integrity": "sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } }, "minipass": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", - "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true } } @@ -74480,25 +74480,25 @@ "dev": true }, "path-scurry": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.3.tgz", - "integrity": "sha512-RAmB+n30SlN+HnNx6EbcpoDy9nwdpcGPnEKrJnu6GZoDWBdIjo1UQMVtW2ybtC7LC2oKLcMq8y5g8WnKLiod9g==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.6.4.tgz", + "integrity": "sha512-Qp/9IHkdNiXJ3/Kon++At2nVpnhRiPq/aSvQN+H3U1WZbvNRK0RIQK/o4HMqPoXjpuGJUEWpHSs6Mnjxqh3TQg==", "dev": true, "requires": { - "lru-cache": "^7.14.1", - "minipass": "^4.0.2" + "lru-cache": "^9.0.0", + "minipass": "^5.0.0" }, "dependencies": { "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.0.1.tgz", + "integrity": "sha512-C8QsKIN1UIXeOs3iWmiZ1lQY+EnKDojWd37fXy1aSbJvH4iSma1uy2OWuoB3m4SYRli5+CUjDv3Dij5DVoetmg==", "dev": true }, "minipass": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", - "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true } } diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index 5bcec64f3..29ef072a7 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -1555,7 +1555,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.6.4", "file-loader": "^6.2.0", - "glob": "^9.3.2", + "glob": "^10.0.0", "gulp": "^4.0.2", "gulp-esbuild": "^0.10.5", "gulp-replace": "^1.1.3", From 1c90fd1353a962ea7c63f5c8dffd73a313c37aa0 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Thu, 13 Apr 2023 14:54:08 +0000 Subject: [PATCH 15/40] Rename variant analysis data and domain mappers to follow convention --- .../src/query-history/store/query-history-domain-mapper.ts | 2 +- .../src/query-history/store/query-history-dto-mapper.ts | 2 +- ...apper.ts => query-history-variant-analysis-domain-mapper.ts} | 0 ...o-mapper.ts => query-history-variant-analysis-dto-mapper.ts} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename extensions/ql-vscode/src/query-history/store/{variant-analysis-domain-mapper.ts => query-history-variant-analysis-domain-mapper.ts} (100%) rename extensions/ql-vscode/src/query-history/store/{variant-analysis-dto-mapper.ts => query-history-variant-analysis-dto-mapper.ts} (100%) diff --git a/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts index 55a54b313..3c1239dac 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts @@ -20,7 +20,7 @@ import { SortDirection, SortedResultSetInfo, } from "../../pure/interface-types"; -import { mapQueryHistoryVariantAnalysisToDto } from "./variant-analysis-domain-mapper"; +import { mapQueryHistoryVariantAnalysisToDto } from "./query-history-variant-analysis-domain-mapper"; export function mapQueryHistoryToDto( queries: QueryHistoryInfo[], diff --git a/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts index fc91b1114..a2f915d2c 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts @@ -22,7 +22,7 @@ import { SortDirection, SortedResultSetInfo, } from "../../pure/interface-types"; -import { mapQueryHistoryVariantAnalysisToDomainModel } from "./variant-analysis-dto-mapper"; +import { mapQueryHistoryVariantAnalysisToDomainModel } from "./query-history-variant-analysis-dto-mapper"; export function mapQueryHistoryToDomainModel( queries: QueryHistoryItemDto[], diff --git a/extensions/ql-vscode/src/query-history/store/variant-analysis-domain-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-domain-mapper.ts similarity index 100% rename from extensions/ql-vscode/src/query-history/store/variant-analysis-domain-mapper.ts rename to extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-domain-mapper.ts diff --git a/extensions/ql-vscode/src/query-history/store/variant-analysis-dto-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto-mapper.ts similarity index 100% rename from extensions/ql-vscode/src/query-history/store/variant-analysis-dto-mapper.ts rename to extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto-mapper.ts From 5634c1ee513d52043fd4e2b361e96f8bf1739088 Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Thu, 13 Apr 2023 11:55:50 +0000 Subject: [PATCH 16/40] Rename `getFirstStoragePath` -> `getFirstWorkspaceFolder` --- extensions/ql-vscode/src/helpers.ts | 2 +- extensions/ql-vscode/src/local-databases.ts | 4 ++-- extensions/ql-vscode/src/skeleton-query-wizard.ts | 4 ++-- .../test/vscode-tests/no-workspace/helpers.test.ts | 12 +++++++----- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index 06989e8fd..4a62c4403 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -786,7 +786,7 @@ export async function* walkDirectory( } } -export function getFirstStoragePath() { +export function getFirstWorkspaceFolder() { const workspaceFolders = workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { diff --git a/extensions/ql-vscode/src/local-databases.ts b/extensions/ql-vscode/src/local-databases.ts index 7dbdf3cf2..3797a1e9f 100644 --- a/extensions/ql-vscode/src/local-databases.ts +++ b/extensions/ql-vscode/src/local-databases.ts @@ -11,7 +11,7 @@ import { showAndLogExceptionWithTelemetry, isFolderAlreadyInWorkspace, showBinaryChoiceDialog, - getFirstStoragePath, + getFirstWorkspaceFolder, } from "./helpers"; import { ProgressCallback, withProgress } from "./progress"; import { @@ -677,7 +677,7 @@ export class DatabaseManager extends DisposableObject { } try { - const workspaceStorage = getFirstStoragePath(); + const workspaceStorage = getFirstWorkspaceFolder(); const qlPackGenerator = new QlPackGenerator( folderName, diff --git a/extensions/ql-vscode/src/skeleton-query-wizard.ts b/extensions/ql-vscode/src/skeleton-query-wizard.ts index 97eb90602..c470cf970 100644 --- a/extensions/ql-vscode/src/skeleton-query-wizard.ts +++ b/extensions/ql-vscode/src/skeleton-query-wizard.ts @@ -6,7 +6,7 @@ import { Credentials } from "./common/authentication"; import { QueryLanguage } from "./common/query-language"; import { askForLanguage, - getFirstStoragePath, + getFirstWorkspaceFolder, isFolderAlreadyInWorkspace, } from "./helpers"; import { getErrorMessage } from "./pure/helpers-pure"; @@ -54,7 +54,7 @@ export class SkeletonQueryWizard { return; } - this.qlPackStoragePath = getFirstStoragePath(); + this.qlPackStoragePath = getFirstWorkspaceFolder(); const skeletonPackAlreadyExists = isFolderAlreadyInWorkspace( this.folderName, diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts index d5afb122c..8a85e528c 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts @@ -26,7 +26,7 @@ import { import { DirResult } from "tmp"; import { - getFirstStoragePath, + getFirstWorkspaceFolder, getInitialQueryContents, InvocationRateLimiter, isFolderAlreadyInWorkspace, @@ -673,16 +673,16 @@ describe("prepareCodeTour", () => { }); }); -describe("getFirstStoragePath", () => { +describe("getFirstWorkspaceFolder", () => { it("should return the first workspace folder", async () => { jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([ { name: "codespaces-codeql", - uri: { fsPath: "codespaces-codeql" }, + uri: { fsPath: "codespaces-codeql", scheme: "file" }, }, ] as WorkspaceFolder[]); - expect(getFirstStoragePath()).toEqual("codespaces-codeql"); + expect(getFirstWorkspaceFolder()).toEqual("codespaces-codeql"); }); describe("if user is in vscode-codeql-starter workspace", () => { @@ -692,6 +692,7 @@ describe("getFirstStoragePath", () => { name: "codeql-custom-queries-cpp", uri: { fsPath: join("vscode-codeql-starter", "codeql-custom-queries-cpp"), + scheme: "file", }, }, { @@ -701,11 +702,12 @@ describe("getFirstStoragePath", () => { "vscode-codeql-starter", "codeql-custom-queries-csharp", ), + scheme: "file", }, }, ] as WorkspaceFolder[]); - expect(getFirstStoragePath()).toEqual("vscode-codeql-starter"); + expect(getFirstWorkspaceFolder()).toEqual("vscode-codeql-starter"); }); }); }); From b83d54f285d27e146df0d4d82d810137394f7bdc Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Thu, 13 Apr 2023 12:00:23 +0000 Subject: [PATCH 17/40] Add a JSDoc description for the method --- extensions/ql-vscode/src/helpers.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index 4a62c4403..047e7405f 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -786,6 +786,18 @@ export async function* walkDirectory( } } +/** + * Returns the path of the first folder in the workspace. + * This is used to decide where to create skeleton QL packs. + * + * If the first folder is a QL pack, then the parent folder is returned. + * This is because the vscode-codeql-starter repo contains a ql pack in + * the first folder. + * + * This is a temporary workaround until we can retire the + * vscode-codeql-starter repo. + */ + export function getFirstWorkspaceFolder() { const workspaceFolders = workspace.workspaceFolders; From 14d8593d58a357ce2a32b707a43e9b25b4156f33 Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Thu, 13 Apr 2023 14:44:06 +0000 Subject: [PATCH 18/40] Use `getOnDiskWorkspaceFolders` to fetch folders --- extensions/ql-vscode/src/helpers.ts | 5 ++--- .../cli-integration/skeleton-query-wizard.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index 047e7405f..d282a4e29 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -799,14 +799,13 @@ export async function* walkDirectory( */ export function getFirstWorkspaceFolder() { - const workspaceFolders = workspace.workspaceFolders; + const workspaceFolders = getOnDiskWorkspaceFolders(); if (!workspaceFolders || workspaceFolders.length === 0) { throw new Error("No workspace folders found"); } - const firstFolder = workspaceFolders[0]; - const firstFolderFsPath = firstFolder.uri.fsPath; + const firstFolderFsPath = workspaceFolders[0]; // For the vscode-codeql-starter repo, the first folder will be a ql pack // so we need to get the parent folder diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts index 2facce402..bfe02a0ac 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts @@ -83,11 +83,11 @@ describe("SkeletonQueryWizard", () => { jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValue([ { name: `codespaces-codeql`, - uri: { fsPath: storagePath }, + uri: { fsPath: storagePath, scheme: "file" }, }, { name: "/second/folder/path", - uri: { fsPath: storagePath }, + uri: { fsPath: storagePath, scheme: "file" }, }, ] as WorkspaceFolder[]); From b6eaf93dba421f18a426b9aba592c05acef041c0 Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Thu, 13 Apr 2023 14:45:32 +0000 Subject: [PATCH 19/40] Rename `workspaceStorage` -> `firstWorkspaceFolder` --- extensions/ql-vscode/src/local-databases.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/ql-vscode/src/local-databases.ts b/extensions/ql-vscode/src/local-databases.ts index 3797a1e9f..847cce3b4 100644 --- a/extensions/ql-vscode/src/local-databases.ts +++ b/extensions/ql-vscode/src/local-databases.ts @@ -677,13 +677,13 @@ export class DatabaseManager extends DisposableObject { } try { - const workspaceStorage = getFirstWorkspaceFolder(); + const firstWorkspaceFolder = getFirstWorkspaceFolder(); const qlPackGenerator = new QlPackGenerator( folderName, databaseItem.language as QueryLanguage, this.cli, - workspaceStorage, + firstWorkspaceFolder, ); await qlPackGenerator.generate(); } catch (e: unknown) { From 64e867d1c44c69cf1e4512768ec76fa199e137be Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Thu, 13 Apr 2023 17:09:10 -0400 Subject: [PATCH 20/40] Update `@types/vscode` and fix naming conflicts --- extensions/ql-vscode/package-lock.json | 14 +++++++------- extensions/ql-vscode/package.json | 2 +- extensions/ql-vscode/src/databases/ui/db-panel.ts | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/extensions/ql-vscode/package-lock.json b/extensions/ql-vscode/package-lock.json index cb456b097..d4270736d 100644 --- a/extensions/ql-vscode/package-lock.json +++ b/extensions/ql-vscode/package-lock.json @@ -93,7 +93,7 @@ "@types/through2": "^2.0.36", "@types/tmp": "^0.1.0", "@types/unzipper": "~0.10.1", - "@types/vscode": "^1.59.0", + "@types/vscode": "^1.67.0", "@types/webpack": "^5.28.0", "@types/webpack-env": "^1.18.0", "@types/xml2js": "~0.4.4", @@ -19170,9 +19170,9 @@ } }, "node_modules/@types/vscode": { - "version": "1.63.1", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.63.1.tgz", - "integrity": "sha512-Z+ZqjRcnGfHP86dvx/BtSwWyZPKQ/LBdmAVImY82TphyjOw2KgTKcp7Nx92oNwCTsHzlshwexAG/WiY2JuUm3g==", + "version": "1.77.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.77.0.tgz", + "integrity": "sha512-MWFN5R7a33n8eJZJmdVlifjig3LWUNRrPeO1xemIcZ0ae0TEQuRc7G2xV0LUX78RZFECY1plYBn+dP/Acc3L0Q==", "dev": true }, "node_modules/@types/webpack": { @@ -61731,9 +61731,9 @@ } }, "@types/vscode": { - "version": "1.63.1", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.63.1.tgz", - "integrity": "sha512-Z+ZqjRcnGfHP86dvx/BtSwWyZPKQ/LBdmAVImY82TphyjOw2KgTKcp7Nx92oNwCTsHzlshwexAG/WiY2JuUm3g==", + "version": "1.77.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.77.0.tgz", + "integrity": "sha512-MWFN5R7a33n8eJZJmdVlifjig3LWUNRrPeO1xemIcZ0ae0TEQuRc7G2xV0LUX78RZFECY1plYBn+dP/Acc3L0Q==", "dev": true }, "@types/webpack": { diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index d4c208b8b..6d6f4821c 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -1531,7 +1531,7 @@ "@types/through2": "^2.0.36", "@types/tmp": "^0.1.0", "@types/unzipper": "~0.10.1", - "@types/vscode": "^1.59.0", + "@types/vscode": "^1.67.0", "@types/webpack": "^5.28.0", "@types/webpack-env": "^1.18.0", "@types/xml2js": "~0.4.4", diff --git a/extensions/ql-vscode/src/databases/ui/db-panel.ts b/extensions/ql-vscode/src/databases/ui/db-panel.ts index 80a0eec5b..7ea333681 100644 --- a/extensions/ql-vscode/src/databases/ui/db-panel.ts +++ b/extensions/ql-vscode/src/databases/ui/db-panel.ts @@ -34,11 +34,11 @@ import { DatabasePanelCommands } from "../../common/commands"; import { App } from "../../common/app"; export interface RemoteDatabaseQuickPickItem extends QuickPickItem { - kind: string; + remoteDatabaseKind: string; } export interface AddListQuickPickItem extends QuickPickItem { - kind: DbListKind; + databaseKind: DbListKind; } export class DbPanel extends DisposableObject { @@ -113,19 +113,19 @@ export class DbPanel extends DisposableObject { ) { await this.addNewRemoteRepo(highlightedItem.parentListName); } else { - const quickPickItems = [ + const quickPickItems: RemoteDatabaseQuickPickItem[] = [ { label: "$(repo) From a GitHub repository", detail: "Add a variant analysis repository from GitHub", alwaysShow: true, - kind: "repo", + remoteDatabaseKind: "repo", }, { label: "$(organization) All repositories of a GitHub org or owner", detail: "Add a variant analysis list of repositories from a GitHub organization/owner", alwaysShow: true, - kind: "owner", + remoteDatabaseKind: "owner", }, ]; const databaseKind = @@ -142,9 +142,9 @@ export class DbPanel extends DisposableObject { // We set 'true' to make this a silent exception. throw new UserCancellationException("No repository selected", true); } - if (databaseKind.kind === "repo") { + if (databaseKind.remoteDatabaseKind === "repo") { await this.addNewRemoteRepo(); - } else if (databaseKind.kind === "owner") { + } else if (databaseKind.remoteDatabaseKind === "owner") { await this.addNewRemoteOwner(); } } From 78d3de6c86bf29a1fb2e4be095e15da416155362 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Thu, 13 Apr 2023 17:35:10 -0400 Subject: [PATCH 21/40] Fixup rename in test code --- .../activated-extension/databases/db-panel.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/ql-vscode/test/vscode-tests/activated-extension/databases/db-panel.test.ts b/extensions/ql-vscode/test/vscode-tests/activated-extension/databases/db-panel.test.ts index 8dd3f19d4..7b72a571b 100644 --- a/extensions/ql-vscode/test/vscode-tests/activated-extension/databases/db-panel.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/activated-extension/databases/db-panel.test.ts @@ -53,7 +53,7 @@ describe("Db panel UI commands", () => { it.skip("should add new local db list", async () => { // Add db list jest.spyOn(window, "showQuickPick").mockResolvedValue({ - kind: DbListKind.Local, + databaseKind: DbListKind.Local, } as AddListQuickPickItem); jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1"); await commandManager.execute( @@ -73,7 +73,7 @@ describe("Db panel UI commands", () => { it("should add new remote repository", async () => { // Add db jest.spyOn(window, "showQuickPick").mockResolvedValue({ - kind: "repo", + remoteDatabaseKind: "repo", } as RemoteDatabaseQuickPickItem); jest.spyOn(window, "showInputBox").mockResolvedValue("owner1/repo1"); @@ -96,7 +96,7 @@ describe("Db panel UI commands", () => { it("should add new remote owner", async () => { // Add owner jest.spyOn(window, "showQuickPick").mockResolvedValue({ - kind: "owner", + remoteDatabaseKind: "owner", } as RemoteDatabaseQuickPickItem); jest.spyOn(window, "showInputBox").mockResolvedValue("owner1"); From 3eaa99696a8d0d68d96fe25a3c95e79d3adeff99 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Thu, 13 Apr 2023 17:39:50 -0400 Subject: [PATCH 22/40] Add iterator function to mock environment variable collection --- .../test/vscode-tests/no-workspace/helpers.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts index 98919219d..8eae5cd03 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/helpers.test.ts @@ -272,6 +272,13 @@ describe("helpers", () => { class MockEnvironmentVariableCollection implements EnvironmentVariableCollection { + [Symbol.iterator](): Iterator< + [variable: string, mutator: EnvironmentVariableMutator], + any, + undefined + > { + throw new Error("Method not implemented."); + } persistent = false; replace(_variable: string, _value: string): void { throw new Error("Method not implemented."); From 2a9911dac22cf1e6d45086584a63665fce196e47 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Fri, 14 Apr 2023 07:22:27 +0000 Subject: [PATCH 23/40] Minor function rename to match convention --- .../store/query-history-variant-analysis-dto-mapper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto-mapper.ts index 4f6931f21..e05451bf0 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto-mapper.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-variant-analysis-dto-mapper.ts @@ -35,12 +35,12 @@ export function mapQueryHistoryVariantAnalysisToDomainModel( resultCount: item.resultCount, status: mapQueryStatusToDomainModel(item.status), completed: item.completed, - variantAnalysis: mapVariantAnalysisDtoToDomainModel(item.variantAnalysis), + variantAnalysis: mapVariantAnalysisToDomainModel(item.variantAnalysis), userSpecifiedLabel: item.userSpecifiedLabel, }; } -function mapVariantAnalysisDtoToDomainModel( +function mapVariantAnalysisToDomainModel( variantAnalysis: VariantAnalysisDto, ): VariantAnalysis { return { From a15eef823d87ff1ed66b2e3cd4dfd9985bdaaaf8 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Fri, 14 Apr 2023 07:23:04 +0000 Subject: [PATCH 24/40] Update test data to match actual data format --- .../variant-analysis/workspace-query-history.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/workspace-query-history.json b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/workspace-query-history.json index add4195f2..d17874be9 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/workspace-query-history.json +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data/variant-analysis/workspace-query-history.json @@ -7,7 +7,11 @@ "completed": true, "variantAnalysis": { "id": 98574321397, - "controllerRepoId": 128321, + "controllerRepo": { + "id": 128321, + "fullName": "github/codeql", + "private": false + }, "query": { "name": "Variant Analysis Integration Test 1", "filePath": "PLACEHOLDER/q2.ql", @@ -30,7 +34,11 @@ "completed": true, "variantAnalysis": { "id": 98574321397, - "controllerRepoId": 128321, + "controllerRepo": { + "id": 128321, + "fullName": "github/codeql", + "private": false + }, "query": { "name": "Variant Analysis Integration Test 2", "filePath": "PLACEHOLDER/q2.ql", From 1419ff2a9aeb73c0160ae1f9ee2edca2a7623f04 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Fri, 14 Apr 2023 10:28:59 +0200 Subject: [PATCH 25/40] Raise timeout of queries tests --- .../ql-vscode/test/vscode-tests/cli-integration/queries.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts index 073e4c6ad..8b85e3fd1 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts @@ -27,7 +27,7 @@ import { QueryResultType } from "../../../src/pure/new-messages"; import { createVSCodeCommandManager } from "../../../src/common/vscode/commands"; import { AllCommands, QueryServerCommands } from "../../../src/common/commands"; -jest.setTimeout(20_000); +jest.setTimeout(60_000); /** * Integration tests for queries From 5766db9285ca62043dd5b7f1ca4d735ed6ff355e Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Fri, 14 Apr 2023 10:46:33 +0200 Subject: [PATCH 26/40] Set default higher timeout on CLI integration tests This sets a default timeout of 3 minutes on CLI integration tests. This is because these tests call into the CLI and execute queries, so these are expected to take a lot longer than the default 5 seconds. This allows us to remove all the individual `jest.setTimeout` calls with different values from the test files. --- .../test/vscode-tests/cli-integration/databaseFetcher.test.ts | 2 -- .../test/vscode-tests/cli-integration/helpers.test.ts | 3 --- .../ql-vscode/test/vscode-tests/cli-integration/jest.config.ts | 3 +++ .../test/vscode-tests/cli-integration/legacy-query.test.ts | 2 -- .../test/vscode-tests/cli-integration/new-query.test.ts | 2 -- .../test/vscode-tests/cli-integration/packaging.test.ts | 3 --- .../test/vscode-tests/cli-integration/queries.test.ts | 2 -- .../test/vscode-tests/cli-integration/run-cli.test.ts | 2 -- .../vscode-tests/cli-integration/skeleton-query-wizard.test.ts | 2 -- .../test/vscode-tests/cli-integration/sourcemap.test.ts | 2 -- .../variant-analysis/variant-analysis-manager.test.ts | 3 --- .../variant-analysis-submission-integration.test.ts | 2 -- 12 files changed, 3 insertions(+), 25 deletions(-) diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts index f7efd3cc0..9a17a9442 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/databaseFetcher.test.ts @@ -16,8 +16,6 @@ import { } from "../global.helper"; import { createMockCommandManager } from "../../__mocks__/commandsMock"; -jest.setTimeout(60_000); - /** * Run various integration tests for databases */ diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts index 7f8561bc6..7ef35d062 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/helpers.test.ts @@ -4,9 +4,6 @@ import { CodeQLCliServer } from "../../../src/cli"; import { tryGetQueryMetadata } from "../../../src/helpers"; import { getActivatedExtension } from "../global.helper"; -// up to 3 minutes per test -jest.setTimeout(3 * 60 * 1000); - describe("helpers (with CLI)", () => { const baseDir = __dirname; diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/jest.config.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/jest.config.ts index 3bd6d399c..ff7eac9da 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/jest.config.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/jest.config.ts @@ -6,6 +6,9 @@ const config: Config = { ...baseConfig, runner: "/../jest-runner-installed-extensions.ts", setupFilesAfterEnv: ["/jest.setup.ts"], + // CLI integration tests call into the CLI and execute queries, so these are expected to take a lot longer + // than the default 5 seconds. + testTimeout: 180_000, // 3 minutes }; export default config; diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts index 2b76797b4..546c055c1 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/legacy-query.test.ts @@ -98,8 +98,6 @@ const db: messages.Dataset = { workingSet: "default", }; -jest.setTimeout(60_000); - describeWithCodeQL()("using the legacy query server", () => { const nullProgressReporter: ProgressReporter = { report: () => { diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts index dbc4ac848..1aab15ab4 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/new-query.test.ts @@ -104,8 +104,6 @@ const nullProgressReporter: ProgressReporter = { }, }; -jest.setTimeout(20_000); - describeWithCodeQL()("using the new query server", () => { let qs: qsClient.QueryServerClient; let cliServer: cli.CodeQLCliServer; diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts index cd6940ccd..5d4c5c298 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/packaging.test.ts @@ -12,9 +12,6 @@ import { import { mockedQuickPickItem } from "../utils/mocking.helpers"; import { getActivatedExtension } from "../global.helper"; -// up to 3 minutes per test -jest.setTimeout(3 * 60 * 1000); - describe("Packaging commands", () => { let cli: CodeQLCliServer; const progress = jest.fn(); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts index 8b85e3fd1..de5d0071b 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts @@ -27,8 +27,6 @@ import { QueryResultType } from "../../../src/pure/new-messages"; import { createVSCodeCommandManager } from "../../../src/common/vscode/commands"; import { AllCommands, QueryServerCommands } from "../../../src/common/commands"; -jest.setTimeout(60_000); - /** * Integration tests for queries */ diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts index 25adbd34a..9ce307b7f 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/run-cli.test.ts @@ -14,8 +14,6 @@ import { KeyType } from "../../../src/contextual/keyType"; import { faker } from "@faker-js/faker"; import { getActivatedExtension } from "../global.helper"; -jest.setTimeout(60_000); - /** * Perform proper integration tests by running the CLI */ diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts index d42c21197..2bf20841b 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/skeleton-query-wizard.test.ts @@ -22,8 +22,6 @@ import * as databaseFetcher from "../../../src/databaseFetcher"; import { createMockDB } from "../../factories/databases/databases"; import { asError } from "../../../src/pure/helpers-pure"; -jest.setTimeout(80_000); - describe("SkeletonQueryWizard", () => { let mockCli: CodeQLCliServer; let wizard: SkeletonQueryWizard; diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts index 615f10b30..763ee6294 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/sourcemap.test.ts @@ -5,8 +5,6 @@ import { readFile, writeFile, ensureDir, copy } from "fs-extra"; import { createVSCodeCommandManager } from "../../../src/common/vscode/commands"; import { AllCommands } from "../../../src/common/commands"; -jest.setTimeout(20_000); - /** * Integration tests for queries */ diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts index fd0f2accf..d1a3ac719 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts @@ -29,9 +29,6 @@ import { QueryLanguage } from "../../../../src/common/query-language"; import { readBundledPack } from "../../utils/bundled-pack-helpers"; import { load } from "js-yaml"; -// up to 3 minutes per test -jest.setTimeout(3 * 60 * 1000); - describe("Variant Analysis Manager", () => { let cli: CodeQLCliServer; let cancellationTokenSource: CancellationTokenSource; diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts index 70a6e93a9..fb3cabe72 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-submission-integration.test.ts @@ -15,8 +15,6 @@ import { getActivatedExtension } from "../../global.helper"; import { createVSCodeCommandManager } from "../../../../src/common/vscode/commands"; import { AllCommands } from "../../../../src/common/commands"; -jest.setTimeout(30_000); - const mockServer = new MockGitHubApiServer(); beforeAll(() => mockServer.startServer()); afterEach(() => mockServer.unloadScenario()); From fd98f3400b28142251a8bd1264e4dc47887e16bc Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Fri, 14 Apr 2023 10:53:34 +0200 Subject: [PATCH 27/40] Add Tuple type --- .../src/data-extensions-editor/predicates.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/predicates.ts b/extensions/ql-vscode/src/data-extensions-editor/predicates.ts index e0d7ea74f..f52115163 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/predicates.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/predicates.ts @@ -12,13 +12,15 @@ export type ExternalApiUsageByType = { export type ExtensiblePredicateDefinition = { extensiblePredicate: string; - generateMethodDefinition: (method: ExternalApiUsageByType) => any[]; - readModeledMethod: (row: any[]) => ModeledMethodWithSignature; + generateMethodDefinition: (method: ExternalApiUsageByType) => Tuple[]; + readModeledMethod: (row: Tuple[]) => ModeledMethodWithSignature; supportedKinds?: string[]; }; -function readRowToMethod(row: any[]): string { +type Tuple = boolean | number | string; + +function readRowToMethod(row: Tuple[]): string { return `${row[0]}.${row[1]}#${row[3]}${row[4]}`; } @@ -48,8 +50,8 @@ export const extensiblePredicateDefinitions: Record< modeledMethod: { type: "source", input: "", - output: row[6], - kind: row[7], + output: row[6] as string, + kind: row[7] as string, }, }), supportedKinds: ["remote"], @@ -75,9 +77,9 @@ export const extensiblePredicateDefinitions: Record< signature: readRowToMethod(row), modeledMethod: { type: "sink", - input: row[6], + input: row[6] as string, output: "", - kind: row[7], + kind: row[7] as string, }, }), supportedKinds: ["sql", "xss", "logging"], @@ -104,9 +106,9 @@ export const extensiblePredicateDefinitions: Record< signature: readRowToMethod(row), modeledMethod: { type: "summary", - input: row[6], - output: row[7], - kind: row[8], + input: row[6] as string, + output: row[7] as string, + kind: row[8] as string, }, }), supportedKinds: ["taint", "value"], From 05d68d0bbe48614607a017d53b91c451a53539e9 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Fri, 14 Apr 2023 09:59:34 +0100 Subject: [PATCH 28/40] Move local query mappers to separate files (#2322) --- .../store/query-history-domain-mapper.ts | 122 +-------------- .../store/query-history-dto-mapper.ts | 142 +----------------- ...query-history-local-query-domain-mapper.ts | 121 +++++++++++++++ .../query-history-local-query-dto-mapper.ts | 141 +++++++++++++++++ 4 files changed, 264 insertions(+), 262 deletions(-) create mode 100644 extensions/ql-vscode/src/query-history/store/query-history-local-query-domain-mapper.ts create mode 100644 extensions/ql-vscode/src/query-history/store/query-history-local-query-dto-mapper.ts diff --git a/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts index 3c1239dac..8df99b18f 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-domain-mapper.ts @@ -1,25 +1,7 @@ import { assertNever } from "../../pure/helpers-pure"; -import { - LocalQueryInfo, - InitialQueryInfo, - CompletedQueryInfo, -} from "../../query-results"; -import { QueryEvaluationInfo } from "../../run-queries-shared"; import { QueryHistoryInfo } from "../query-history-info"; -import { - QueryHistoryLocalQueryDto, - InitialQueryInfoDto, - QueryEvaluationInfoDto, - CompletedQueryInfoDto, - SortedResultSetInfoDto, - SortDirectionDto, -} from "./query-history-local-query-dto"; +import { mapLocalQueryInfoToDto } from "./query-history-local-query-domain-mapper"; import { QueryHistoryItemDto } from "./query-history-dto"; -import { - RawResultsSortState, - SortDirection, - SortedResultSetInfo, -} from "../../pure/interface-types"; import { mapQueryHistoryVariantAnalysisToDto } from "./query-history-variant-analysis-domain-mapper"; export function mapQueryHistoryToDto( @@ -35,105 +17,3 @@ export function mapQueryHistoryToDto( } }); } - -function mapLocalQueryInfoToDto( - query: LocalQueryInfo, -): QueryHistoryLocalQueryDto { - return { - initialInfo: mapInitialQueryInfoToDto(query.initialInfo), - t: "local", - evalLogLocation: query.evalLogLocation, - evalLogSummaryLocation: query.evalLogSummaryLocation, - jsonEvalLogSummaryLocation: query.jsonEvalLogSummaryLocation, - evalLogSummarySymbolsLocation: query.evalLogSummarySymbolsLocation, - failureReason: query.failureReason, - completedQuery: - query.completedQuery && mapCompletedQueryToDto(query.completedQuery), - }; -} - -function mapCompletedQueryToDto( - query: CompletedQueryInfo, -): CompletedQueryInfoDto { - const sortedResults = Object.fromEntries( - Object.entries(query.sortedResultsInfo).map(([key, value]) => { - return [key, mapSortedResultSetInfoToDto(value)]; - }), - ); - - return { - query: mapQueryEvaluationInfoToDto(query.query), - result: { - runId: query.result.runId, - queryId: query.result.queryId, - resultType: query.result.resultType, - evaluationTime: query.result.evaluationTime, - message: query.result.message, - logFileLocation: query.result.logFileLocation, - }, - logFileLocation: query.logFileLocation, - successful: query.successful, - message: query.message, - resultCount: query.resultCount, - sortedResultsInfo: sortedResults, - }; -} - -function mapSortDirectionToDto(sortDirection: SortDirection): SortDirectionDto { - switch (sortDirection) { - case SortDirection.asc: - return SortDirectionDto.asc; - case SortDirection.desc: - return SortDirectionDto.desc; - } -} - -function mapRawResultsSortStateToDto( - sortState: RawResultsSortState, -): SortedResultSetInfoDto["sortState"] { - return { - columnIndex: sortState.columnIndex, - sortDirection: mapSortDirectionToDto(sortState.sortDirection), - }; -} - -function mapSortedResultSetInfoToDto( - resultSet: SortedResultSetInfo, -): SortedResultSetInfoDto { - return { - resultsPath: resultSet.resultsPath, - sortState: mapRawResultsSortStateToDto(resultSet.sortState), - }; -} - -function mapInitialQueryInfoToDto( - localQueryInitialInfo: InitialQueryInfo, -): InitialQueryInfoDto { - return { - userSpecifiedLabel: localQueryInitialInfo.userSpecifiedLabel, - queryText: localQueryInitialInfo.queryText, - isQuickQuery: localQueryInitialInfo.isQuickQuery, - isQuickEval: localQueryInitialInfo.isQuickEval, - quickEvalPosition: localQueryInitialInfo.quickEvalPosition, - queryPath: localQueryInitialInfo.queryPath, - databaseInfo: { - databaseUri: localQueryInitialInfo.databaseInfo.databaseUri, - name: localQueryInitialInfo.databaseInfo.name, - }, - start: localQueryInitialInfo.start, - id: localQueryInitialInfo.id, - }; -} - -function mapQueryEvaluationInfoToDto( - queryEvaluationInfo: QueryEvaluationInfo, -): QueryEvaluationInfoDto { - return { - querySaveDir: queryEvaluationInfo.querySaveDir, - dbItemPath: queryEvaluationInfo.dbItemPath, - databaseHasMetadataFile: queryEvaluationInfo.databaseHasMetadataFile, - quickEvalPosition: queryEvaluationInfo.quickEvalPosition, - metadata: queryEvaluationInfo.metadata, - resultsPaths: queryEvaluationInfo.resultsPaths, - }; -} diff --git a/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts index a2f915d2c..282e5fe2a 100644 --- a/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts +++ b/extensions/ql-vscode/src/query-history/store/query-history-dto-mapper.ts @@ -1,28 +1,7 @@ -import { - LocalQueryInfo, - CompletedQueryInfo, - InitialQueryInfo, -} from "../../query-results"; -import { QueryEvaluationInfo } from "../../run-queries-shared"; import { QueryHistoryInfo } from "../query-history-info"; -import { - CompletedQueryInfoDto, - QueryEvaluationInfoDto, - InitialQueryInfoDto, - QueryHistoryLocalQueryDto, - SortDirectionDto, - InterpretedResultsSortStateDto, - SortedResultSetInfoDto, - RawResultsSortStateDto, -} from "./query-history-local-query-dto"; import { QueryHistoryItemDto } from "./query-history-dto"; -import { - InterpretedResultsSortState, - RawResultsSortState, - SortDirection, - SortedResultSetInfo, -} from "../../pure/interface-types"; import { mapQueryHistoryVariantAnalysisToDomainModel } from "./query-history-variant-analysis-dto-mapper"; +import { mapLocalQueryItemToDomainModel } from "./query-history-local-query-dto-mapper"; export function mapQueryHistoryToDomainModel( queries: QueryHistoryItemDto[], @@ -41,122 +20,3 @@ export function mapQueryHistoryToDomainModel( ); }); } - -function mapLocalQueryItemToDomainModel( - localQuery: QueryHistoryLocalQueryDto, -): LocalQueryInfo { - return new LocalQueryInfo( - mapInitialQueryInfoToDomainModel(localQuery.initialInfo), - undefined, - localQuery.failureReason, - localQuery.completedQuery && - mapCompletedQueryInfoToDomainModel(localQuery.completedQuery), - localQuery.evalLogLocation, - localQuery.evalLogSummaryLocation, - localQuery.jsonEvalLogSummaryLocation, - localQuery.evalLogSummarySymbolsLocation, - ); -} - -function mapCompletedQueryInfoToDomainModel( - completedQuery: CompletedQueryInfoDto, -): CompletedQueryInfo { - const sortState = - completedQuery.interpretedResultsSortState && - mapSortStateToDomainModel(completedQuery.interpretedResultsSortState); - - const sortedResults = Object.fromEntries( - Object.entries(completedQuery.sortedResultsInfo).map(([key, value]) => { - return [key, mapSortedResultSetInfoToDomainModel(value)]; - }), - ); - - return new CompletedQueryInfo( - mapQueryEvaluationInfoToDomainModel(completedQuery.query), - { - runId: completedQuery.result.runId, - queryId: completedQuery.result.queryId, - resultType: completedQuery.result.resultType, - evaluationTime: completedQuery.result.evaluationTime, - message: completedQuery.result.message, - logFileLocation: completedQuery.result.logFileLocation, - }, - completedQuery.logFileLocation, - completedQuery.successful ?? completedQuery.sucessful, - completedQuery.message, - sortState, - completedQuery.resultCount, - sortedResults, - ); -} - -function mapInitialQueryInfoToDomainModel( - initialInfo: InitialQueryInfoDto, -): InitialQueryInfo { - return { - userSpecifiedLabel: initialInfo.userSpecifiedLabel, - queryText: initialInfo.queryText, - isQuickQuery: initialInfo.isQuickQuery, - isQuickEval: initialInfo.isQuickEval, - quickEvalPosition: initialInfo.quickEvalPosition, - queryPath: initialInfo.queryPath, - databaseInfo: { - databaseUri: initialInfo.databaseInfo.databaseUri, - name: initialInfo.databaseInfo.name, - }, - start: new Date(initialInfo.start), - id: initialInfo.id, - }; -} - -function mapQueryEvaluationInfoToDomainModel( - evaluationInfo: QueryEvaluationInfoDto, -): QueryEvaluationInfo { - return new QueryEvaluationInfo( - evaluationInfo.querySaveDir, - evaluationInfo.dbItemPath, - evaluationInfo.databaseHasMetadataFile, - evaluationInfo.quickEvalPosition, - evaluationInfo.metadata, - ); -} - -function mapSortDirectionToDomainModel( - sortDirection: SortDirectionDto, -): SortDirection { - switch (sortDirection) { - case SortDirectionDto.asc: - return SortDirection.asc; - case SortDirectionDto.desc: - return SortDirection.desc; - } -} - -function mapSortStateToDomainModel( - sortState: InterpretedResultsSortStateDto, -): InterpretedResultsSortState { - return { - sortBy: sortState.sortBy, - sortDirection: mapSortDirectionToDomainModel(sortState.sortDirection), - }; -} - -function mapSortedResultSetInfoToDomainModel( - sortedResultSetInfo: SortedResultSetInfoDto, -): SortedResultSetInfo { - return { - resultsPath: sortedResultSetInfo.resultsPath, - sortState: mapRawResultsSortStateToDomainModel( - sortedResultSetInfo.sortState, - ), - }; -} - -function mapRawResultsSortStateToDomainModel( - sortState: RawResultsSortStateDto, -): RawResultsSortState { - return { - columnIndex: sortState.columnIndex, - sortDirection: mapSortDirectionToDomainModel(sortState.sortDirection), - }; -} diff --git a/extensions/ql-vscode/src/query-history/store/query-history-local-query-domain-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-local-query-domain-mapper.ts new file mode 100644 index 000000000..db2019a68 --- /dev/null +++ b/extensions/ql-vscode/src/query-history/store/query-history-local-query-domain-mapper.ts @@ -0,0 +1,121 @@ +import { + LocalQueryInfo, + InitialQueryInfo, + CompletedQueryInfo, +} from "../../query-results"; +import { QueryEvaluationInfo } from "../../run-queries-shared"; +import { + QueryHistoryLocalQueryDto, + InitialQueryInfoDto, + QueryEvaluationInfoDto, + CompletedQueryInfoDto, + SortedResultSetInfoDto, + SortDirectionDto, +} from "./query-history-local-query-dto"; +import { + RawResultsSortState, + SortDirection, + SortedResultSetInfo, +} from "../../pure/interface-types"; + +export function mapLocalQueryInfoToDto( + query: LocalQueryInfo, +): QueryHistoryLocalQueryDto { + return { + initialInfo: mapInitialQueryInfoToDto(query.initialInfo), + t: "local", + evalLogLocation: query.evalLogLocation, + evalLogSummaryLocation: query.evalLogSummaryLocation, + jsonEvalLogSummaryLocation: query.jsonEvalLogSummaryLocation, + evalLogSummarySymbolsLocation: query.evalLogSummarySymbolsLocation, + failureReason: query.failureReason, + completedQuery: + query.completedQuery && mapCompletedQueryToDto(query.completedQuery), + }; +} + +function mapCompletedQueryToDto( + query: CompletedQueryInfo, +): CompletedQueryInfoDto { + const sortedResults = Object.fromEntries( + Object.entries(query.sortedResultsInfo).map(([key, value]) => { + return [key, mapSortedResultSetInfoToDto(value)]; + }), + ); + + return { + query: mapQueryEvaluationInfoToDto(query.query), + result: { + runId: query.result.runId, + queryId: query.result.queryId, + resultType: query.result.resultType, + evaluationTime: query.result.evaluationTime, + message: query.result.message, + logFileLocation: query.result.logFileLocation, + }, + logFileLocation: query.logFileLocation, + successful: query.successful, + message: query.message, + resultCount: query.resultCount, + sortedResultsInfo: sortedResults, + }; +} + +function mapSortDirectionToDto(sortDirection: SortDirection): SortDirectionDto { + switch (sortDirection) { + case SortDirection.asc: + return SortDirectionDto.asc; + case SortDirection.desc: + return SortDirectionDto.desc; + } +} + +function mapRawResultsSortStateToDto( + sortState: RawResultsSortState, +): SortedResultSetInfoDto["sortState"] { + return { + columnIndex: sortState.columnIndex, + sortDirection: mapSortDirectionToDto(sortState.sortDirection), + }; +} + +function mapSortedResultSetInfoToDto( + resultSet: SortedResultSetInfo, +): SortedResultSetInfoDto { + return { + resultsPath: resultSet.resultsPath, + sortState: mapRawResultsSortStateToDto(resultSet.sortState), + }; +} + +function mapInitialQueryInfoToDto( + localQueryInitialInfo: InitialQueryInfo, +): InitialQueryInfoDto { + return { + userSpecifiedLabel: localQueryInitialInfo.userSpecifiedLabel, + queryText: localQueryInitialInfo.queryText, + isQuickQuery: localQueryInitialInfo.isQuickQuery, + isQuickEval: localQueryInitialInfo.isQuickEval, + quickEvalPosition: localQueryInitialInfo.quickEvalPosition, + queryPath: localQueryInitialInfo.queryPath, + databaseInfo: { + databaseUri: localQueryInitialInfo.databaseInfo.databaseUri, + name: localQueryInitialInfo.databaseInfo.name, + }, + start: localQueryInitialInfo.start, + id: localQueryInitialInfo.id, + }; +} + +function mapQueryEvaluationInfoToDto( + queryEvaluationInfo: QueryEvaluationInfo, +): QueryEvaluationInfoDto { + return { + querySaveDir: queryEvaluationInfo.querySaveDir, + dbItemPath: queryEvaluationInfo.dbItemPath, + databaseHasMetadataFile: queryEvaluationInfo.databaseHasMetadataFile, + quickEvalPosition: queryEvaluationInfo.quickEvalPosition, + metadata: queryEvaluationInfo.metadata, + resultsPaths: queryEvaluationInfo.resultsPaths, + }; +} diff --git a/extensions/ql-vscode/src/query-history/store/query-history-local-query-dto-mapper.ts b/extensions/ql-vscode/src/query-history/store/query-history-local-query-dto-mapper.ts new file mode 100644 index 000000000..63f0f763e --- /dev/null +++ b/extensions/ql-vscode/src/query-history/store/query-history-local-query-dto-mapper.ts @@ -0,0 +1,141 @@ +import { + LocalQueryInfo, + CompletedQueryInfo, + InitialQueryInfo, +} from "../../query-results"; +import { QueryEvaluationInfo } from "../../run-queries-shared"; +import { + CompletedQueryInfoDto, + QueryEvaluationInfoDto, + InitialQueryInfoDto, + QueryHistoryLocalQueryDto, + SortDirectionDto, + InterpretedResultsSortStateDto, + SortedResultSetInfoDto, + RawResultsSortStateDto, +} from "./query-history-local-query-dto"; +import { + InterpretedResultsSortState, + RawResultsSortState, + SortDirection, + SortedResultSetInfo, +} from "../../pure/interface-types"; + +export function mapLocalQueryItemToDomainModel( + localQuery: QueryHistoryLocalQueryDto, +): LocalQueryInfo { + return new LocalQueryInfo( + mapInitialQueryInfoToDomainModel(localQuery.initialInfo), + undefined, + localQuery.failureReason, + localQuery.completedQuery && + mapCompletedQueryInfoToDomainModel(localQuery.completedQuery), + localQuery.evalLogLocation, + localQuery.evalLogSummaryLocation, + localQuery.jsonEvalLogSummaryLocation, + localQuery.evalLogSummarySymbolsLocation, + ); +} + +function mapCompletedQueryInfoToDomainModel( + completedQuery: CompletedQueryInfoDto, +): CompletedQueryInfo { + const sortState = + completedQuery.interpretedResultsSortState && + mapSortStateToDomainModel(completedQuery.interpretedResultsSortState); + + const sortedResults = Object.fromEntries( + Object.entries(completedQuery.sortedResultsInfo).map(([key, value]) => { + return [key, mapSortedResultSetInfoToDomainModel(value)]; + }), + ); + + return new CompletedQueryInfo( + mapQueryEvaluationInfoToDomainModel(completedQuery.query), + { + runId: completedQuery.result.runId, + queryId: completedQuery.result.queryId, + resultType: completedQuery.result.resultType, + evaluationTime: completedQuery.result.evaluationTime, + message: completedQuery.result.message, + logFileLocation: completedQuery.result.logFileLocation, + }, + completedQuery.logFileLocation, + completedQuery.successful ?? completedQuery.sucessful, + completedQuery.message, + sortState, + completedQuery.resultCount, + sortedResults, + ); +} + +function mapInitialQueryInfoToDomainModel( + initialInfo: InitialQueryInfoDto, +): InitialQueryInfo { + return { + userSpecifiedLabel: initialInfo.userSpecifiedLabel, + queryText: initialInfo.queryText, + isQuickQuery: initialInfo.isQuickQuery, + isQuickEval: initialInfo.isQuickEval, + quickEvalPosition: initialInfo.quickEvalPosition, + queryPath: initialInfo.queryPath, + databaseInfo: { + databaseUri: initialInfo.databaseInfo.databaseUri, + name: initialInfo.databaseInfo.name, + }, + start: new Date(initialInfo.start), + id: initialInfo.id, + }; +} + +function mapQueryEvaluationInfoToDomainModel( + evaluationInfo: QueryEvaluationInfoDto, +): QueryEvaluationInfo { + return new QueryEvaluationInfo( + evaluationInfo.querySaveDir, + evaluationInfo.dbItemPath, + evaluationInfo.databaseHasMetadataFile, + evaluationInfo.quickEvalPosition, + evaluationInfo.metadata, + ); +} + +function mapSortDirectionToDomainModel( + sortDirection: SortDirectionDto, +): SortDirection { + switch (sortDirection) { + case SortDirectionDto.asc: + return SortDirection.asc; + case SortDirectionDto.desc: + return SortDirection.desc; + } +} + +function mapSortStateToDomainModel( + sortState: InterpretedResultsSortStateDto, +): InterpretedResultsSortState { + return { + sortBy: sortState.sortBy, + sortDirection: mapSortDirectionToDomainModel(sortState.sortDirection), + }; +} + +function mapSortedResultSetInfoToDomainModel( + sortedResultSetInfo: SortedResultSetInfoDto, +): SortedResultSetInfo { + return { + resultsPath: sortedResultSetInfo.resultsPath, + sortState: mapRawResultsSortStateToDomainModel( + sortedResultSetInfo.sortState, + ), + }; +} + +function mapRawResultsSortStateToDomainModel( + sortState: RawResultsSortStateDto, +): RawResultsSortState { + return { + columnIndex: sortState.columnIndex, + sortDirection: mapSortDirectionToDomainModel(sortState.sortDirection), + }; +} From 4a8ba1377dd98296f8c66386b3a4630052b2f1e1 Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Fri, 14 Apr 2023 09:50:15 +0000 Subject: [PATCH 29/40] Don't offer to create skeleton pack again When running Create Query in the codespaces-codeql repo, it successfully creates codeql-custom-queries-xxx as a subfolder of the first workspace folder, and then adds a database. After the database gets added, we get prompted with this message: ``` We've noticed you don't have a CodeQL pack available to analyze this database. Can we set up a query pack for you? ``` which would try to create another QL pack. Since we're no longer pushing QL packs as top level folders in the workspace when we use the new "Create Query" flow, we also need to adapt the original flow to take into account subfolders. Just as a reminder, the original flow is: - Be in the codespace template - Download a database from GitHub - The extension will offer to create a QL pack for the database The new flow: - Run the "Create Query" command - Choose a language - Create a QL pack - Download a database for it In the new flow the last step of downloading a database would trigger the extension to offer to create a QL pack. Let's fix this by detecting subfolders as well and exiting early. --- extensions/ql-vscode/src/local-databases.ts | 10 +++++++--- .../ql-vscode/src/skeleton-query-wizard.ts | 7 ++++--- .../minimal-workspace/local-databases.test.ts | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/extensions/ql-vscode/src/local-databases.ts b/extensions/ql-vscode/src/local-databases.ts index 847cce3b4..6cfec5874 100644 --- a/extensions/ql-vscode/src/local-databases.ts +++ b/extensions/ql-vscode/src/local-databases.ts @@ -30,6 +30,7 @@ import { isCodespacesTemplate } from "./config"; import { QlPackGenerator } from "./qlpack-generator"; import { QueryLanguage } from "./common/query-language"; import { App } from "./common/app"; +import { existsSync } from "fs"; /** * databases.ts @@ -663,8 +664,13 @@ export class DatabaseManager extends DisposableObject { return; } + const firstWorkspaceFolder = getFirstWorkspaceFolder(); const folderName = `codeql-custom-queries-${databaseItem.language}`; - if (isFolderAlreadyInWorkspace(folderName)) { + + if ( + existsSync(join(firstWorkspaceFolder, folderName)) || + isFolderAlreadyInWorkspace(folderName) + ) { return; } @@ -677,8 +683,6 @@ export class DatabaseManager extends DisposableObject { } try { - const firstWorkspaceFolder = getFirstWorkspaceFolder(); - const qlPackGenerator = new QlPackGenerator( folderName, databaseItem.language as QueryLanguage, diff --git a/extensions/ql-vscode/src/skeleton-query-wizard.ts b/extensions/ql-vscode/src/skeleton-query-wizard.ts index c470cf970..c71ca19b6 100644 --- a/extensions/ql-vscode/src/skeleton-query-wizard.ts +++ b/extensions/ql-vscode/src/skeleton-query-wizard.ts @@ -14,6 +14,7 @@ import { QlPackGenerator } from "./qlpack-generator"; import { DatabaseItem, DatabaseManager } from "./local-databases"; import { ProgressCallback, UserCancellationException } from "./progress"; import { askForGitHubRepo, downloadGitHubDatabase } from "./databaseFetcher"; +import { existsSync } from "fs"; type QueryLanguagesToDatabaseMap = Record; @@ -56,9 +57,9 @@ export class SkeletonQueryWizard { this.qlPackStoragePath = getFirstWorkspaceFolder(); - const skeletonPackAlreadyExists = isFolderAlreadyInWorkspace( - this.folderName, - ); + const skeletonPackAlreadyExists = + existsSync(join(this.qlPackStoragePath, this.folderName)) || + isFolderAlreadyInWorkspace(this.folderName); if (skeletonPackAlreadyExists) { // just create a new example query file in skeleton QL pack diff --git a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts index 4e6fbaaa1..92d4c0164 100644 --- a/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/minimal-workspace/local-databases.test.ts @@ -687,6 +687,22 @@ describe("local databases", () => { ); }); }); + + describe("when the QL pack already exists", () => { + beforeEach(() => { + fs.mkdirSync(join(dir.name, `codeql-custom-queries-${language}`)); + }); + + it("should exit early", async () => { + showBinaryChoiceDialogSpy = jest + .spyOn(helpers, "showBinaryChoiceDialog") + .mockResolvedValue(false); + + await (databaseManager as any).createSkeletonPacks(mockDbItem); + + expect(generateSpy).not.toBeCalled(); + }); + }); }); describe("openDatabase", () => { From bfc5f49d4497e2af89a80e92a217ecf8fb323384 Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Fri, 14 Apr 2023 11:01:18 +0000 Subject: [PATCH 30/40] Check specifically for the starter workspace We've made an exception to fetch the parent folder when we're in the vscode-codeql-starter workspace. We'd like to make this more specific so that it doesn't interfere with other repos. --- extensions/ql-vscode/src/helpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index d282a4e29..f07bcb583 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -809,7 +809,9 @@ export function getFirstWorkspaceFolder() { // For the vscode-codeql-starter repo, the first folder will be a ql pack // so we need to get the parent folder - if (firstFolderFsPath.includes("codeql-custom-queries")) { + if ( + firstFolderFsPath.includes("vscode-codeql-starter/codeql-custom-queries") + ) { // return the parent folder return dirname(firstFolderFsPath); } else { From 70d533f0737dbfbb5c355037c8a6f25150de46b8 Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Fri, 14 Apr 2023 12:31:25 +0000 Subject: [PATCH 31/40] Make check platform agnostic The separator character is different on Windows. --- extensions/ql-vscode/src/helpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/helpers.ts b/extensions/ql-vscode/src/helpers.ts index f07bcb583..4aa733b78 100644 --- a/extensions/ql-vscode/src/helpers.ts +++ b/extensions/ql-vscode/src/helpers.ts @@ -810,7 +810,9 @@ export function getFirstWorkspaceFolder() { // For the vscode-codeql-starter repo, the first folder will be a ql pack // so we need to get the parent folder if ( - firstFolderFsPath.includes("vscode-codeql-starter/codeql-custom-queries") + firstFolderFsPath.includes( + join("vscode-codeql-starter", "codeql-custom-queries"), + ) ) { // return the parent folder return dirname(firstFolderFsPath); From 90936780bdd47420be8816288e90816c6da4fbdd Mon Sep 17 00:00:00 2001 From: Anders Starcke Henriksen Date: Fri, 14 Apr 2023 14:46:29 +0200 Subject: [PATCH 32/40] Add support for running C# query. --- .../data-extensions-editor/queries/csharp.ts | 198 ++++++++++++++++++ .../data-extensions-editor/queries/index.ts | 2 + .../data-extensions-editor/queries/java.ts | 6 +- .../external-api-usage-query.test.ts | 175 ++++++++-------- 4 files changed, 287 insertions(+), 94 deletions(-) create mode 100644 extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts new file mode 100644 index 000000000..02355a6d3 --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts @@ -0,0 +1,198 @@ +import { Query } from "./query"; + +export const fetchExternalApisQuery: Query = { + mainQuery: `/** + * @name Usage of APIs coming from external libraries + * @description A list of 3rd party APIs used in the codebase. + * @tags telemetry + * @id csharp/telemetry/fetch-external-apis + */ + + import csharp + import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl + import ExternalApi + + private Call aUsage(ExternalApi api) { + result.getTarget().getUnboundDeclaration() = api + } + + private boolean isSupported(ExternalApi api) { + api.isSupported() and result = true + or + not api.isSupported() and + result = false + } + + from ExternalApi api, string apiName, boolean supported, Call usage + where + apiName = api.getApiName() and + supported = isSupported(api) and + usage = aUsage(api) + select apiName, supported, usage +`, + dependencies: { + "ExternalApi.qll": `/** Provides classes and predicates related to handling APIs from external libraries. */ + +private import csharp +private import dotnet +private import semmle.code.csharp.dispatch.Dispatch +private import semmle.code.csharp.dataflow.ExternalFlow +private import semmle.code.csharp.dataflow.FlowSummary +private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon +private import semmle.code.csharp.dataflow.internal.DataFlowPrivate +private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch +private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl +private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate +private import semmle.code.csharp.security.dataflow.flowsources.Remote + +pragma[nomagic] +private predicate isTestNamespace(Namespace ns) { + ns.getFullName() + .matches([ + "NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%" + ]) +} + +/** + * A test library. + */ +class TestLibrary extends RefType { + TestLibrary() { isTestNamespace(this.getNamespace()) } +} + +/** Holds if the given callable is not worth supporting. */ +private predicate isUninteresting(DotNet::Callable c) { + c.getDeclaringType() instanceof TestLibrary or + c.(Constructor).isParameterless() +} + +/** + * An external API from either the C# Standard Library or a 3rd party library. + */ +class ExternalApi extends DotNet::Callable { + ExternalApi() { + this.isUnboundDeclaration() and + this.fromLibrary() and + this.(Modifiable).isEffectivelyPublic() and + not isUninteresting(this) + } + + /** + * Gets the unbound type, name and parameter types of this API. + */ + bindingset[this] + private string getSignature() { + result = + this.getDeclaringType().getUnboundDeclaration() + "." + this.getName() + "(" + + parameterQualifiedTypeNamesToString(this) + ")" + } + + /** + * Gets the namespace of this API. + */ + bindingset[this] + string getNamespace() { this.getDeclaringType().hasQualifiedName(result, _) } + + /** + * Gets the namespace and signature of this API. + */ + bindingset[this] + string getApiName() { result = this.getNamespace() + "#" + this.getSignature() } + + /** Gets a node that is an input to a call to this API. */ + private ArgumentNode getAnInput() { + result + .getCall() + .(DataFlowDispatch::NonDelegateDataFlowCall) + .getATarget(_) + .getUnboundDeclaration() = this + } + + /** Gets a node that is an output from a call to this API. */ + private DataFlow::Node getAnOutput() { + exists( + Call c, DataFlowDispatch::NonDelegateDataFlowCall dc, DataFlowImplCommon::ReturnKindExt ret + | + dc.getDispatchCall().getCall() = c and + c.getTarget().getUnboundDeclaration() = this + | + result = ret.getAnOutNode(dc) + ) + } + + /** Holds if this API has a supported summary. */ + pragma[nomagic] + predicate hasSummary() { + this instanceof SummarizedCallable + or + defaultAdditionalTaintStep(this.getAnInput(), _) + } + + /** Holds if this API is a known source. */ + pragma[nomagic] + predicate isSource() { + this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _) + } + + /** Holds if this API is a known sink. */ + pragma[nomagic] + predicate isSink() { sinkNode(this.getAnInput(), _) } + + /** Holds if this API is a known neutral. */ + pragma[nomagic] + predicate isNeutral() { this instanceof FlowSummaryImpl::Public::NeutralCallable } + + /** + * Holds if this API is supported by existing CodeQL libraries, that is, it is either a + * recognized source, sink or neutral or it has a flow summary. + */ + predicate isSupported() { + this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral() + } +} + +/** + * Gets the limit for the number of results produced by a telemetry query. + */ +int resultLimit() { result = 1000 } + +/** + * Holds if it is relevant to count usages of "api". + */ +signature predicate relevantApi(ExternalApi api); + +/** + * Given a predicate to count relevant API usages, this module provides a predicate + * for restricting the number or returned results based on a certain limit. + */ +module Results { + private int getUsages(string apiName) { + result = + strictcount(Call c, ExternalApi api | + c.getTarget().getUnboundDeclaration() = api and + apiName = api.getApiName() and + getRelevantUsages(api) + ) + } + + private int getOrder(string apiName) { + apiName = + rank[result](string name, int usages | + usages = getUsages(name) + | + name order by usages desc, name + ) + } + + /** + * Holds if there exists an API with "apiName" that is being used "usages" times + * and if it is in the top results (guarded by resultLimit). + */ + predicate restrict(string apiName, int usages) { + usages = getUsages(apiName) and + getOrder(apiName) <= resultLimit() + } +} +`, + }, +}; diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts index 5f864ff03..bbf5320ac 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/index.ts @@ -1,7 +1,9 @@ +import { fetchExternalApisQuery as csharpFetchExternalApisQuery } from "./csharp"; import { fetchExternalApisQuery as javaFetchExternalApisQuery } from "./java"; import { Query } from "./query"; import { QueryLanguage } from "../../common/query-language"; export const fetchExternalApiQueries: Partial> = { + [QueryLanguage.CSharp]: csharpFetchExternalApisQuery, [QueryLanguage.Java]: javaFetchExternalApisQuery, }; diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts index 5d164f164..433fff8ca 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts @@ -20,11 +20,7 @@ private Call aUsage(ExternalApi api) { private boolean isSupported(ExternalApi api) { api.isSupported() and result = true or - api = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() and result = true - or - not api.isSupported() and - not api = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() and - result = false + not api.isSupported() and result = false } from ExternalApi api, string apiName, boolean supported, Call usage diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts index bf2f7d883..d4f0c1234 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts @@ -5,13 +5,12 @@ import { import { createMockLogger } from "../../../__mocks__/loggerMock"; import type { Uri } from "vscode"; import { DatabaseKind } from "../../../../src/local-databases"; -import * as queryResolver from "../../../../src/contextual/queryResolver"; import { file } from "tmp-promise"; import { QueryResultType } from "../../../../src/pure/new-messages"; import { readdir, readFile } from "fs-extra"; import { load } from "js-yaml"; import { dirname, join } from "path"; -import { fetchExternalApisQuery } from "../../../../src/data-extensions-editor/queries/java"; +import { fetchExternalApiQueries } from "../../../../src/data-extensions-editor/queries/index"; import * as helpers from "../../../../src/helpers"; import { RedactableError } from "../../../../src/pure/errors"; @@ -29,100 +28,98 @@ function createMockUri(path = "/a/b/c/foo"): Uri { } describe("runQuery", () => { - it("runs the query", async () => { - jest.spyOn(queryResolver, "qlpackOfDatabase").mockResolvedValue({ - dbschemePack: "codeql/java-all", - dbschemePackIsLibraryPack: false, - queryPack: "codeql/java-queries", - }); - + it("runs all queries", async () => { const logPath = (await file()).path; - const options = { - cliServer: { - resolveQlpacks: jest.fn().mockResolvedValue({ - "my/java-extensions": "/a/b/c/", - }), - }, - queryRunner: { - createQueryRun: jest.fn().mockReturnValue({ - evaluate: jest.fn().mockResolvedValue({ - resultType: QueryResultType.SUCCESS, + // Test all queries + for (const [lang, query] of Object.entries(fetchExternalApiQueries)) { + console.log(`hello ${lang}`); + const options = { + cliServer: { + resolveQlpacks: jest.fn().mockResolvedValue({ + "my/extensions": "/a/b/c/", }), - outputDir: { - logPath, - }, - }), - logger: createMockLogger(), - }, - databaseItem: { - databaseUri: createMockUri("/a/b/c/src.zip"), - contents: { - kind: DatabaseKind.Database, - name: "foo", - datasetUri: createMockUri(), }, - language: "java", - }, - queryStorageDir: "/tmp/queries", - progress: jest.fn(), - token: { - isCancellationRequested: false, - onCancellationRequested: jest.fn(), - }, - }; - const result = await runQuery(options); + queryRunner: { + createQueryRun: jest.fn().mockReturnValue({ + evaluate: jest.fn().mockResolvedValue({ + resultType: QueryResultType.SUCCESS, + }), + outputDir: { + logPath, + }, + }), + logger: createMockLogger(), + }, + databaseItem: { + databaseUri: createMockUri("/a/b/c/src.zip"), + contents: { + kind: DatabaseKind.Database, + name: "foo", + datasetUri: createMockUri(), + }, + language: lang, + }, + queryStorageDir: "/tmp/queries", + progress: jest.fn(), + token: { + isCancellationRequested: false, + onCancellationRequested: jest.fn(), + }, + }; + const result = await runQuery(options); - expect(result?.resultType).toEqual(QueryResultType.SUCCESS); + expect(result?.resultType).toEqual(QueryResultType.SUCCESS); - expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1); - expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true); - expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith( - "/a/b/c/src.zip", - { - queryPath: expect.stringMatching(/FetchExternalApis\.ql/), - quickEvalPosition: undefined, - }, - false, - [], - ["my/java-extensions"], - "/tmp/queries", - undefined, - undefined, - ); - - const queryPath = - options.queryRunner.createQueryRun.mock.calls[0][1].queryPath; - const queryDirectory = dirname(queryPath); - - const queryFiles = await readdir(queryDirectory); - expect(queryFiles.sort()).toEqual( - ["codeql-pack.yml", "FetchExternalApis.ql", "ExternalApi.qll"].sort(), - ); - - const suiteFileContents = await readFile( - join(queryDirectory, "codeql-pack.yml"), - "utf8", - ); - const suiteYaml = load(suiteFileContents); - expect(suiteYaml).toEqual({ - name: "codeql/external-api-usage", - version: "0.0.0", - dependencies: { - "codeql/java-all": "*", - }, - }); - - expect( - await readFile(join(queryDirectory, "FetchExternalApis.ql"), "utf8"), - ).toEqual(fetchExternalApisQuery.mainQuery); - - for (const [filename, contents] of Object.entries( - fetchExternalApisQuery.dependencies ?? {}, - )) { - expect(await readFile(join(queryDirectory, filename), "utf8")).toEqual( - contents, + expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1); + expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true); + expect(options.queryRunner.createQueryRun).toHaveBeenCalledWith( + "/a/b/c/src.zip", + { + queryPath: expect.stringMatching(/FetchExternalApis\.ql/), + quickEvalPosition: undefined, + }, + false, + [], + ["my/extensions"], + "/tmp/queries", + undefined, + undefined, ); + + const queryPath = + options.queryRunner.createQueryRun.mock.calls[0][1].queryPath; + const queryDirectory = dirname(queryPath); + + const queryFiles = await readdir(queryDirectory); + expect(queryFiles.sort()).toEqual( + ["codeql-pack.yml", "FetchExternalApis.ql", "ExternalApi.qll"].sort(), + ); + + const suiteFileContents = await readFile( + join(queryDirectory, "codeql-pack.yml"), + "utf8", + ); + const suiteYaml = load(suiteFileContents); + expect(suiteYaml).toEqual({ + name: "codeql/external-api-usage", + version: "0.0.0", + dependencies: { + [`codeql/${lang}-all`]: "*", + }, + }); + + expect( + await readFile(join(queryDirectory, "FetchExternalApis.ql"), "utf8"), + ).toEqual(query.mainQuery); + + for (const [filename, contents] of Object.entries( + query.dependencies ?? {}, + )) { + expect(await readFile(join(queryDirectory, filename), "utf8")).toEqual( + contents, + ); + } } }); }); From 35e8ce1654b1670d4c76660c944d1c56b9423d82 Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 14 Apr 2023 10:50:27 -0700 Subject: [PATCH 33/40] Fix various test flakiness This commit addresses various test flakiness: 1. Bump timeouts for queries tests 2. Add a dispose handler to queryserver-client. This will help us during tests because if there is a test that timesout while a query is running, the query's progress callback won't be invoked. We will still get a timeout error in the first test, but the second test will not get a spurious error. 3. Handle a disposed query server in `deregisterDatabase`. This method will remove the database from the currently running query server. If there is no query server, then there is nothing to remove. So, this error is safe to ignore. 4. Explicitly `end()` a connection `ServerProcess`. I'm not 100% sure if this is necessary, but it seems like it prevents responses from being handled and erroring out. 5. Better handling of ideServer restarts. Previously, if you quickly called `CodeQL: Restart Query Server` twice in a row, you would get an error from the ideServer restart. Restart fails if the server is not already started. So, in this case just call a start. --- extensions/ql-vscode/src/extension.ts | 8 +++++++- extensions/ql-vscode/src/json-rpc-server.ts | 1 + extensions/ql-vscode/src/local-databases.ts | 14 +++++++++++++- .../src/query-server/queryserver-client.ts | 8 +++++++- .../vscode-tests/cli-integration/queries.test.ts | 1 + 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index dd33e00fb..08135dec5 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -177,7 +177,13 @@ function getCommands( cliServer.restartCliServer(); await Promise.all([ queryRunner.restartQueryServer(progress, token), - ideServer.restart(), + async () => { + if (ideServer.isRunning()) { + await ideServer.restart(); + } else { + await ideServer.start(); + } + }, ]); void showAndLogInformationMessage("CodeQL Query Server restarted.", { outputLogger: queryServerLogger, diff --git a/extensions/ql-vscode/src/json-rpc-server.ts b/extensions/ql-vscode/src/json-rpc-server.ts index d6f7db2c8..88f656b54 100644 --- a/extensions/ql-vscode/src/json-rpc-server.ts +++ b/extensions/ql-vscode/src/json-rpc-server.ts @@ -23,6 +23,7 @@ export class ServerProcess implements Disposable { dispose(): void { void this.logger.log(`Stopping ${this.name}...`); this.connection.dispose(); + this.connection.end(); this.child.stdin!.end(); this.child.stderr!.destroy(); // TODO kill the process if it doesn't terminate after a certain time limit. diff --git a/extensions/ql-vscode/src/local-databases.ts b/extensions/ql-vscode/src/local-databases.ts index a4e32a5d0..944b35a71 100644 --- a/extensions/ql-vscode/src/local-databases.ts +++ b/extensions/ql-vscode/src/local-databases.ts @@ -1022,7 +1022,19 @@ export class DatabaseManager extends DisposableObject { token: vscode.CancellationToken, dbItem: DatabaseItem, ) { - await this.qs.deregisterDatabase(progress, token, dbItem); + try { + await this.qs.deregisterDatabase(progress, token, dbItem); + } catch (e) { + const message = getErrorMessage(e); + if (message === "Connection is disposed.") { + // This is expected if the query server is not running. + void extLogger.log( + `Could not de-register database '${dbItem.name}' because query server is not running.`, + ); + return; + } + throw e; + } } private async registerDatabase( progress: ProgressCallback, diff --git a/extensions/ql-vscode/src/query-server/queryserver-client.ts b/extensions/ql-vscode/src/query-server/queryserver-client.ts index e992d323e..1c84482a8 100644 --- a/extensions/ql-vscode/src/query-server/queryserver-client.ts +++ b/extensions/ql-vscode/src/query-server/queryserver-client.ts @@ -1,6 +1,6 @@ import { ensureFile } from "fs-extra"; -import { DisposableObject } from "../pure/disposable-object"; +import { DisposableObject, DisposeHandler } from "../pure/disposable-object"; import { CancellationToken } from "vscode"; import { createMessageConnection, RequestType } from "vscode-jsonrpc/node"; import * as cli from "../cli"; @@ -224,4 +224,10 @@ export class QueryServerClient extends DisposableObject { delete this.progressCallbacks[id]; } } + + public dispose(disposeHandler?: DisposeHandler | undefined): void { + this.progressCallbacks = {}; + this.stopQueryServer(); + super.dispose(disposeHandler); + } } diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts index de5d0071b..99e1d0668 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts @@ -30,6 +30,7 @@ import { AllCommands, QueryServerCommands } from "../../../src/common/commands"; /** * Integration tests for queries */ +jest.setTimeout(60_000); describeWithCodeQL()("Queries", () => { let dbItem: DatabaseItem; let databaseManager: DatabaseManager; From 9a40decc099ef805fa1c7119abb4f3506b942e21 Mon Sep 17 00:00:00 2001 From: Andrew Eisenberg Date: Fri, 14 Apr 2023 11:31:28 -0700 Subject: [PATCH 34/40] Remove timeout in test A global timeout has already been set. --- .../ql-vscode/test/vscode-tests/cli-integration/queries.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts index 99e1d0668..de5d0071b 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/queries.test.ts @@ -30,7 +30,6 @@ import { AllCommands, QueryServerCommands } from "../../../src/common/commands"; /** * Integration tests for queries */ -jest.setTimeout(60_000); describeWithCodeQL()("Queries", () => { let dbItem: DatabaseItem; let databaseManager: DatabaseManager; From a7f87658bb35f5a5ee5269888a6a72aa088211ea Mon Sep 17 00:00:00 2001 From: Anders Starcke Henriksen Date: Mon, 17 Apr 2023 09:28:42 +0200 Subject: [PATCH 35/40] Update extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts Co-authored-by: Andrew Eisenberg --- .../data-extensions-editor/external-api-usage-query.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts index d4f0c1234..dd289ed14 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts @@ -33,7 +33,6 @@ describe("runQuery", () => { // Test all queries for (const [lang, query] of Object.entries(fetchExternalApiQueries)) { - console.log(`hello ${lang}`); const options = { cliServer: { resolveQlpacks: jest.fn().mockResolvedValue({ From 1e42c1152f7e7388930cf71c0557af129d0aa21d Mon Sep 17 00:00:00 2001 From: Anders Starcke Henriksen Date: Mon, 17 Apr 2023 10:06:00 +0200 Subject: [PATCH 36/40] Update query ID. --- .../ql-vscode/src/data-extensions-editor/queries/csharp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts index 02355a6d3..5e2bcb93d 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts @@ -5,7 +5,7 @@ export const fetchExternalApisQuery: Query = { * @name Usage of APIs coming from external libraries * @description A list of 3rd party APIs used in the codebase. * @tags telemetry - * @id csharp/telemetry/fetch-external-apis + * @id cs/telemetry/fetch-external-apis */ import csharp From 923e13fce36e2d9761ac3465f78d7a5ab41cc63d Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Mon, 17 Apr 2023 14:25:57 +0200 Subject: [PATCH 37/40] Add better support for different languages in data extension editor There were still some places where we were hardcoding Java in the data extension editor. This changes these places to use the database item language instead. --- .../data-extensions-editor-view.ts | 6 +- .../extension-pack-picker.ts | 8 +-- .../src/data-extensions-editor/yaml.ts | 3 +- .../data-extensions-editor/yaml.test.ts | 27 ++++++++ .../extension-pack-picker.test.ts | 66 +++++++++++++++++++ 5 files changed, 104 insertions(+), 6 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts index f1835c9ef..24d08e769 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts @@ -148,7 +148,11 @@ export class DataExtensionsEditorView extends AbstractWebview< externalApiUsages: ExternalApiUsage[], modeledMethods: Record, ): Promise { - const yaml = createDataExtensionYaml(externalApiUsages, modeledMethods); + const yaml = createDataExtensionYaml( + this.databaseItem.language, + externalApiUsages, + modeledMethods, + ); await outputFile(this.modelFilename, yaml); diff --git a/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts b/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts index df47932a4..87f2d7220 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts @@ -23,7 +23,7 @@ const packNameLength = 128; export async function pickExtensionPackModelFile( cliServer: Pick, - databaseItem: Pick, + databaseItem: Pick, progress: ProgressCallback, token: CancellationToken, ): Promise { @@ -53,7 +53,7 @@ export async function pickExtensionPackModelFile( async function pickExtensionPack( cliServer: Pick, - databaseItem: Pick, + databaseItem: Pick, progress: ProgressCallback, token: CancellationToken, ): Promise { @@ -184,7 +184,7 @@ async function pickModelFile( } async function pickNewExtensionPack( - databaseItem: Pick, + databaseItem: Pick, token: CancellationToken, ): Promise { const workspaceFolders = getOnDiskWorkspaceFoldersObjects(); @@ -257,7 +257,7 @@ async function pickNewExtensionPack( version: "0.0.0", library: true, extensionTargets: { - "codeql/java-all": "*", + [`codeql/${databaseItem.language}-all`]: "*", }, dataExtensions: ["models/**/*.yml"], }), diff --git a/extensions/ql-vscode/src/data-extensions-editor/yaml.ts b/extensions/ql-vscode/src/data-extensions-editor/yaml.ts index 9cd869543..903272748 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/yaml.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/yaml.ts @@ -43,6 +43,7 @@ function createDataProperty( } export function createDataExtensionYaml( + language: string, externalApiUsages: ExternalApiUsage[], modeledMethods: Record, ) { @@ -69,7 +70,7 @@ export function createDataExtensionYaml( const extensions = Object.entries(extensiblePredicateDefinitions).map( ([type, definition]) => ` - addsTo: - pack: codeql/java-all + pack: codeql/${language}-all extensible: ${definition.extensiblePredicate} data:${createDataProperty( methodsByType[type as Exclude], diff --git a/extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts b/extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts index dde07e6a4..a499e12eb 100644 --- a/extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts +++ b/extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts @@ -6,6 +6,7 @@ import { describe("createDataExtensionYaml", () => { it("creates the correct YAML file", () => { const yaml = createDataExtensionYaml( + "java", [ { signature: "org.sql2o.Connection#createQuery(String)", @@ -99,6 +100,32 @@ describe("createDataExtensionYaml", () => { pack: codeql/java-all extensible: neutralModel data: [] +`); + }); + + it("includes the correct language", () => { + const yaml = createDataExtensionYaml("csharp", [], {}); + + expect(yaml).toEqual(`extensions: + - addsTo: + pack: codeql/csharp-all + extensible: sourceModel + data: [] + + - addsTo: + pack: codeql/csharp-all + extensible: sinkModel + data: [] + + - addsTo: + pack: codeql/csharp-all + extensible: summaryModel + data: [] + + - addsTo: + pack: codeql/csharp-all + extensible: neutralModel + data: [] `); }); }); diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts index 2846f6c88..88ae01773 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/extension-pack-picker.test.ts @@ -27,6 +27,7 @@ describe("pickExtensionPackModelFile", () => { }; const databaseItem = { name: "github/vscode-codeql", + language: "java", }; const cancellationTokenSource = new CancellationTokenSource(); @@ -304,6 +305,71 @@ describe("pickExtensionPackModelFile", () => { }); }); + it("allows user to create an extension pack when there are no extension packs with a different language", async () => { + const cliServer = mockCliServer({}, { models: [], data: {} }); + + const tmpDir = await dir({ + unsafeCleanup: true, + }); + + showQuickPickSpy.mockResolvedValueOnce({ + label: "codeql-custom-queries-java", + path: tmpDir.path, + } as QuickPickItem); + showInputBoxSpy.mockResolvedValueOnce("my-extension-pack"); + showInputBoxSpy.mockResolvedValue("models/my-model.yml"); + + expect( + await pickExtensionPackModelFile( + cliServer, + { + ...databaseItem, + language: "csharp", + }, + progress, + token, + ), + ).toEqual(join(tmpDir.path, "my-extension-pack", "models", "my-model.yml")); + expect(showQuickPickSpy).toHaveBeenCalledTimes(1); + expect(showInputBoxSpy).toHaveBeenCalledTimes(2); + expect(showInputBoxSpy).toHaveBeenCalledWith( + { + title: expect.stringMatching(/extension pack/i), + prompt: expect.stringMatching(/extension pack/i), + placeHolder: expect.stringMatching(/github\/vscode-codeql-extensions/), + validateInput: expect.any(Function), + }, + token, + ); + expect(showInputBoxSpy).toHaveBeenCalledWith( + { + title: expect.stringMatching(/model file/), + value: "models/github.vscode-codeql.model.yml", + validateInput: expect.any(Function), + }, + token, + ); + expect(cliServer.resolveQlpacks).toHaveBeenCalled(); + expect(cliServer.resolveExtensions).toHaveBeenCalled(); + + expect( + loadYaml( + await readFile( + join(tmpDir.path, "my-extension-pack", "codeql-pack.yml"), + "utf8", + ), + ), + ).toEqual({ + name: "my-extension-pack", + version: "0.0.0", + library: true, + extensionTargets: { + "codeql/csharp-all": "*", + }, + dataExtensions: ["models/**/*.yml"], + }); + }); + it("allows cancelling the workspace folder selection", async () => { const cliServer = mockCliServer({}, { models: [], data: {} }); From 79c39a0826e16d3cf19c47a6a8c72ca859273673 Mon Sep 17 00:00:00 2001 From: Koen Vlaswinkel Date: Mon, 17 Apr 2023 14:38:15 +0200 Subject: [PATCH 38/40] Add database language check to data extensions editor This will not allow the user to open the data extensions editor for a database if it is not one of the supported languages. The supported languages is a list of `string` rather than a list of `QueryLanguage` because a database item's language is also a `string`. --- .../data-extensions-editor-module.ts | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts index 886dcca6a..e330db0b1 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-module.ts @@ -11,6 +11,8 @@ import { showAndLogErrorMessage } from "../helpers"; import { withProgress } from "../progress"; import { pickExtensionPackModelFile } from "./extension-pack-picker"; +const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"]; + export class DataExtensionsEditorModule { private readonly queryStorageDir: string; @@ -51,15 +53,22 @@ export class DataExtensionsEditorModule { public getCommands(): DataExtensionsEditorCommands { return { - "codeQL.openDataExtensionsEditor": async () => - withProgress( - async (progress, token) => { - const db = this.databaseManager.currentDatabaseItem; - if (!db) { - void showAndLogErrorMessage("No database selected"); - return; - } + "codeQL.openDataExtensionsEditor": async () => { + const db = this.databaseManager.currentDatabaseItem; + if (!db) { + void showAndLogErrorMessage("No database selected"); + return; + } + if (!SUPPORTED_LANGUAGES.includes(db.language)) { + void showAndLogErrorMessage( + `The data extensions editor is not supported for ${db.language} databases.`, + ); + return; + } + + return withProgress( + async (progress, token) => { if (!(await this.cliServer.cliConstraints.supportsQlpacksKind())) { void showAndLogErrorMessage( `This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND.format()} or later.`, @@ -92,7 +101,8 @@ export class DataExtensionsEditorModule { { title: "Opening Data Extensions Editor", }, - ), + ); + }, }; } From 7ce0e0a75a7f9a62ba5a4430c925ca94ef93aac6 Mon Sep 17 00:00:00 2001 From: Anders Starcke Henriksen Date: Mon, 17 Apr 2023 14:42:37 +0200 Subject: [PATCH 39/40] Rename supported to modelled. --- .../DataExtensionsEditor.tsx | 14 +++++++------- .../{supported.spec.ts => modelled.spec.ts} | 18 +++++++++--------- .../{supported.ts => modelled.ts} | 10 +++++----- 3 files changed, 21 insertions(+), 21 deletions(-) rename extensions/ql-vscode/src/view/data-extensions-editor/__tests__/{supported.spec.ts => modelled.spec.ts} (54%) rename extensions/ql-vscode/src/view/data-extensions-editor/{supported.ts => modelled.ts} (52%) diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx index 998ade270..c27250df8 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx +++ b/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx @@ -16,7 +16,7 @@ import { ModeledMethod } from "../../data-extensions-editor/modeled-method"; import { MethodRow } from "./MethodRow"; import { assertNever } from "../../pure/helpers-pure"; import { vscode } from "../vscode-api"; -import { calculateSupportedPercentage } from "./supported"; +import { calculateModelledPercentage } from "./modelled"; export const DataExtensionsEditorContainer = styled.div` margin-top: 1rem; @@ -97,12 +97,12 @@ export function DataExtensionsEditor({ }; }, []); - const supportedPercentage = useMemo( - () => calculateSupportedPercentage(externalApiUsages), + const modelledPercentage = useMemo( + () => calculateModelledPercentage(externalApiUsages), [externalApiUsages], ); - const unsupportedPercentage = 100 - supportedPercentage; + const unModelledPercentage = 100 - modelledPercentage; const onChange = useCallback( (method: ExternalApiUsage, model: ModeledMethod) => { @@ -140,10 +140,10 @@ export function DataExtensionsEditor({ {externalApiUsages.length > 0 && ( <>
-

External API support stats

+

External API model stats

    -
  • Supported: {supportedPercentage.toFixed(2)}%
  • -
  • Unsupported: {unsupportedPercentage.toFixed(2)}%
  • +
  • Modelled: {modelledPercentage.toFixed(2)}%
  • +
  • Unmodelled: {unModelledPercentage.toFixed(2)}%
diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/__tests__/supported.spec.ts b/extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modelled.spec.ts similarity index 54% rename from extensions/ql-vscode/src/view/data-extensions-editor/__tests__/supported.spec.ts rename to extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modelled.spec.ts index 16e8b9b13..7cfd26a12 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/__tests__/supported.spec.ts +++ b/extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modelled.spec.ts @@ -1,13 +1,13 @@ -import { calculateSupportedPercentage } from "../supported"; +import { calculateModelledPercentage } from "../modelled"; -describe("calculateSupportedPercentage", () => { +describe("calculateModelledPercentage", () => { it("when there are no external API usages", () => { - expect(calculateSupportedPercentage([])).toBe(0); + expect(calculateModelledPercentage([])).toBe(0); }); - it("when there are is 1 supported external API usage", () => { + it("when there are is 1 modelled external API usage", () => { expect( - calculateSupportedPercentage([ + calculateModelledPercentage([ { supported: true, }, @@ -15,9 +15,9 @@ describe("calculateSupportedPercentage", () => { ).toBe(100); }); - it("when there are is 1 unsupported external API usage", () => { + it("when there are is 1 unmodelled external API usage", () => { expect( - calculateSupportedPercentage([ + calculateModelledPercentage([ { supported: false, }, @@ -25,9 +25,9 @@ describe("calculateSupportedPercentage", () => { ).toBe(0); }); - it("when there are multiple supporte and unsupported external API usage", () => { + it("when there are multiple modelled and unmodelled external API usage", () => { expect( - calculateSupportedPercentage([ + calculateModelledPercentage([ { supported: false, }, diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/supported.ts b/extensions/ql-vscode/src/view/data-extensions-editor/modelled.ts similarity index 52% rename from extensions/ql-vscode/src/view/data-extensions-editor/supported.ts rename to extensions/ql-vscode/src/view/data-extensions-editor/modelled.ts index 670b7b58d..c9d50f7b4 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/supported.ts +++ b/extensions/ql-vscode/src/view/data-extensions-editor/modelled.ts @@ -1,17 +1,17 @@ import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage"; -export function calculateSupportedPercentage( +export function calculateModelledPercentage( externalApiUsages: Array>, ): number { if (externalApiUsages.length === 0) { return 0; } - const supportedExternalApiUsages = externalApiUsages.filter( + const modelledExternalApiUsages = externalApiUsages.filter( (m) => m.supported, ); - const supportedRatio = - supportedExternalApiUsages.length / externalApiUsages.length; - return supportedRatio * 100; + const modelledRatio = + modelledExternalApiUsages.length / externalApiUsages.length; + return modelledRatio * 100; } From fb4f39dcfbaba510cddf70535618ce2350c7ff46 Mon Sep 17 00:00:00 2001 From: Anders Starcke Henriksen Date: Mon, 17 Apr 2023 15:00:41 +0200 Subject: [PATCH 40/40] Modelled -> Modeled. --- .../DataExtensionsEditor.tsx | 12 ++++++------ .../view/data-extensions-editor/MethodRow.tsx | 2 +- .../{modelled.spec.ts => modeled.spec.ts} | 18 +++++++++--------- .../src/view/data-extensions-editor/modeled.ts | 15 +++++++++++++++ .../view/data-extensions-editor/modelled.ts | 17 ----------------- 5 files changed, 31 insertions(+), 33 deletions(-) rename extensions/ql-vscode/src/view/data-extensions-editor/__tests__/{modelled.spec.ts => modeled.spec.ts} (55%) create mode 100644 extensions/ql-vscode/src/view/data-extensions-editor/modeled.ts delete mode 100644 extensions/ql-vscode/src/view/data-extensions-editor/modelled.ts diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx index c27250df8..4b7598765 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx +++ b/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx @@ -16,7 +16,7 @@ import { ModeledMethod } from "../../data-extensions-editor/modeled-method"; import { MethodRow } from "./MethodRow"; import { assertNever } from "../../pure/helpers-pure"; import { vscode } from "../vscode-api"; -import { calculateModelledPercentage } from "./modelled"; +import { calculateModeledPercentage } from "./modeled"; export const DataExtensionsEditorContainer = styled.div` margin-top: 1rem; @@ -97,12 +97,12 @@ export function DataExtensionsEditor({ }; }, []); - const modelledPercentage = useMemo( - () => calculateModelledPercentage(externalApiUsages), + const modeledPercentage = useMemo( + () => calculateModeledPercentage(externalApiUsages), [externalApiUsages], ); - const unModelledPercentage = 100 - modelledPercentage; + const unModeledPercentage = 100 - modeledPercentage; const onChange = useCallback( (method: ExternalApiUsage, model: ModeledMethod) => { @@ -142,8 +142,8 @@ export function DataExtensionsEditor({

External API model stats

    -
  • Modelled: {modelledPercentage.toFixed(2)}%
  • -
  • Unmodelled: {unModelledPercentage.toFixed(2)}%
  • +
  • Modeled: {modeledPercentage.toFixed(2)}%
  • +
  • Unmodeled: {unModeledPercentage.toFixed(2)}%
diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx index 47cb04f0e..6c0191b9b 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx +++ b/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx @@ -155,7 +155,7 @@ export const MethodRow = ({ value={modeledMethod?.type ?? "none"} onInput={handleTypeInput} > - Unmodelled + Unmodeled Source Sink Flow summary diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modelled.spec.ts b/extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modeled.spec.ts similarity index 55% rename from extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modelled.spec.ts rename to extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modeled.spec.ts index 7cfd26a12..6156ecd42 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modelled.spec.ts +++ b/extensions/ql-vscode/src/view/data-extensions-editor/__tests__/modeled.spec.ts @@ -1,13 +1,13 @@ -import { calculateModelledPercentage } from "../modelled"; +import { calculateModeledPercentage } from "../modeled"; -describe("calculateModelledPercentage", () => { +describe("calculateModeledPercentage", () => { it("when there are no external API usages", () => { - expect(calculateModelledPercentage([])).toBe(0); + expect(calculateModeledPercentage([])).toBe(0); }); - it("when there are is 1 modelled external API usage", () => { + it("when there are is 1 modeled external API usage", () => { expect( - calculateModelledPercentage([ + calculateModeledPercentage([ { supported: true, }, @@ -15,9 +15,9 @@ describe("calculateModelledPercentage", () => { ).toBe(100); }); - it("when there are is 1 unmodelled external API usage", () => { + it("when there are is 1 unmodeled external API usage", () => { expect( - calculateModelledPercentage([ + calculateModeledPercentage([ { supported: false, }, @@ -25,9 +25,9 @@ describe("calculateModelledPercentage", () => { ).toBe(0); }); - it("when there are multiple modelled and unmodelled external API usage", () => { + it("when there are multiple modeled and unmodeled external API usage", () => { expect( - calculateModelledPercentage([ + calculateModeledPercentage([ { supported: false, }, diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/modeled.ts b/extensions/ql-vscode/src/view/data-extensions-editor/modeled.ts new file mode 100644 index 000000000..dd29df262 --- /dev/null +++ b/extensions/ql-vscode/src/view/data-extensions-editor/modeled.ts @@ -0,0 +1,15 @@ +import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage"; + +export function calculateModeledPercentage( + externalApiUsages: Array>, +): number { + if (externalApiUsages.length === 0) { + return 0; + } + + const modeledExternalApiUsages = externalApiUsages.filter((m) => m.supported); + + const modeledRatio = + modeledExternalApiUsages.length / externalApiUsages.length; + return modeledRatio * 100; +} diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/modelled.ts b/extensions/ql-vscode/src/view/data-extensions-editor/modelled.ts deleted file mode 100644 index c9d50f7b4..000000000 --- a/extensions/ql-vscode/src/view/data-extensions-editor/modelled.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage"; - -export function calculateModelledPercentage( - externalApiUsages: Array>, -): number { - if (externalApiUsages.length === 0) { - return 0; - } - - const modelledExternalApiUsages = externalApiUsages.filter( - (m) => m.supported, - ); - - const modelledRatio = - modelledExternalApiUsages.length / externalApiUsages.length; - return modelledRatio * 100; -}