Merge branch 'main' into location-url-column
This commit is contained in:
@@ -44,21 +44,21 @@ choose to go through some of the Optional Test Cases.
|
||||
|
||||
#### Test case 2: Running a problem query and viewing results
|
||||
|
||||
1. Open the [javascript UnsafeJQueryPlugin query](https://github.com/github/codeql/blob/main/javascript/ql/src/Security/CWE-079/UnsafeJQueryPlugin.ql).
|
||||
1. Open the [javascript ReDoS query](https://github.com/github/codeql/blob/main/javascript/ql/src/Performance/ReDoS.ql).
|
||||
2. Select the `babel/babel` database (or download it if you don't have one already)
|
||||
3. Run a local query.
|
||||
4. Once the query completes:
|
||||
- Check that the result messages are rendered
|
||||
- Check that alert locations can be clicked on
|
||||
|
||||
#### Test case 3: Running a non-probem query and viewing results
|
||||
#### Test case 3: Running a non-problem query and viewing results
|
||||
|
||||
1. Open the [cpp FunLinesOfCode query](https://github.com/github/codeql/blob/main/cpp/ql/src/Metrics/Functions/FunLinesOfCode.ql).
|
||||
2. Select the `google/brotli` database (or download it if you don't have one already)
|
||||
3. Run a local query.
|
||||
4. Once the query completes:
|
||||
- Check that the results table is rendered
|
||||
- Check that alert locations can be clicked on
|
||||
- Check that result locations can be clicked on
|
||||
|
||||
#### Test case 3: Can use AST viewer
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { StorybookConfig } from "@storybook/core-common";
|
||||
import type { StorybookConfig } from "@storybook/react-webpack5";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
@@ -8,13 +8,13 @@ const config: StorybookConfig = {
|
||||
"@storybook/addon-interactions",
|
||||
"./vscode-theme-addon/preset.ts",
|
||||
],
|
||||
framework: "@storybook/react",
|
||||
core: {
|
||||
builder: "@storybook/builder-webpack5",
|
||||
framework: {
|
||||
name: "@storybook/react-webpack5",
|
||||
options: {},
|
||||
},
|
||||
features: {
|
||||
babelModeV7: true,
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
export default config;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addons } from "@storybook/addons";
|
||||
import { addons } from "@storybook/manager-api";
|
||||
import { themes } from "@storybook/theming";
|
||||
|
||||
addons.setConfig({
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
import { Preview } from "@storybook/react";
|
||||
import { themes } from "@storybook/theming";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
|
||||
// Allow all stories/components to use Codicons
|
||||
import "@vscode/codicons/dist/codicon.css";
|
||||
|
||||
// https://storybook.js.org/docs/react/configure/overview#configure-story-rendering
|
||||
export const parameters = {
|
||||
// All props starting with `on` will automatically receive an action as a prop
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
// All props matching these names will automatically get the correct control
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
// Use a dark theme to be aligned with VSCode
|
||||
docs: {
|
||||
theme: themes.dark,
|
||||
},
|
||||
backgrounds: {
|
||||
// The background is injected by our theme CSS files
|
||||
disable: true,
|
||||
},
|
||||
};
|
||||
|
||||
(window as any).acquireVsCodeApi = () => ({
|
||||
postMessage: action("post-vscode-message"),
|
||||
setState: action("set-vscode-state"),
|
||||
});
|
||||
|
||||
// https://storybook.js.org/docs/react/configure/overview#configure-story-rendering
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
// All props starting with `on` will automatically receive an action as a prop
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
// All props matching these names will automatically get the correct control
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
// Use a dark theme to be aligned with VSCode
|
||||
docs: {
|
||||
theme: themes.dark,
|
||||
},
|
||||
backgrounds: {
|
||||
// The background is injected by our theme CSS files
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import * as React from "react";
|
||||
import { FunctionComponent, useCallback } from "react";
|
||||
|
||||
import { useGlobals } from "@storybook/api";
|
||||
import { useGlobals } from "@storybook/manager-api";
|
||||
import {
|
||||
IconButton,
|
||||
Icons,
|
||||
WithTooltip,
|
||||
TooltipLinkList,
|
||||
Link,
|
||||
WithHideFn,
|
||||
WithTooltip,
|
||||
} from "@storybook/components";
|
||||
|
||||
import { themeNames, VSCodeTheme } from "./theme";
|
||||
@@ -26,7 +24,7 @@ export const ThemeSelector: FunctionComponent = () => {
|
||||
);
|
||||
|
||||
const createLinks = useCallback(
|
||||
(onHide: () => void): Link[] =>
|
||||
(onHide: () => void) =>
|
||||
Object.values(VSCodeTheme).map((theme) => ({
|
||||
id: theme,
|
||||
onClick() {
|
||||
@@ -44,8 +42,8 @@ export const ThemeSelector: FunctionComponent = () => {
|
||||
<WithTooltip
|
||||
placement="top"
|
||||
trigger="click"
|
||||
closeOnClick
|
||||
tooltip={({ onHide }: WithHideFn) => (
|
||||
closeOnOutsideClick
|
||||
tooltip={({ onHide }: { onHide: () => void }) => (
|
||||
<TooltipLinkList links={createLinks(onHide)} />
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { addons, types } from "@storybook/addons";
|
||||
import { addons, types } from "@storybook/manager-api";
|
||||
import { ThemeSelector } from "./ThemeSelector";
|
||||
|
||||
const ADDON_ID = "vscode-theme-addon";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function config(entry = []) {
|
||||
export function previewAnnotations(entry = []) {
|
||||
return [...entry, require.resolve("./preview.ts")];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useGlobals } from "@storybook/addons";
|
||||
import { useEffect } from "react";
|
||||
import type {
|
||||
AnyFramework,
|
||||
PartialStoryFn as StoryFunction,
|
||||
StoryContext,
|
||||
} from "@storybook/csf";
|
||||
@@ -34,11 +33,8 @@ const themeFiles: { [key in VSCodeTheme]: string } = {
|
||||
.default,
|
||||
};
|
||||
|
||||
export const withTheme = (
|
||||
StoryFn: StoryFunction<AnyFramework>,
|
||||
context: StoryContext<AnyFramework>,
|
||||
) => {
|
||||
const [{ vscodeTheme }] = useGlobals();
|
||||
export const withTheme = (StoryFn: StoryFunction, context: StoryContext) => {
|
||||
const { vscodeTheme } = context.globals;
|
||||
|
||||
useEffect(() => {
|
||||
const styleSelectorId =
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
## [UNRELEASED]
|
||||
|
||||
- Links to code on GitHub now include column numbers as well as line numbers. [#2406](https://github.com/github/vscode-codeql/pull/2406)
|
||||
- No longer highlight trailing commas for jump to definition. [#2615](https://github.com/github/vscode-codeql/pull/2615)
|
||||
|
||||
## 1.8.8 - 17 July 2023
|
||||
|
||||
- Remove support for CodeQL CLI versions older than 2.9.4. [#2610](https://github.com/github/vscode-codeql/pull/2610)
|
||||
- Implement syntax highlighting for the `additional` and `default` keywords. [#2609](https://github.com/github/vscode-codeql/pull/2609)
|
||||
|
||||
## 1.8.7 - 29 June 2023
|
||||
|
||||
- Show a run button on the file tab for query files, that will start a local query. This button will only show when a local database is selected in the extension. [#2544](https://github.com/github/vscode-codeql/pull/2544)
|
||||
- Add `CodeQL: Quick Evaluation Count` command to generate the count summary statistics of the results set
|
||||
without spending the time to compute locations and strings.
|
||||
- Add a `CodeQL: Quick Evaluation Count` command to generate the count summary statistics of the results set
|
||||
without spending the time to compute locations and strings. [#2475](https://github.com/github/vscode-codeql/pull/2475)
|
||||
|
||||
## 1.8.6 - 14 June 2023
|
||||
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"end": "^\\s*//\\s*#?endregion\\b"
|
||||
}
|
||||
},
|
||||
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\.\\<\\>\\/\\?\\s]+)"
|
||||
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\.\\<\\>\\/\\?\\s\\,]+)"
|
||||
}
|
||||
|
||||
45618
extensions/ql-vscode/package-lock.json
generated
45618
extensions/ql-vscode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.8.8",
|
||||
"version": "1.8.9",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -1733,8 +1733,8 @@
|
||||
"lint": "eslint . --ext .js,.ts,.tsx --max-warnings=0",
|
||||
"lint:markdown": "markdownlint-cli2 \"../../**/*.{md,mdx}\" \"!**/node_modules/**\" \"!**/.vscode-test/**\" \"!**/build/cli/v*/**\"",
|
||||
"format-staged": "lint-staged",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build",
|
||||
"lint:scenarios": "ts-node scripts/lint-scenarios.ts",
|
||||
"check-types": "find . -type f -name \"tsconfig.json\" -not -path \"./node_modules/*\" | sed -r 's|/[^/]+$||' | sort | uniq | xargs -I {} sh -c \"echo Checking types in {} && cd {} && npx tsc --noEmit\"",
|
||||
"postinstall": "patch-package",
|
||||
@@ -1786,17 +1786,22 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.13",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.18.6",
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
"@github/markdownlint-github": "^0.3.0",
|
||||
"@octokit/plugin-throttling": "^5.0.1",
|
||||
"@storybook/addon-actions": "^6.5.17-alpha.0",
|
||||
"@storybook/addon-essentials": "^6.5.17-alpha.0",
|
||||
"@storybook/addon-interactions": "^6.5.17-alpha.0",
|
||||
"@storybook/addon-links": "^6.5.17-alpha.0",
|
||||
"@storybook/builder-webpack5": "^6.5.17-alpha.0",
|
||||
"@storybook/manager-webpack5": "^6.5.17-alpha.0",
|
||||
"@storybook/react": "^6.5.17-alpha.0",
|
||||
"@storybook/testing-library": "^0.0.13",
|
||||
"@storybook/addon-actions": "^7.1.0",
|
||||
"@storybook/addon-essentials": "^7.1.0",
|
||||
"@storybook/addon-interactions": "^7.1.0",
|
||||
"@storybook/addon-links": "^7.1.0",
|
||||
"@storybook/components": "^7.1.0",
|
||||
"@storybook/csf": "^0.1.1",
|
||||
"@storybook/manager-api": "^7.1.0",
|
||||
"@storybook/react": "^7.1.0",
|
||||
"@storybook/react-webpack5": "^7.1.0",
|
||||
"@storybook/theming": "^7.1.0",
|
||||
"@testing-library/dom": "^9.3.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
@@ -1837,6 +1842,7 @@
|
||||
"@vscode/vsce": "^2.19.0",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"applicationinsights": "^2.3.5",
|
||||
"cosmiconfig": "^7.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "~6.8.1",
|
||||
"del": "^6.0.0",
|
||||
@@ -1868,6 +1874,7 @@
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^7.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"storybook": "^7.1.0",
|
||||
"tar-stream": "^3.0.0",
|
||||
"through2": "^4.0.2",
|
||||
"ts-jest": "^29.0.1",
|
||||
|
||||
@@ -1806,6 +1806,11 @@ export class CliVersionConstraint {
|
||||
"2.10.0",
|
||||
);
|
||||
|
||||
/**
|
||||
* CLI version where the `resolve extensions` subcommand exists.
|
||||
*/
|
||||
public static CLI_VERSION_WITH_RESOLVE_EXTENSIONS = new SemVer("2.10.2");
|
||||
|
||||
/**
|
||||
* CLI version where the `--evaluator-log` and related options to the query server were introduced,
|
||||
* on a per-query server basis.
|
||||
@@ -1882,6 +1887,12 @@ export class CliVersionConstraint {
|
||||
);
|
||||
}
|
||||
|
||||
async supportsResolveExtensions() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS,
|
||||
);
|
||||
}
|
||||
|
||||
async supportsStructuredEvalLog() {
|
||||
return this.isVersionAtLeast(
|
||||
CliVersionConstraint.CLI_VERSION_WITH_STRUCTURED_EVAL_LOG,
|
||||
|
||||
@@ -78,6 +78,16 @@ export class DataExtensionsEditorModule {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(await this.cliServer.cliConstraints.supportsResolveExtensions())
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS.format()} or later.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const modelFile = await pickExtensionPack(
|
||||
this.cliServer,
|
||||
db,
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ViewColumn,
|
||||
window,
|
||||
} from "vscode";
|
||||
import { join } from "path";
|
||||
import { RequestError } from "@octokit/request-error";
|
||||
import {
|
||||
AbstractWebview,
|
||||
@@ -21,8 +20,6 @@ import {
|
||||
showAndLogExceptionWithTelemetry,
|
||||
showAndLogErrorMessage,
|
||||
} from "../common/logging";
|
||||
import { outputFile, readFile } from "fs-extra";
|
||||
import { load as loadYaml } from "js-yaml";
|
||||
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { asError, assertNever, getErrorMessage } from "../common/helpers-pure";
|
||||
@@ -34,11 +31,6 @@ import { showResolvableLocation } from "../databases/local-databases/locations";
|
||||
import { decodeBqrsToExternalApiUsages } from "./bqrs";
|
||||
import { redactableError } from "../common/errors";
|
||||
import { readQueryResults, runQuery } from "./external-api-usage-query";
|
||||
import {
|
||||
createDataExtensionYamlsForApplicationMode,
|
||||
createDataExtensionYamlsForFrameworkMode,
|
||||
loadDataExtensionYaml,
|
||||
} from "./yaml";
|
||||
import { ExternalApiUsage } from "./external-api-usage";
|
||||
import { ModeledMethod } from "./modeled-method";
|
||||
import { ExtensionPack } from "./shared/extension-pack";
|
||||
@@ -49,8 +41,9 @@ import {
|
||||
} from "./auto-model";
|
||||
import { enableFrameworkMode, showLlmGeneration } from "../config";
|
||||
import { getAutoModelUsages } from "./auto-model-usages-query";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
|
||||
import { join } from "path";
|
||||
|
||||
export class DataExtensionsEditorView extends AbstractWebview<
|
||||
ToDataExtensionsEditorMessage,
|
||||
@@ -123,9 +116,14 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
|
||||
break;
|
||||
case "saveModeledMethods":
|
||||
await this.saveModeledMethods(
|
||||
await saveModeledMethods(
|
||||
this.extensionPack,
|
||||
this.databaseItem.name,
|
||||
this.databaseItem.language,
|
||||
msg.externalApiUsages,
|
||||
msg.modeledMethods,
|
||||
this.mode,
|
||||
this.app.logger,
|
||||
);
|
||||
await Promise.all([this.setViewState(), this.loadExternalApiUsages()]);
|
||||
|
||||
@@ -194,79 +192,16 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
||||
}
|
||||
}
|
||||
|
||||
protected async saveModeledMethods(
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
): Promise<void> {
|
||||
let yamls: Record<string, string>;
|
||||
switch (this.mode) {
|
||||
case Mode.Application:
|
||||
yamls = createDataExtensionYamlsForApplicationMode(
|
||||
this.databaseItem.language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
);
|
||||
break;
|
||||
case Mode.Framework:
|
||||
yamls = createDataExtensionYamlsForFrameworkMode(
|
||||
this.databaseItem.name,
|
||||
this.databaseItem.language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
assertNever(this.mode);
|
||||
}
|
||||
|
||||
for (const [filename, yaml] of Object.entries(yamls)) {
|
||||
await outputFile(join(this.extensionPack.path, filename), yaml);
|
||||
}
|
||||
|
||||
void this.app.logger.log(`Saved data extension YAML`);
|
||||
}
|
||||
|
||||
protected async loadExistingModeledMethods(): Promise<void> {
|
||||
try {
|
||||
const extensions = await this.cliServer.resolveExtensions(
|
||||
this.extensionPack.path,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
const modeledMethods = await loadModeledMethods(
|
||||
this.extensionPack,
|
||||
this.cliServer,
|
||||
this.app.logger,
|
||||
);
|
||||
|
||||
const modelFiles = new Set<string>();
|
||||
|
||||
if (this.extensionPack.path in extensions.data) {
|
||||
for (const extension of extensions.data[this.extensionPack.path]) {
|
||||
modelFiles.add(extension.file);
|
||||
}
|
||||
}
|
||||
|
||||
const existingModeledMethods: Record<string, ModeledMethod> = {};
|
||||
|
||||
for (const modelFile of modelFiles) {
|
||||
const yaml = await readFile(modelFile, "utf8");
|
||||
|
||||
const data = loadYaml(yaml, {
|
||||
filename: modelFile,
|
||||
});
|
||||
|
||||
const modeledMethods = loadDataExtensionYaml(data);
|
||||
if (!modeledMethods) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`Failed to parse data extension YAML ${modelFile}.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(modeledMethods)) {
|
||||
existingModeledMethods[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
await this.postMessage({
|
||||
t: "loadModeledMethods",
|
||||
modeledMethods: existingModeledMethods,
|
||||
modeledMethods,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
void showAndLogErrorMessage(
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { outputFile, readFile } from "fs-extra";
|
||||
import { ExternalApiUsage } from "./external-api-usage";
|
||||
import { ModeledMethod } from "./modeled-method";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { createDataExtensionYamls, loadDataExtensionYaml } from "./yaml";
|
||||
import { join } from "path";
|
||||
import { ExtensionPack } from "./shared/extension-pack";
|
||||
import {
|
||||
Logger,
|
||||
NotificationLogger,
|
||||
showAndLogErrorMessage,
|
||||
} from "../common/logging";
|
||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||
import { load as loadYaml } from "js-yaml";
|
||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||
import { pathsEqual } from "../common/files";
|
||||
|
||||
export async function saveModeledMethods(
|
||||
extensionPack: ExtensionPack,
|
||||
databaseName: string,
|
||||
language: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
mode: Mode,
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
const yamls = createDataExtensionYamls(
|
||||
databaseName,
|
||||
language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
mode,
|
||||
);
|
||||
|
||||
for (const [filename, yaml] of Object.entries(yamls)) {
|
||||
await outputFile(join(extensionPack.path, filename), yaml);
|
||||
}
|
||||
|
||||
void logger.log(`Saved data extension YAML`);
|
||||
}
|
||||
|
||||
export async function loadModeledMethods(
|
||||
extensionPack: ExtensionPack,
|
||||
cliServer: CodeQLCliServer,
|
||||
logger: NotificationLogger,
|
||||
): Promise<Record<string, ModeledMethod>> {
|
||||
const modelFiles = await listModelFiles(extensionPack.path, cliServer);
|
||||
|
||||
const existingModeledMethods: Record<string, ModeledMethod> = {};
|
||||
|
||||
for (const modelFile of modelFiles) {
|
||||
const yaml = await readFile(modelFile, "utf8");
|
||||
|
||||
const data = loadYaml(yaml, {
|
||||
filename: modelFile,
|
||||
});
|
||||
|
||||
const modeledMethods = loadDataExtensionYaml(data);
|
||||
if (!modeledMethods) {
|
||||
void showAndLogErrorMessage(
|
||||
logger,
|
||||
`Failed to parse data extension YAML ${modelFile}.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(modeledMethods)) {
|
||||
existingModeledMethods[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return existingModeledMethods;
|
||||
}
|
||||
|
||||
export async function listModelFiles(
|
||||
extensionPackPath: string,
|
||||
cliServer: CodeQLCliServer,
|
||||
): Promise<Set<string>> {
|
||||
const result = await cliServer.resolveExtensions(
|
||||
extensionPackPath,
|
||||
getOnDiskWorkspaceFolders(),
|
||||
);
|
||||
|
||||
const modelFiles = new Set<string>();
|
||||
for (const [path, extensions] of Object.entries(result.data)) {
|
||||
if (pathsEqual(path, extensionPackPath)) {
|
||||
for (const extension of extensions) {
|
||||
modelFiles.add(extension.file);
|
||||
}
|
||||
}
|
||||
}
|
||||
return modelFiles;
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
|
||||
import * as dataSchemaJson from "./data-schema.json";
|
||||
import { sanitizeExtensionPackName } from "./extension-pack-name";
|
||||
import { Mode } from "./shared/mode";
|
||||
import { assertNever } from "../common/helpers-pure";
|
||||
|
||||
const ajv = new Ajv({ allErrors: true });
|
||||
const dataSchemaValidate = ajv.compile(dataSchemaJson);
|
||||
@@ -66,6 +68,32 @@ export function createDataExtensionYaml(
|
||||
${extensions.join("\n")}`;
|
||||
}
|
||||
|
||||
export function createDataExtensionYamls(
|
||||
databaseName: string,
|
||||
language: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
mode: Mode,
|
||||
) {
|
||||
switch (mode) {
|
||||
case Mode.Application:
|
||||
return createDataExtensionYamlsForApplicationMode(
|
||||
language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
);
|
||||
case Mode.Framework:
|
||||
return createDataExtensionYamlsForFrameworkMode(
|
||||
databaseName,
|
||||
language,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
);
|
||||
default:
|
||||
assertNever(mode);
|
||||
}
|
||||
}
|
||||
|
||||
export function createDataExtensionYamlsForApplicationMode(
|
||||
language: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
@@ -16,9 +16,9 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof Alert>;
|
||||
} as Meta<typeof Alert>;
|
||||
|
||||
const Template: ComponentStory<typeof Alert> = (args) => <Alert {...args} />;
|
||||
const Template: StoryFn<typeof Alert> = (args) => <Alert {...args} />;
|
||||
|
||||
export const Warning = Template.bind({});
|
||||
Warning.args = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { CodePaths } from "../../view/common";
|
||||
import type { CodeFlow } from "../../variant-analysis/shared/analysis-result";
|
||||
@@ -9,11 +9,9 @@ export default {
|
||||
title: "Code Paths",
|
||||
component: CodePaths,
|
||||
decorators: [(Story) => <Story />],
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodePaths>;
|
||||
|
||||
const Template: ComponentStory<typeof CodePaths> = (args) => (
|
||||
<CodePaths {...args} />
|
||||
);
|
||||
const Template: StoryFn<typeof CodePaths> = (args) => <CodePaths {...args} />;
|
||||
|
||||
export const PowerShell = Template.bind({});
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { FileCodeSnippet } from "../../view/common";
|
||||
|
||||
export default {
|
||||
title: "File Code Snippet",
|
||||
component: FileCodeSnippet,
|
||||
} as ComponentMeta<typeof FileCodeSnippet>;
|
||||
} as Meta<typeof FileCodeSnippet>;
|
||||
|
||||
const Template: ComponentStory<typeof FileCodeSnippet> = (args) => (
|
||||
const Template: StoryFn<typeof FileCodeSnippet> = (args) => (
|
||||
<FileCodeSnippet {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { LastUpdated as LastUpdatedComponent } from "../../view/common/LastUpdated";
|
||||
|
||||
export default {
|
||||
title: "Last Updated",
|
||||
component: LastUpdatedComponent,
|
||||
} as ComponentMeta<typeof LastUpdatedComponent>;
|
||||
} as Meta<typeof LastUpdatedComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof LastUpdatedComponent> = (args) => (
|
||||
const Template: StoryFn<typeof LastUpdatedComponent> = (args) => (
|
||||
<LastUpdatedComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import StarCountComponent from "../../view/common/StarCount";
|
||||
|
||||
export default {
|
||||
title: "Star Count",
|
||||
component: StarCountComponent,
|
||||
} as ComponentMeta<typeof StarCountComponent>;
|
||||
} as Meta<typeof StarCountComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof StarCountComponent> = (args) => (
|
||||
const Template: StoryFn<typeof StarCountComponent> = (args) => (
|
||||
<StarCountComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import TextButtonComponent from "../../view/common/TextButton";
|
||||
|
||||
@@ -15,9 +15,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof TextButtonComponent>;
|
||||
} as Meta<typeof TextButtonComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof TextButtonComponent> = (args) => (
|
||||
const Template: StoryFn<typeof TextButtonComponent> = (args) => (
|
||||
<TextButtonComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { CodePaths, Codicon as CodiconComponent } from "../../../view/common";
|
||||
import { Codicon as CodiconComponent } from "../../../view/common";
|
||||
|
||||
// To regenerate the icons, use the following command from the `extensions/ql-vscode` directory:
|
||||
// jq -R '[inputs | [splits(", *")] as $row | $row[0]]' < node_modules/@vscode/codicons/dist/codicon.csv > src/stories/common/icon/vscode-icons.json
|
||||
@@ -17,9 +17,9 @@ export default {
|
||||
options: icons,
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodiconComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof CodiconComponent> = (args) => (
|
||||
const Template: StoryFn<typeof CodiconComponent> = (args) => (
|
||||
<CodiconComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import {
|
||||
CodePaths,
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
export default {
|
||||
title: "Icon/Error Icon",
|
||||
component: ErrorIconComponent,
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodePaths>;
|
||||
|
||||
const Template: ComponentStory<typeof ErrorIconComponent> = (args) => (
|
||||
const Template: StoryFn<typeof ErrorIconComponent> = (args) => (
|
||||
<ErrorIconComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import {
|
||||
CodePaths,
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
export default {
|
||||
title: "Icon/Success Icon",
|
||||
component: SuccessIconComponent,
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodePaths>;
|
||||
|
||||
const Template: ComponentStory<typeof SuccessIconComponent> = (args) => (
|
||||
const Template: StoryFn<typeof SuccessIconComponent> = (args) => (
|
||||
<SuccessIconComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import {
|
||||
CodePaths,
|
||||
@@ -10,9 +10,9 @@ import {
|
||||
export default {
|
||||
title: "Icon/Warning Icon",
|
||||
component: WarningIconComponent,
|
||||
} as ComponentMeta<typeof CodePaths>;
|
||||
} as Meta<typeof CodePaths>;
|
||||
|
||||
const Template: ComponentStory<typeof WarningIconComponent> = (args) => (
|
||||
const Template: StoryFn<typeof WarningIconComponent> = (args) => (
|
||||
<WarningIconComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { DataExtensionsEditor as DataExtensionsEditorComponent } from "../../view/data-extensions-editor/DataExtensionsEditor";
|
||||
@@ -9,11 +9,11 @@ import { CallClassification } from "../../data-extensions-editor/external-api-us
|
||||
export default {
|
||||
title: "Data Extensions Editor/Data Extensions Editor",
|
||||
component: DataExtensionsEditorComponent,
|
||||
} as ComponentMeta<typeof DataExtensionsEditorComponent>;
|
||||
} as Meta<typeof DataExtensionsEditorComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof DataExtensionsEditorComponent> = (
|
||||
args,
|
||||
) => <DataExtensionsEditorComponent {...args} />;
|
||||
const Template: StoryFn<typeof DataExtensionsEditorComponent> = (args) => (
|
||||
<DataExtensionsEditorComponent {...args} />
|
||||
);
|
||||
|
||||
export const DataExtensionsEditor = Template.bind({});
|
||||
DataExtensionsEditor.args = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { MethodRow as MethodRowComponent } from "../../view/data-extensions-editor/MethodRow";
|
||||
import { CallClassification } from "../../data-extensions-editor/external-api-usage";
|
||||
@@ -8,9 +8,9 @@ import { CallClassification } from "../../data-extensions-editor/external-api-us
|
||||
export default {
|
||||
title: "Data Extensions Editor/Method Row",
|
||||
component: MethodRowComponent,
|
||||
} as ComponentMeta<typeof MethodRowComponent>;
|
||||
} as Meta<typeof MethodRowComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof MethodRowComponent> = (args) => (
|
||||
const Template: StoryFn<typeof MethodRowComponent> = (args) => (
|
||||
<MethodRowComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { DataFlowPaths as DataFlowPathsComponent } from "../../view/data-flow-paths/DataFlowPaths";
|
||||
import { createMockDataFlowPaths } from "../../../test/factories/variant-analysis/shared/data-flow-paths";
|
||||
export default {
|
||||
title: "Data Flow Paths/Data Flow Paths",
|
||||
component: DataFlowPathsComponent,
|
||||
} as ComponentMeta<typeof DataFlowPathsComponent>;
|
||||
} as Meta<typeof DataFlowPathsComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof DataFlowPathsComponent> = (args) => (
|
||||
const Template: StoryFn<typeof DataFlowPathsComponent> = (args) => (
|
||||
<DataFlowPathsComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import AnalysisAlertResult from "../../view/variant-analysis/AnalysisAlertResult";
|
||||
import type { AnalysisAlert } from "../../variant-analysis/shared/analysis-result";
|
||||
@@ -8,9 +8,9 @@ import type { AnalysisAlert } from "../../variant-analysis/shared/analysis-resul
|
||||
export default {
|
||||
title: "Variant Analysis/Analysis Alert Result",
|
||||
component: AnalysisAlertResult,
|
||||
} as ComponentMeta<typeof AnalysisAlertResult>;
|
||||
} as Meta<typeof AnalysisAlertResult>;
|
||||
|
||||
const Template: ComponentStory<typeof AnalysisAlertResult> = (args) => (
|
||||
const Template: StoryFn<typeof AnalysisAlertResult> = (args) => (
|
||||
<AnalysisAlertResult {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import { VariantAnalysisFailureReason } from "../../variant-analysis/shared/variant-analysis";
|
||||
import { FailureReasonAlert } from "../../view/variant-analysis/FailureReasonAlert";
|
||||
|
||||
export default {
|
||||
title: "Variant Analysis/Failure reason alert",
|
||||
component: FailureReasonAlert,
|
||||
} as ComponentMeta<typeof FailureReasonAlert>;
|
||||
} as Meta<typeof FailureReasonAlert>;
|
||||
|
||||
const Template: ComponentStory<typeof FailureReasonAlert> = (args) => (
|
||||
const Template: StoryFn<typeof FailureReasonAlert> = (args) => (
|
||||
<FailureReasonAlert {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { QueryDetails as QueryDetailsComponent } from "../../view/variant-analysis/QueryDetails";
|
||||
@@ -29,9 +29,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof QueryDetailsComponent>;
|
||||
} as Meta<typeof QueryDetailsComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof QueryDetailsComponent> = (args) => (
|
||||
const Template: StoryFn<typeof QueryDetailsComponent> = (args) => (
|
||||
<QueryDetailsComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import {
|
||||
@@ -27,9 +27,9 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof RepoRow>;
|
||||
} as Meta<typeof RepoRow>;
|
||||
|
||||
const Template: ComponentStory<typeof RepoRow> = (args: RepoRowProps) => (
|
||||
const Template: StoryFn<typeof RepoRow> = (args: RepoRowProps) => (
|
||||
<RepoRow {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesFilter as RepositoriesFilterComponent } from "../../view/variant-analysis/RepositoriesFilter";
|
||||
import { FilterKey } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesFilterComponent>;
|
||||
} as Meta<typeof RepositoriesFilterComponent>;
|
||||
|
||||
export const RepositoriesFilter = () => {
|
||||
const [value, setValue] = useState(FilterKey.All);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSearch as RepositoriesSearchComponent } from "../../view/variant-analysis/RepositoriesSearch";
|
||||
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesSearchComponent>;
|
||||
} as Meta<typeof RepositoriesSearchComponent>;
|
||||
|
||||
export const RepositoriesSearch = () => {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSearchSortRow as RepositoriesSearchSortRowComponent } from "../../view/variant-analysis/RepositoriesSearchSortRow";
|
||||
import { defaultFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesSearchSortRowComponent>;
|
||||
} as Meta<typeof RepositoriesSearchSortRowComponent>;
|
||||
|
||||
export const RepositoriesSearchSortRow = () => {
|
||||
const [value, setValue] = useState(defaultFilterSortState);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta } from "@storybook/react";
|
||||
import { Meta } from "@storybook/react";
|
||||
|
||||
import { RepositoriesSort as RepositoriesSortComponent } from "../../view/variant-analysis/RepositoriesSort";
|
||||
import { SortKey } from "../../variant-analysis/shared/variant-analysis-filter-sort";
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof RepositoriesSortComponent>;
|
||||
} as Meta<typeof RepositoriesSortComponent>;
|
||||
|
||||
export const RepositoriesSort = () => {
|
||||
const [value, setValue] = useState(SortKey.Alphabetically);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysis as VariantAnalysisComponent } from "../../view/variant-analysis/VariantAnalysis";
|
||||
import {
|
||||
@@ -18,9 +18,9 @@ import { createMockRepositoryWithMetadata } from "../../../test/factories/varian
|
||||
export default {
|
||||
title: "Variant Analysis/Variant Analysis",
|
||||
component: VariantAnalysisComponent,
|
||||
} as ComponentMeta<typeof VariantAnalysisComponent>;
|
||||
} as Meta<typeof VariantAnalysisComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisComponent> = (args) => (
|
||||
const Template: StoryFn<typeof VariantAnalysisComponent> = (args) => (
|
||||
<VariantAnalysisComponent {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisStatus } from "../../variant-analysis/shared/variant-analysis";
|
||||
@@ -36,9 +36,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof VariantAnalysisActions>;
|
||||
} as Meta<typeof VariantAnalysisActions>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisActions> = (args) => (
|
||||
const Template: StoryFn<typeof VariantAnalysisActions> = (args) => (
|
||||
<VariantAnalysisActions {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
@@ -28,11 +28,11 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof VariantAnalysisAnalyzedRepos>;
|
||||
} as Meta<typeof VariantAnalysisAnalyzedRepos>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisAnalyzedRepos> = (
|
||||
args,
|
||||
) => <VariantAnalysisAnalyzedRepos {...args} />;
|
||||
const Template: StoryFn<typeof VariantAnalysisAnalyzedRepos> = (args) => (
|
||||
<VariantAnalysisAnalyzedRepos {...args} />
|
||||
);
|
||||
|
||||
const interpretedResultsForRepo = (
|
||||
nwo: string,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisHeader } from "../../view/variant-analysis/VariantAnalysisHeader";
|
||||
@@ -59,9 +59,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof VariantAnalysisHeader>;
|
||||
} as Meta<typeof VariantAnalysisHeader>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisHeader> = (args) => (
|
||||
const Template: StoryFn<typeof VariantAnalysisHeader> = (args) => (
|
||||
<VariantAnalysisHeader {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisLoading as VariantAnalysisLoadingComponent } from "../../view/variant-analysis/VariantAnalysisLoading";
|
||||
@@ -16,9 +16,9 @@ export default {
|
||||
),
|
||||
],
|
||||
argTypes: {},
|
||||
} as ComponentMeta<typeof VariantAnalysisLoadingComponent>;
|
||||
} as Meta<typeof VariantAnalysisLoadingComponent>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisLoadingComponent> = () => (
|
||||
const Template: StoryFn<typeof VariantAnalysisLoadingComponent> = () => (
|
||||
<VariantAnalysisLoadingComponent />
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisOutcomePanels } from "../../view/variant-analysis/VariantAnalysisOutcomePanels";
|
||||
@@ -27,11 +27,9 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof VariantAnalysisOutcomePanels>;
|
||||
} as Meta<typeof VariantAnalysisOutcomePanels>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisOutcomePanels> = (
|
||||
args,
|
||||
) => {
|
||||
const Template: StoryFn<typeof VariantAnalysisOutcomePanels> = (args) => {
|
||||
const [filterSortState, setFilterSortState] =
|
||||
useState<RepositoriesFilterSortState>(defaultFilterSortState);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentMeta, ComponentStory } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisSkippedRepositoriesTab } from "../../view/variant-analysis/VariantAnalysisSkippedRepositoriesTab";
|
||||
@@ -16,9 +16,9 @@ export default {
|
||||
</VariantAnalysisContainer>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof VariantAnalysisSkippedRepositoriesTab>;
|
||||
} as Meta<typeof VariantAnalysisSkippedRepositoriesTab>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisSkippedRepositoriesTab> = (
|
||||
const Template: StoryFn<typeof VariantAnalysisSkippedRepositoriesTab> = (
|
||||
args,
|
||||
) => <VariantAnalysisSkippedRepositoriesTab {...args} />;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { VariantAnalysisContainer } from "../../view/variant-analysis/VariantAnalysisContainer";
|
||||
import { VariantAnalysisStats } from "../../view/variant-analysis/VariantAnalysisStats";
|
||||
@@ -24,9 +24,9 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof VariantAnalysisStats>;
|
||||
} as Meta<typeof VariantAnalysisStats>;
|
||||
|
||||
const Template: ComponentStory<typeof VariantAnalysisStats> = (args) => (
|
||||
const Template: StoryFn<typeof VariantAnalysisStats> = (args) => (
|
||||
<VariantAnalysisStats {...args} />
|
||||
);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import { DataExtensionEditorViewState } from "../../data-extensions-editor/share
|
||||
import { ModeledMethodsList } from "./ModeledMethodsList";
|
||||
import { percentFormatter } from "./formatters";
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { groupMethods } from "../../data-extensions-editor/shared/sorting";
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
text-align: center;
|
||||
@@ -75,7 +74,9 @@ export function DataExtensionsEditor({
|
||||
const [externalApiUsages, setExternalApiUsages] = useState<
|
||||
ExternalApiUsage[]
|
||||
>(initialExternalApiUsages);
|
||||
const [unsavedModels, setUnsavedModels] = useState<Set<string>>(new Set());
|
||||
const [modifiedSignatures, setModifiedSignatures] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
const [modeledMethods, setModeledMethods] = useState<
|
||||
Record<string, ModeledMethod>
|
||||
@@ -119,15 +120,11 @@ export function DataExtensionsEditor({
|
||||
),
|
||||
};
|
||||
});
|
||||
setUnsavedModels(
|
||||
(oldUnsavedModels) =>
|
||||
setModifiedSignatures(
|
||||
(oldModifiedSignatures) =>
|
||||
new Set([
|
||||
...oldUnsavedModels,
|
||||
...modelsAffectedByNewModeledMethods(
|
||||
msg.modeledMethods,
|
||||
externalApiUsages,
|
||||
viewState?.mode ?? Mode.Application,
|
||||
),
|
||||
...oldModifiedSignatures,
|
||||
...Object.keys(msg.modeledMethods),
|
||||
]),
|
||||
);
|
||||
break;
|
||||
@@ -145,7 +142,7 @@ export function DataExtensionsEditor({
|
||||
return () => {
|
||||
window.removeEventListener("message", listener);
|
||||
};
|
||||
}, [externalApiUsages, viewState?.mode]);
|
||||
}, []);
|
||||
|
||||
const modeledPercentage = useMemo(
|
||||
() => calculateModeledPercentage(externalApiUsages),
|
||||
@@ -160,8 +157,9 @@ export function DataExtensionsEditor({
|
||||
...oldModeledMethods,
|
||||
[method.signature]: model,
|
||||
}));
|
||||
setUnsavedModels(
|
||||
(oldUnsavedModels) => new Set([...oldUnsavedModels, modelName]),
|
||||
setModifiedSignatures(
|
||||
(oldModifiedSignatures) =>
|
||||
new Set([...oldModifiedSignatures, method.signature]),
|
||||
);
|
||||
},
|
||||
[],
|
||||
@@ -179,12 +177,11 @@ export function DataExtensionsEditor({
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
});
|
||||
setUnsavedModels(new Set());
|
||||
setModifiedSignatures(new Set());
|
||||
}, [externalApiUsages, modeledMethods]);
|
||||
|
||||
const onSaveModelClick = useCallback(
|
||||
(
|
||||
modelName: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
) => {
|
||||
@@ -193,10 +190,12 @@ export function DataExtensionsEditor({
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
});
|
||||
setUnsavedModels((oldUnsavedModels) => {
|
||||
const newUnsavedModels = new Set(oldUnsavedModels);
|
||||
newUnsavedModels.delete(modelName);
|
||||
return newUnsavedModels;
|
||||
setModifiedSignatures((oldModifiedSignatures) => {
|
||||
const newModifiedSignatures = new Set([...oldModifiedSignatures]);
|
||||
for (const externalApiUsage of externalApiUsages) {
|
||||
newModifiedSignatures.delete(externalApiUsage.signature);
|
||||
}
|
||||
return newModifiedSignatures;
|
||||
});
|
||||
},
|
||||
[],
|
||||
@@ -317,8 +316,8 @@ export function DataExtensionsEditor({
|
||||
</ButtonsContainer>
|
||||
<ModeledMethodsList
|
||||
externalApiUsages={externalApiUsages}
|
||||
unsavedModels={unsavedModels}
|
||||
modeledMethods={modeledMethods}
|
||||
modifiedSignatures={modifiedSignatures}
|
||||
viewState={viewState}
|
||||
onChange={onChange}
|
||||
onSaveModelClick={onSaveModelClick}
|
||||
@@ -331,15 +330,3 @@ export function DataExtensionsEditor({
|
||||
</DataExtensionsEditorContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function modelsAffectedByNewModeledMethods(
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
mode: Mode,
|
||||
): string[] {
|
||||
const signatures = new Set(Object.keys(modeledMethods));
|
||||
const affectedExternalApiUsages = externalApiUsages.filter(
|
||||
(externalApiUsage) => signatures.has(externalApiUsage.signature),
|
||||
);
|
||||
return Object.keys(groupMethods(affectedExternalApiUsages, mode));
|
||||
}
|
||||
|
||||
@@ -71,15 +71,14 @@ type Props = {
|
||||
libraryVersion?: string;
|
||||
externalApiUsages: ExternalApiUsage[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modifiedSignatures: Set<string>;
|
||||
viewState: DataExtensionEditorViewState;
|
||||
hasUnsavedChanges: boolean;
|
||||
onChange: (
|
||||
modelName: string,
|
||||
externalApiUsage: ExternalApiUsage,
|
||||
modeledMethod: ModeledMethod,
|
||||
) => void;
|
||||
onSaveModelClick: (
|
||||
modelName: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
) => void;
|
||||
@@ -95,8 +94,8 @@ export const LibraryRow = ({
|
||||
libraryVersion,
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
modifiedSignatures,
|
||||
viewState,
|
||||
hasUnsavedChanges,
|
||||
onChange,
|
||||
onSaveModelClick,
|
||||
onGenerateFromLlmClick,
|
||||
@@ -137,11 +136,11 @@ export const LibraryRow = ({
|
||||
|
||||
const handleSave = useCallback(
|
||||
async (e: React.MouseEvent) => {
|
||||
onSaveModelClick(title, externalApiUsages, modeledMethods);
|
||||
onSaveModelClick(externalApiUsages, modeledMethods);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
[title, externalApiUsages, modeledMethods, onSaveModelClick],
|
||||
[externalApiUsages, modeledMethods, onSaveModelClick],
|
||||
);
|
||||
|
||||
const onChangeWithModelName = useCallback(
|
||||
@@ -151,6 +150,12 @@ export const LibraryRow = ({
|
||||
[onChange, title],
|
||||
);
|
||||
|
||||
const hasUnsavedChanges = useMemo(() => {
|
||||
return externalApiUsages.some((externalApiUsage) =>
|
||||
modifiedSignatures.has(externalApiUsage.signature),
|
||||
);
|
||||
}, [externalApiUsages, modifiedSignatures]);
|
||||
|
||||
return (
|
||||
<LibraryContainer>
|
||||
<TitleContainer onClick={toggleExpanded} aria-expanded={isExpanded}>
|
||||
@@ -195,6 +200,7 @@ export const LibraryRow = ({
|
||||
<ModeledMethodDataGrid
|
||||
externalApiUsages={externalApiUsages}
|
||||
modeledMethods={modeledMethods}
|
||||
modifiedSignatures={modifiedSignatures}
|
||||
mode={viewState.mode}
|
||||
onChange={onChangeWithModelName}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
VSCodeCheckbox,
|
||||
VSCodeDataGridCell,
|
||||
VSCodeDataGridRow,
|
||||
VSCodeLink,
|
||||
@@ -20,6 +19,10 @@ import { extensiblePredicateDefinitions } from "../../data-extensions-editor/pre
|
||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||
import { Dropdown } from "../common/Dropdown";
|
||||
import { MethodClassifications } from "./MethodClassifications";
|
||||
import {
|
||||
ModelingStatus,
|
||||
ModelingStatusIndicator,
|
||||
} from "./ModelingStatusIndicator";
|
||||
|
||||
const ApiOrMethodCell = styled(VSCodeDataGridCell)`
|
||||
display: flex;
|
||||
@@ -51,6 +54,7 @@ const modelTypeOptions: Array<{ value: ModeledMethodType; label: string }> = [
|
||||
type Props = {
|
||||
externalApiUsage: ExternalApiUsage;
|
||||
modeledMethod: ModeledMethod | undefined;
|
||||
methodIsUnsaved: boolean;
|
||||
mode: Mode;
|
||||
onChange: (
|
||||
externalApiUsage: ExternalApiUsage,
|
||||
@@ -59,11 +63,12 @@ type Props = {
|
||||
};
|
||||
|
||||
export const MethodRow = (props: Props) => {
|
||||
const { externalApiUsage, modeledMethod } = props;
|
||||
const { externalApiUsage, modeledMethod, methodIsUnsaved } = props;
|
||||
|
||||
const methodCanBeModeled =
|
||||
!externalApiUsage.supported ||
|
||||
(modeledMethod && modeledMethod?.type !== "none");
|
||||
(modeledMethod && modeledMethod?.type !== "none") ||
|
||||
methodIsUnsaved;
|
||||
|
||||
if (methodCanBeModeled) {
|
||||
return <ModelableMethodRow {...props} />;
|
||||
@@ -73,7 +78,8 @@ export const MethodRow = (props: Props) => {
|
||||
};
|
||||
|
||||
function ModelableMethodRow(props: Props) {
|
||||
const { externalApiUsage, modeledMethod, mode, onChange } = props;
|
||||
const { externalApiUsage, modeledMethod, methodIsUnsaved, mode, onChange } =
|
||||
props;
|
||||
|
||||
const argumentsList = useMemo(() => {
|
||||
if (externalApiUsage.methodParameters === "()") {
|
||||
@@ -192,10 +198,12 @@ function ModelableMethodRow(props: Props) {
|
||||
: undefined;
|
||||
const showKindCell = predicate?.supportedKinds;
|
||||
|
||||
const modelingStatus = getModelingStatus(modeledMethod, methodIsUnsaved);
|
||||
|
||||
return (
|
||||
<VSCodeDataGridRow>
|
||||
<ApiOrMethodCell gridColumn={1}>
|
||||
<VSCodeCheckbox />
|
||||
<ModelingStatusIndicator status={modelingStatus} />
|
||||
<ExternalApiUsageName {...props} />
|
||||
{mode === Mode.Application && (
|
||||
<UsagesButton onClick={jumpToUsage}>
|
||||
@@ -251,7 +259,7 @@ function UnmodelableMethodRow(props: Props) {
|
||||
return (
|
||||
<VSCodeDataGridRow>
|
||||
<ApiOrMethodCell gridColumn={1}>
|
||||
<VSCodeCheckbox />
|
||||
<ModelingStatusIndicator status="saved" />
|
||||
<ExternalApiUsageName {...props} />
|
||||
{mode === Mode.Application && (
|
||||
<UsagesButton onClick={jumpToUsage}>
|
||||
@@ -287,3 +295,17 @@ function sendJumpToUsageMessage(externalApiUsage: ExternalApiUsage) {
|
||||
location: externalApiUsage.usages[0].url,
|
||||
});
|
||||
}
|
||||
|
||||
function getModelingStatus(
|
||||
modeledMethod: ModeledMethod | undefined,
|
||||
methodIsUnsaved: boolean,
|
||||
): ModelingStatus {
|
||||
if (modeledMethod) {
|
||||
if (methodIsUnsaved) {
|
||||
return "unsaved";
|
||||
} else if (modeledMethod.type !== "none") {
|
||||
return "saved";
|
||||
}
|
||||
}
|
||||
return "unmodeled";
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { sortMethods } from "../../data-extensions-editor/shared/sorting";
|
||||
type Props = {
|
||||
externalApiUsages: ExternalApiUsage[];
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modifiedSignatures: Set<string>;
|
||||
mode: Mode;
|
||||
onChange: (
|
||||
externalApiUsage: ExternalApiUsage,
|
||||
@@ -24,6 +25,7 @@ type Props = {
|
||||
export const ModeledMethodDataGrid = ({
|
||||
externalApiUsages,
|
||||
modeledMethods,
|
||||
modifiedSignatures,
|
||||
mode,
|
||||
onChange,
|
||||
}: Props) => {
|
||||
@@ -56,6 +58,7 @@ export const ModeledMethodDataGrid = ({
|
||||
key={externalApiUsage.signature}
|
||||
externalApiUsage={externalApiUsage}
|
||||
modeledMethod={modeledMethods[externalApiUsage.signature]}
|
||||
methodIsUnsaved={modifiedSignatures.has(externalApiUsage.signature)}
|
||||
mode={mode}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
@@ -12,8 +12,8 @@ import { DataExtensionEditorViewState } from "../../data-extensions-editor/share
|
||||
|
||||
type Props = {
|
||||
externalApiUsages: ExternalApiUsage[];
|
||||
unsavedModels: Set<string>;
|
||||
modeledMethods: Record<string, ModeledMethod>;
|
||||
modifiedSignatures: Set<string>;
|
||||
viewState: DataExtensionEditorViewState;
|
||||
onChange: (
|
||||
modelName: string,
|
||||
@@ -21,7 +21,6 @@ type Props = {
|
||||
modeledMethod: ModeledMethod,
|
||||
) => void;
|
||||
onSaveModelClick: (
|
||||
modelName: string,
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
modeledMethods: Record<string, ModeledMethod>,
|
||||
) => void;
|
||||
@@ -38,8 +37,8 @@ const libraryNameOverrides: Record<string, string> = {
|
||||
|
||||
export const ModeledMethodsList = ({
|
||||
externalApiUsages,
|
||||
unsavedModels,
|
||||
modeledMethods,
|
||||
modifiedSignatures,
|
||||
viewState,
|
||||
onChange,
|
||||
onSaveModelClick,
|
||||
@@ -79,8 +78,8 @@ export const ModeledMethodsList = ({
|
||||
title={libraryNameOverrides[libraryName] ?? libraryName}
|
||||
libraryVersion={libraryVersions[libraryName]}
|
||||
externalApiUsages={grouped[libraryName]}
|
||||
hasUnsavedChanges={unsavedModels.has(libraryName)}
|
||||
modeledMethods={modeledMethods}
|
||||
modifiedSignatures={modifiedSignatures}
|
||||
viewState={viewState}
|
||||
onChange={onChange}
|
||||
onSaveModelClick={onSaveModelClick}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as React from "react";
|
||||
import { assertNever } from "../../common/helpers-pure";
|
||||
import { Codicon } from "../common/icon/Codicon";
|
||||
|
||||
export type ModelingStatus = "unmodeled" | "unsaved" | "saved";
|
||||
|
||||
interface Props {
|
||||
status: ModelingStatus;
|
||||
}
|
||||
|
||||
export function ModelingStatusIndicator({ status }: Props) {
|
||||
switch (status) {
|
||||
case "unmodeled":
|
||||
return <Codicon name="circle-large-outline" label="Method not modeled" />;
|
||||
case "unsaved":
|
||||
return <Codicon name="pass" label="Changes have not been saved" />;
|
||||
case "saved":
|
||||
return <Codicon name="pass-filled" label="Method modeled" />;
|
||||
default:
|
||||
assertNever(status);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { renderLocation } from "./result-table-utils";
|
||||
import { Location } from "./locations/Location";
|
||||
import { CellValue } from "../../common/bqrs-cli-types";
|
||||
|
||||
interface Props {
|
||||
@@ -16,14 +16,15 @@ export default function RawTableValue(props: Props): JSX.Element {
|
||||
typeof rawValue === "number" ||
|
||||
typeof rawValue === "boolean"
|
||||
) {
|
||||
return <span>{renderLocation(undefined, rawValue.toString())}</span>;
|
||||
return <Location label={rawValue.toString()} />;
|
||||
}
|
||||
|
||||
return renderLocation(
|
||||
rawValue.url,
|
||||
rawValue.label,
|
||||
props.databaseUri,
|
||||
undefined,
|
||||
props.onSelected,
|
||||
return (
|
||||
<Location
|
||||
loc={rawValue.url}
|
||||
label={rawValue.label}
|
||||
databaseUri={props.databaseUri}
|
||||
onClick={props.onSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { basename } from "path";
|
||||
import * as React from "react";
|
||||
import * as Sarif from "sarif";
|
||||
import * as Keys from "./result-keys";
|
||||
import { chevronDown, chevronRight, info, listUnordered } from "./octicons";
|
||||
import {
|
||||
className,
|
||||
renderLocation,
|
||||
ResultTableProps,
|
||||
selectableZebraStripe,
|
||||
jumpToLocation,
|
||||
@@ -18,15 +16,12 @@ import {
|
||||
NavigationDirection,
|
||||
SarifInterpretationData,
|
||||
} from "../../common/interface-types";
|
||||
import {
|
||||
parseSarifPlainTextMessage,
|
||||
parseSarifLocation,
|
||||
isNoLocation,
|
||||
} from "../../common/sarif-utils";
|
||||
import { isWholeFileLoc, isLineColumnLoc } from "../../common/bqrs-utils";
|
||||
import { parseSarifLocation, isNoLocation } from "../../common/sarif-utils";
|
||||
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
import { AlertTableHeader } from "./alert-table-header";
|
||||
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
|
||||
import { SarifLocation } from "./locations/SarifLocation";
|
||||
|
||||
export type AlertTableProps = ResultTableProps & {
|
||||
resultSet: InterpretedResultSet<SarifInterpretationData>;
|
||||
@@ -100,41 +95,6 @@ export class AlertTable extends React.Component<
|
||||
const { numTruncatedResults, sourceLocationPrefix } =
|
||||
resultSet.interpretation;
|
||||
|
||||
function renderRelatedLocations(
|
||||
msg: string,
|
||||
relatedLocations: Sarif.Location[],
|
||||
resultKey: Keys.PathNode | Keys.Result | undefined,
|
||||
): JSX.Element[] {
|
||||
const relatedLocationsById: { [k: string]: Sarif.Location } = {};
|
||||
for (const loc of relatedLocations) {
|
||||
relatedLocationsById[loc.id!] = loc;
|
||||
}
|
||||
|
||||
// match things like `[link-text](related-location-id)`
|
||||
const parts = parseSarifPlainTextMessage(msg);
|
||||
|
||||
return parts.map((part, i) => {
|
||||
if (typeof part === "string") {
|
||||
return <span key={i}>{part}</span>;
|
||||
} else {
|
||||
const renderedLocation = renderSarifLocationWithText(
|
||||
part.text,
|
||||
relatedLocationsById[part.dest],
|
||||
resultKey,
|
||||
);
|
||||
return <span key={i}>{renderedLocation}</span>;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderNonLocation(
|
||||
msg: string | undefined,
|
||||
locationHint: string,
|
||||
): JSX.Element | undefined {
|
||||
if (msg === undefined) return undefined;
|
||||
return <span title={locationHint}>{msg}</span>;
|
||||
}
|
||||
|
||||
const updateSelectionCallback = (
|
||||
resultKey: Keys.PathNode | Keys.Result | undefined,
|
||||
) => {
|
||||
@@ -147,65 +107,6 @@ export class AlertTable extends React.Component<
|
||||
};
|
||||
};
|
||||
|
||||
function renderSarifLocationWithText(
|
||||
text: string | undefined,
|
||||
loc: Sarif.Location,
|
||||
resultKey: Keys.PathNode | Keys.Result | undefined,
|
||||
): JSX.Element | undefined {
|
||||
const parsedLoc = parseSarifLocation(loc, sourceLocationPrefix);
|
||||
if ("hint" in parsedLoc) {
|
||||
return renderNonLocation(text, parsedLoc.hint);
|
||||
} else if (isWholeFileLoc(parsedLoc) || isLineColumnLoc(parsedLoc)) {
|
||||
return renderLocation(
|
||||
parsedLoc,
|
||||
text,
|
||||
databaseUri,
|
||||
undefined,
|
||||
updateSelectionCallback(resultKey),
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render sarif location as a link with the text being simply a
|
||||
* human-readable form of the location itself.
|
||||
*/
|
||||
function renderSarifLocation(
|
||||
loc: Sarif.Location,
|
||||
pathNodeKey: Keys.PathNode | Keys.Result | undefined,
|
||||
): JSX.Element | undefined {
|
||||
const parsedLoc = parseSarifLocation(loc, sourceLocationPrefix);
|
||||
if ("hint" in parsedLoc) {
|
||||
return renderNonLocation("[no location]", parsedLoc.hint);
|
||||
} else if (isWholeFileLoc(parsedLoc)) {
|
||||
const shortLocation = `${basename(parsedLoc.userVisibleFile)}`;
|
||||
const longLocation = `${parsedLoc.userVisibleFile}`;
|
||||
return renderLocation(
|
||||
parsedLoc,
|
||||
shortLocation,
|
||||
databaseUri,
|
||||
longLocation,
|
||||
updateSelectionCallback(pathNodeKey),
|
||||
);
|
||||
} else if (isLineColumnLoc(parsedLoc)) {
|
||||
const shortLocation = `${basename(parsedLoc.userVisibleFile)}:${
|
||||
parsedLoc.startLine
|
||||
}:${parsedLoc.startColumn}`;
|
||||
const longLocation = `${parsedLoc.userVisibleFile}`;
|
||||
return renderLocation(
|
||||
parsedLoc,
|
||||
shortLocation,
|
||||
databaseUri,
|
||||
longLocation,
|
||||
updateSelectionCallback(pathNodeKey),
|
||||
);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const toggler: (keys: Keys.ResultKey[]) => (e: React.MouseEvent) => void = (
|
||||
indices,
|
||||
) => {
|
||||
@@ -220,19 +121,32 @@ export class AlertTable extends React.Component<
|
||||
(result, resultIndex) => {
|
||||
const resultKey: Keys.Result = { resultIndex };
|
||||
const text = result.message.text || "[no text]";
|
||||
const msg: JSX.Element[] =
|
||||
result.relatedLocations === undefined
|
||||
? [<span key="0">{text}</span>]
|
||||
: renderRelatedLocations(text, result.relatedLocations, resultKey);
|
||||
const msg =
|
||||
result.relatedLocations === undefined ? (
|
||||
<span key="0">{text}</span>
|
||||
) : (
|
||||
<SarifMessageWithLocations
|
||||
msg={text}
|
||||
relatedLocations={result.relatedLocations}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
databaseUri={databaseUri}
|
||||
onClick={updateSelectionCallback(resultKey)}
|
||||
/>
|
||||
);
|
||||
|
||||
const currentResultExpanded = this.state.expanded.has(
|
||||
Keys.keyToString(resultKey),
|
||||
);
|
||||
const indicator = currentResultExpanded ? chevronDown : chevronRight;
|
||||
const location =
|
||||
result.locations !== undefined &&
|
||||
result.locations.length > 0 &&
|
||||
renderSarifLocation(result.locations[0], resultKey);
|
||||
const location = result.locations !== undefined &&
|
||||
result.locations.length > 0 && (
|
||||
<SarifLocation
|
||||
loc={result.locations[0]}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
databaseUri={databaseUri}
|
||||
onClick={updateSelectionCallback(resultKey)}
|
||||
/>
|
||||
);
|
||||
const locationCells = (
|
||||
<td className="vscode-codeql__location-cell">{location}</td>
|
||||
);
|
||||
@@ -342,17 +256,28 @@ export class AlertTable extends React.Component<
|
||||
const step = pathNodes[pathNodeIndex];
|
||||
const msg =
|
||||
step.location !== undefined &&
|
||||
step.location.message !== undefined
|
||||
? renderSarifLocationWithText(
|
||||
step.location.message.text,
|
||||
step.location,
|
||||
pathNodeKey,
|
||||
)
|
||||
: "[no location]";
|
||||
step.location.message !== undefined ? (
|
||||
<SarifLocation
|
||||
text={step.location.message.text}
|
||||
loc={step.location}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
databaseUri={databaseUri}
|
||||
onClick={updateSelectionCallback(pathNodeKey)}
|
||||
/>
|
||||
) : (
|
||||
"[no location]"
|
||||
);
|
||||
const additionalMsg =
|
||||
step.location !== undefined
|
||||
? renderSarifLocation(step.location, pathNodeKey)
|
||||
: "";
|
||||
step.location !== undefined ? (
|
||||
<SarifLocation
|
||||
loc={step.location}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
databaseUri={databaseUri}
|
||||
onClick={updateSelectionCallback(pathNodeKey)}
|
||||
/>
|
||||
) : (
|
||||
""
|
||||
);
|
||||
const isSelected = Keys.equalsNotUndefined(
|
||||
this.state.selectedItem,
|
||||
pathNodeKey,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { ResolvableLocationValue } from "../../../common/bqrs-cli-types";
|
||||
import { jumpToLocation } from "../result-table-utils";
|
||||
|
||||
interface Props {
|
||||
loc: ResolvableLocationValue;
|
||||
label: string;
|
||||
databaseUri: string;
|
||||
title?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A clickable location link.
|
||||
*/
|
||||
export function ClickableLocation({
|
||||
loc,
|
||||
label,
|
||||
databaseUri,
|
||||
title,
|
||||
onClick: onClick,
|
||||
}: Props): JSX.Element {
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
jumpToLocation(loc, databaseUri);
|
||||
onClick?.();
|
||||
},
|
||||
[loc, databaseUri, onClick],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*
|
||||
eslint-disable-next-line
|
||||
jsx-a11y/anchor-is-valid,
|
||||
*/}
|
||||
<a
|
||||
href="#"
|
||||
className="vscode-codeql__result-table-location-link"
|
||||
title={title}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
55
extensions/ql-vscode/src/view/results/locations/Location.tsx
Normal file
55
extensions/ql-vscode/src/view/results/locations/Location.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import * as React from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { UrlValue } from "../../../common/bqrs-cli-types";
|
||||
import {
|
||||
isStringLoc,
|
||||
tryGetResolvableLocation,
|
||||
} from "../../../common/bqrs-utils";
|
||||
import { convertNonPrintableChars } from "../../../common/text-utils";
|
||||
import { NonClickableLocation } from "./NonClickableLocation";
|
||||
import { ClickableLocation } from "./ClickableLocation";
|
||||
|
||||
interface Props {
|
||||
loc?: UrlValue;
|
||||
label?: string;
|
||||
databaseUri?: string;
|
||||
title?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A location link. Will be clickable if a location URL and database URI are provided.
|
||||
*/
|
||||
export function Location({
|
||||
loc,
|
||||
label,
|
||||
databaseUri,
|
||||
title,
|
||||
onClick,
|
||||
}: Props): JSX.Element {
|
||||
const resolvableLoc = useMemo(() => tryGetResolvableLocation(loc), [loc]);
|
||||
const displayLabel = useMemo(() => convertNonPrintableChars(label), [label]);
|
||||
|
||||
if (loc === undefined) {
|
||||
return <NonClickableLocation msg={displayLabel} />;
|
||||
}
|
||||
|
||||
if (isStringLoc(loc)) {
|
||||
return <a href={loc}>{loc}</a>;
|
||||
}
|
||||
|
||||
if (databaseUri === undefined || resolvableLoc === undefined) {
|
||||
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClickableLocation
|
||||
loc={resolvableLoc}
|
||||
label={displayLabel}
|
||||
databaseUri={databaseUri}
|
||||
title={title}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import * as React from "react";
|
||||
|
||||
interface Props {
|
||||
msg?: string;
|
||||
locationHint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-clickable location for when there isn't a valid link.
|
||||
* Designed to fit in with the other types of location components.
|
||||
*/
|
||||
export function NonClickableLocation({ msg, locationHint }: Props) {
|
||||
if (msg === undefined) return null;
|
||||
return <span title={locationHint}>{msg}</span>;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import * as React from "react";
|
||||
import * as Sarif from "sarif";
|
||||
import { isLineColumnLoc, isWholeFileLoc } from "../../../common/bqrs-utils";
|
||||
import { parseSarifLocation } from "../../../common/sarif-utils";
|
||||
import { basename } from "path";
|
||||
import { useMemo } from "react";
|
||||
import { Location } from "./Location";
|
||||
|
||||
interface Props {
|
||||
text?: string;
|
||||
loc?: Sarif.Location;
|
||||
sourceLocationPrefix: string;
|
||||
databaseUri: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A clickable SARIF location link.
|
||||
*
|
||||
* Custom text can be provided, but otherwise the text will be
|
||||
* a human-readable form of the location itself.
|
||||
*/
|
||||
export function SarifLocation({
|
||||
text,
|
||||
loc,
|
||||
sourceLocationPrefix,
|
||||
databaseUri,
|
||||
onClick,
|
||||
}: Props) {
|
||||
const parsedLoc = useMemo(
|
||||
() => loc && parseSarifLocation(loc, sourceLocationPrefix),
|
||||
[loc, sourceLocationPrefix],
|
||||
);
|
||||
if (parsedLoc === undefined || "hint" in parsedLoc) {
|
||||
return <Location label={text || "[no location]"} title={parsedLoc?.hint} />;
|
||||
}
|
||||
|
||||
if (isWholeFileLoc(parsedLoc)) {
|
||||
return (
|
||||
<Location
|
||||
loc={parsedLoc}
|
||||
label={text || `${basename(parsedLoc.userVisibleFile)}`}
|
||||
databaseUri={databaseUri}
|
||||
title={text ? undefined : `${parsedLoc.userVisibleFile}`}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLineColumnLoc(parsedLoc)) {
|
||||
return (
|
||||
<Location
|
||||
loc={parsedLoc}
|
||||
label={
|
||||
text ||
|
||||
`${basename(parsedLoc.userVisibleFile)}:${parsedLoc.startLine}:${
|
||||
parsedLoc.startColumn
|
||||
}`
|
||||
}
|
||||
databaseUri={databaseUri}
|
||||
title={text ? undefined : `${parsedLoc.userVisibleFile}`}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as React from "react";
|
||||
import * as Sarif from "sarif";
|
||||
import { parseSarifPlainTextMessage } from "../../../common/sarif-utils";
|
||||
import { SarifLocation } from "./SarifLocation";
|
||||
|
||||
interface Props {
|
||||
msg: string;
|
||||
relatedLocations: Sarif.Location[];
|
||||
sourceLocationPrefix: string;
|
||||
databaseUri: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a SARIF message and populates clickable locations.
|
||||
*/
|
||||
export function SarifMessageWithLocations({
|
||||
msg,
|
||||
relatedLocations,
|
||||
sourceLocationPrefix,
|
||||
databaseUri,
|
||||
onClick,
|
||||
}: Props) {
|
||||
const relatedLocationsById: Map<number, Sarif.Location> = new Map();
|
||||
for (const loc of relatedLocations) {
|
||||
if (loc.id !== undefined) {
|
||||
relatedLocationsById.set(loc.id, loc);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{parseSarifPlainTextMessage(msg).map((part, i) => {
|
||||
if (typeof part === "string") {
|
||||
return <span key={i}>{part}</span>;
|
||||
} else {
|
||||
return (
|
||||
<SarifLocation
|
||||
key={i}
|
||||
text={part.text}
|
||||
loc={relatedLocationsById.get(part.dest)}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
databaseUri={databaseUri}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { UrlValue, ResolvableLocationValue } from "../../common/bqrs-cli-types";
|
||||
import { isStringLoc, tryGetResolvableLocation } from "../../common/bqrs-utils";
|
||||
import { ResolvableLocationValue } from "../../common/bqrs-cli-types";
|
||||
import {
|
||||
RawResultsSortState,
|
||||
QueryMetadata,
|
||||
@@ -9,7 +8,6 @@ import {
|
||||
} from "../../common/interface-types";
|
||||
import { assertNever } from "../../common/helpers-pure";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { convertNonPrintableChars } from "../../common/text-utils";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
|
||||
export interface ResultTableProps {
|
||||
@@ -44,21 +42,6 @@ export const oddRowClassName = "vscode-codeql__result-table-row--odd";
|
||||
export const pathRowClassName = "vscode-codeql__result-table-row--path";
|
||||
export const selectedRowClassName = "vscode-codeql__result-table-row--selected";
|
||||
|
||||
export function jumpToLocationHandler(
|
||||
loc: ResolvableLocationValue,
|
||||
databaseUri: string,
|
||||
callback?: () => void,
|
||||
): (e: React.MouseEvent) => void {
|
||||
return (e) => {
|
||||
jumpToLocation(loc, databaseUri);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function jumpToLocation(
|
||||
loc: ResolvableLocationValue,
|
||||
databaseUri: string,
|
||||
@@ -77,47 +60,6 @@ export function openFile(filePath: string): void {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a location as a link which when clicked displays the original location.
|
||||
*/
|
||||
export function renderLocation(
|
||||
loc?: UrlValue,
|
||||
label?: string,
|
||||
databaseUri?: string,
|
||||
title?: string,
|
||||
callback?: () => void,
|
||||
): JSX.Element {
|
||||
const displayLabel = convertNonPrintableChars(label!);
|
||||
|
||||
if (loc === undefined) {
|
||||
return <span>{displayLabel}</span>;
|
||||
} else if (isStringLoc(loc)) {
|
||||
return <a href={loc}>{loc}</a>;
|
||||
}
|
||||
|
||||
const resolvableLoc = tryGetResolvableLocation(loc);
|
||||
if (databaseUri !== undefined && resolvableLoc !== undefined) {
|
||||
return (
|
||||
<>
|
||||
{/*
|
||||
eslint-disable-next-line
|
||||
jsx-a11y/anchor-is-valid,
|
||||
*/}
|
||||
<a
|
||||
href="#"
|
||||
className="vscode-codeql__result-table-location-link"
|
||||
title={title}
|
||||
onClick={jumpToLocationHandler(resolvableLoc, databaseUri, callback)}
|
||||
>
|
||||
{displayLabel}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return <span title={title}>{displayLabel}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attributes for a zebra-striped table row at position `index`.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
VariantAnalysisStats,
|
||||
VariantAnalysisStatsProps,
|
||||
} from "../VariantAnalysisStats";
|
||||
import { userEvent } from "@storybook/testing-library";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
describe(VariantAnalysisStats.name, () => {
|
||||
const onViewLogsClick = jest.fn();
|
||||
@@ -141,13 +141,13 @@ describe(VariantAnalysisStats.name, () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders 'View logs' link when the variant analysis status is succeeded", () => {
|
||||
it("renders 'View logs' link when the variant analysis status is succeeded", async () => {
|
||||
render({
|
||||
variantAnalysisStatus: VariantAnalysisStatus.Succeeded,
|
||||
completedAt: new Date(),
|
||||
});
|
||||
|
||||
userEvent.click(screen.getByText("View logs"));
|
||||
await userEvent.click(screen.getByText("View logs"));
|
||||
expect(onViewLogsClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
import { Uri, workspace } from "vscode";
|
||||
import * as tmp from "tmp";
|
||||
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
||||
import { getActivatedExtension } from "../../global.helper";
|
||||
import { mkdirSync, writeFileSync } from "fs";
|
||||
import {
|
||||
listModelFiles,
|
||||
loadModeledMethods,
|
||||
} from "../../../../src/data-extensions-editor/modeled-method-fs";
|
||||
import { ExtensionPack } from "../../../../src/data-extensions-editor/shared/extension-pack";
|
||||
import { join } from "path";
|
||||
import { extLogger } from "../../../../src/common/logging/vscode";
|
||||
import { homedir } from "os";
|
||||
|
||||
const dummyExtensionPackContents = `
|
||||
name: dummy/pack
|
||||
version: 0.0.0
|
||||
library: true
|
||||
extensionTargets:
|
||||
codeql/java-all: '*'
|
||||
dataExtensions:
|
||||
- models/**/*.yml
|
||||
`;
|
||||
|
||||
const dummyModelContents = `
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/java-all
|
||||
extensible: sourceModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/java-all
|
||||
extensible: sinkModel
|
||||
data:
|
||||
- ["org.eclipse.jetty.server","Server",true,"getConnectors","()","","Argument[this]","sql","manual"]
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/java-all
|
||||
extensible: summaryModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/java-all
|
||||
extensible: neutralModel
|
||||
data: []
|
||||
`;
|
||||
|
||||
describe("modeled-method-fs", () => {
|
||||
let tmpDir: string;
|
||||
let tmpDirRemoveCallback: (() => void) | undefined;
|
||||
let workspacePath: string;
|
||||
let cli: CodeQLCliServer;
|
||||
|
||||
beforeEach(async () => {
|
||||
// On windows, make sure to use a temp directory that isn't an alias and therefore won't be canonicalised by CodeQL.
|
||||
// See https://github.com/github/vscode-codeql/pull/2605 for more context.
|
||||
const t = tmp.dirSync({
|
||||
dir:
|
||||
process.platform === "win32"
|
||||
? join(homedir(), "AppData", "Local", "Temp")
|
||||
: undefined,
|
||||
});
|
||||
tmpDir = t.name;
|
||||
tmpDirRemoveCallback = t.removeCallback;
|
||||
|
||||
const workspaceFolder = {
|
||||
uri: Uri.file(join(tmpDir, "workspace")),
|
||||
name: "workspace",
|
||||
index: 0,
|
||||
};
|
||||
workspacePath = workspaceFolder.uri.fsPath;
|
||||
mkdirSync(workspacePath);
|
||||
jest
|
||||
.spyOn(workspace, "workspaceFolders", "get")
|
||||
.mockReturnValue([workspaceFolder]);
|
||||
|
||||
const extension = await getActivatedExtension();
|
||||
cli = extension.cliServer;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tmpDirRemoveCallback?.();
|
||||
});
|
||||
|
||||
function writeExtensionPackFiles(
|
||||
extensionPackName: string,
|
||||
modelFileNames: string[],
|
||||
): string {
|
||||
const extensionPackPath = join(workspacePath, extensionPackName);
|
||||
mkdirSync(extensionPackPath);
|
||||
|
||||
writeFileSync(
|
||||
join(extensionPackPath, "codeql-pack.yml"),
|
||||
dummyExtensionPackContents,
|
||||
);
|
||||
|
||||
mkdirSync(join(extensionPackPath, "models"));
|
||||
for (const filename of modelFileNames) {
|
||||
writeFileSync(
|
||||
join(extensionPackPath, "models", filename),
|
||||
dummyModelContents,
|
||||
);
|
||||
}
|
||||
|
||||
return extensionPackPath;
|
||||
}
|
||||
|
||||
function makeExtensionPack(path: string): ExtensionPack {
|
||||
return {
|
||||
path,
|
||||
yamlPath: path,
|
||||
name: "dummy/pack",
|
||||
version: "0.0.1",
|
||||
extensionTargets: {},
|
||||
dataExtensions: [],
|
||||
};
|
||||
}
|
||||
|
||||
describe("listModelFiles", () => {
|
||||
it("should return the empty set when the extension pack is empty", async () => {
|
||||
if (!(await cli.cliConstraints.supportsResolveExtensions())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionPackPath = writeExtensionPackFiles("extension-pack", []);
|
||||
|
||||
const modelFiles = await listModelFiles(extensionPackPath, cli);
|
||||
expect(modelFiles).toEqual(new Set());
|
||||
});
|
||||
|
||||
it("should find all model files", async () => {
|
||||
if (!(await cli.cliConstraints.supportsResolveExtensions())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionPackPath = writeExtensionPackFiles("extension-pack", [
|
||||
"library1.model.yml",
|
||||
"library2.model.yml",
|
||||
]);
|
||||
|
||||
const modelFiles = await listModelFiles(extensionPackPath, cli);
|
||||
expect(modelFiles).toEqual(
|
||||
new Set([
|
||||
join(extensionPackPath, "models", "library1.model.yml"),
|
||||
join(extensionPackPath, "models", "library2.model.yml"),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("should ignore model files from other extension packs", async () => {
|
||||
if (!(await cli.cliConstraints.supportsResolveExtensions())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionPackPath = writeExtensionPackFiles("extension-pack", [
|
||||
"library1.model.yml",
|
||||
]);
|
||||
writeExtensionPackFiles("another-extension-pack", ["library2.model.yml"]);
|
||||
|
||||
const modelFiles = await listModelFiles(extensionPackPath, cli);
|
||||
expect(modelFiles).toEqual(
|
||||
new Set([join(extensionPackPath, "models", "library1.model.yml")]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadModeledMethods", () => {
|
||||
it("should load modeled methods", async () => {
|
||||
if (!(await cli.cliConstraints.supportsResolveExtensions())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionPackPath = writeExtensionPackFiles("extension-pack", [
|
||||
"library.model.yml",
|
||||
]);
|
||||
|
||||
const modeledMethods = await loadModeledMethods(
|
||||
makeExtensionPack(extensionPackPath),
|
||||
cli,
|
||||
extLogger,
|
||||
);
|
||||
|
||||
expect(Object.keys(modeledMethods).length).toEqual(1);
|
||||
expect(Object.keys(modeledMethods)[0]).toEqual(
|
||||
"org.eclipse.jetty.server.Server#getConnectors()",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user