Merge branch 'main' into dbartol/passthru
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
**/* @github/codeql-vscode-reviewers
|
**/* @github/codeql-vscode-reviewers
|
||||||
**/variant-analysis/ @github/code-scanning-secexp-reviewers
|
**/variant-analysis/ @github/code-scanning-secexp-reviewers
|
||||||
**/databases/ @github/code-scanning-secexp-reviewers
|
**/databases/ @github/code-scanning-secexp-reviewers
|
||||||
|
**/method-modeling/ @github/code-scanning-secexp-reviewers
|
||||||
**/model-editor/ @github/code-scanning-secexp-reviewers
|
**/model-editor/ @github/code-scanning-secexp-reviewers
|
||||||
**/queries-panel/ @github/code-scanning-secexp-reviewers
|
**/queries-panel/ @github/code-scanning-secexp-reviewers
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
- It is now possible to show the language of query history items using the `%l` specifier in the `codeQL.queryHistory.format` setting. Note that this only works for queries run after this upgrade, and older items will show `unknown` as a language. [#2892](https://github.com/github/vscode-codeql/pull/2892)
|
- It is now possible to show the language of query history items using the `%l` specifier in the `codeQL.queryHistory.format` setting. Note that this only works for queries run after this upgrade, and older items will show `unknown` as a language. [#2892](https://github.com/github/vscode-codeql/pull/2892)
|
||||||
- Increase the required version of VS Code to 1.82.0. [#2877](https://github.com/github/vscode-codeql/pull/2877)
|
- Increase the required version of VS Code to 1.82.0. [#2877](https://github.com/github/vscode-codeql/pull/2877)
|
||||||
- Fix a bug where the query server was restarted twice after configuration changes. [#2884](https://github.com/github/vscode-codeql/pull/2884).
|
- Fix a bug where the query server was restarted twice after configuration changes. [#2884](https://github.com/github/vscode-codeql/pull/2884).
|
||||||
|
- Add support for the `telemetry.telemetryLevel` setting. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code). [#2824](https://github.com/github/vscode-codeql/pull/2824).
|
||||||
|
- Fix syntax highlighting directly after import statements with instantiation arguments. [#2792](https://github.com/github/vscode-codeql/pull/2792)
|
||||||
|
|
||||||
## 1.9.1 - 29 September 2023
|
## 1.9.1 - 29 September 2023
|
||||||
|
|
||||||
|
|||||||
@@ -450,13 +450,20 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false,
|
"default": false,
|
||||||
"scope": "application",
|
"scope": "application",
|
||||||
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent to GitHub. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code)"
|
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the one of the global telemetry settings (`#telemetry.enableTelemetry#` or `#telemetry.telemetryLevel#`) must be enabled for telemetry to be sent to GitHub. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code)",
|
||||||
|
"tags": [
|
||||||
|
"telemetry",
|
||||||
|
"usesOnlineServices"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"codeQL.telemetry.logTelemetry": {
|
"codeQL.telemetry.logTelemetry": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false,
|
"default": false,
|
||||||
"scope": "application",
|
"scope": "application",
|
||||||
"description": "Specifies whether or not to write telemetry events to the extension log."
|
"description": "Specifies whether or not to write telemetry events to the extension log.",
|
||||||
|
"tags": [
|
||||||
|
"telemetry"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,8 +546,7 @@ interface RefreshMethods {
|
|||||||
|
|
||||||
interface SaveModeledMethods {
|
interface SaveModeledMethods {
|
||||||
t: "saveModeledMethods";
|
t: "saveModeledMethods";
|
||||||
methods: Method[];
|
methodSignatures?: string[];
|
||||||
modeledMethods: Record<string, ModeledMethod>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GenerateMethodMessage {
|
interface GenerateMethodMessage {
|
||||||
@@ -587,7 +586,7 @@ interface SetInModelingModeMessage {
|
|||||||
|
|
||||||
interface RevealMethodMessage {
|
interface RevealMethodMessage {
|
||||||
t: "revealMethod";
|
t: "revealMethod";
|
||||||
method: Method;
|
methodSignature: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToModelEditorMessage =
|
export type ToModelEditorMessage =
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import {
|
|||||||
Extension,
|
Extension,
|
||||||
ExtensionContext,
|
ExtensionContext,
|
||||||
ConfigurationChangeEvent,
|
ConfigurationChangeEvent,
|
||||||
|
env,
|
||||||
} from "vscode";
|
} from "vscode";
|
||||||
import TelemetryReporter from "vscode-extension-telemetry";
|
import TelemetryReporter from "vscode-extension-telemetry";
|
||||||
import {
|
import {
|
||||||
ConfigListener,
|
ConfigListener,
|
||||||
CANARY_FEATURES,
|
CANARY_FEATURES,
|
||||||
ENABLE_TELEMETRY,
|
ENABLE_TELEMETRY,
|
||||||
GLOBAL_ENABLE_TELEMETRY,
|
|
||||||
LOG_TELEMETRY,
|
LOG_TELEMETRY,
|
||||||
isIntegrationTestMode,
|
isIntegrationTestMode,
|
||||||
isCanary,
|
isCanary,
|
||||||
@@ -59,8 +59,6 @@ export class ExtensionTelemetryListener
|
|||||||
extends ConfigListener
|
extends ConfigListener
|
||||||
implements AppTelemetry
|
implements AppTelemetry
|
||||||
{
|
{
|
||||||
static relevantSettings = [ENABLE_TELEMETRY, CANARY_FEATURES];
|
|
||||||
|
|
||||||
private reporter?: TelemetryReporter;
|
private reporter?: TelemetryReporter;
|
||||||
|
|
||||||
private cliVersionStr = NOT_SET_CLI_VERSION;
|
private cliVersionStr = NOT_SET_CLI_VERSION;
|
||||||
@@ -72,6 +70,10 @@ export class ExtensionTelemetryListener
|
|||||||
private readonly ctx: ExtensionContext,
|
private readonly ctx: ExtensionContext,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
env.onDidChangeTelemetryEnabled(async () => {
|
||||||
|
await this.initialize();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,10 +93,7 @@ export class ExtensionTelemetryListener
|
|||||||
async handleDidChangeConfiguration(
|
async handleDidChangeConfiguration(
|
||||||
e: ConfigurationChangeEvent,
|
e: ConfigurationChangeEvent,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (
|
if (e.affectsConfiguration(ENABLE_TELEMETRY.qualifiedName)) {
|
||||||
e.affectsConfiguration("codeQL.telemetry.enableTelemetry") ||
|
|
||||||
e.affectsConfiguration("telemetry.enableTelemetry")
|
|
||||||
) {
|
|
||||||
await this.initialize();
|
await this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +101,7 @@ export class ExtensionTelemetryListener
|
|||||||
// Re-request if codeQL.canary is being set to `true` and telemetry
|
// Re-request if codeQL.canary is being set to `true` and telemetry
|
||||||
// is not currently enabled.
|
// is not currently enabled.
|
||||||
if (
|
if (
|
||||||
e.affectsConfiguration("codeQL.canary") &&
|
e.affectsConfiguration(CANARY_FEATURES.qualifiedName) &&
|
||||||
CANARY_FEATURES.getValue() &&
|
CANARY_FEATURES.getValue() &&
|
||||||
!ENABLE_TELEMETRY.getValue()
|
!ENABLE_TELEMETRY.getValue()
|
||||||
) {
|
) {
|
||||||
@@ -212,7 +211,7 @@ export class ExtensionTelemetryListener
|
|||||||
properties.stack = error.stack;
|
properties.stack = error.stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reporter.sendTelemetryEvent("error", properties, {});
|
this.reporter.sendTelemetryErrorEvent("error", properties, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -224,7 +223,7 @@ export class ExtensionTelemetryListener
|
|||||||
// if global telemetry is disabled, avoid showing the dialog or making any changes
|
// if global telemetry is disabled, avoid showing the dialog or making any changes
|
||||||
let result = undefined;
|
let result = undefined;
|
||||||
if (
|
if (
|
||||||
GLOBAL_ENABLE_TELEMETRY.getValue() &&
|
env.isTelemetryEnabled &&
|
||||||
// Avoid showing the dialog if we are in integration test mode.
|
// Avoid showing the dialog if we are in integration test mode.
|
||||||
!isIntegrationTestMode()
|
!isIntegrationTestMode()
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -72,15 +72,8 @@ export const VSCODE_SAVE_BEFORE_START_SETTING = new Setting(
|
|||||||
|
|
||||||
const ROOT_SETTING = new Setting("codeQL");
|
const ROOT_SETTING = new Setting("codeQL");
|
||||||
|
|
||||||
// Global configuration
|
// Telemetry configuration
|
||||||
const TELEMETRY_SETTING = new Setting("telemetry", ROOT_SETTING);
|
const TELEMETRY_SETTING = new Setting("telemetry", ROOT_SETTING);
|
||||||
const AST_VIEWER_SETTING = new Setting("astViewer", ROOT_SETTING);
|
|
||||||
const CONTEXTUAL_QUERIES_SETTINGS = new Setting(
|
|
||||||
"contextualQueries",
|
|
||||||
ROOT_SETTING,
|
|
||||||
);
|
|
||||||
const GLOBAL_TELEMETRY_SETTING = new Setting("telemetry");
|
|
||||||
const LOG_INSIGHTS_SETTING = new Setting("logInsights", ROOT_SETTING);
|
|
||||||
|
|
||||||
export const LOG_TELEMETRY = new Setting("logTelemetry", TELEMETRY_SETTING);
|
export const LOG_TELEMETRY = new Setting("logTelemetry", TELEMETRY_SETTING);
|
||||||
export const ENABLE_TELEMETRY = new Setting(
|
export const ENABLE_TELEMETRY = new Setting(
|
||||||
@@ -88,11 +81,6 @@ export const ENABLE_TELEMETRY = new Setting(
|
|||||||
TELEMETRY_SETTING,
|
TELEMETRY_SETTING,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const GLOBAL_ENABLE_TELEMETRY = new Setting(
|
|
||||||
"enableTelemetry",
|
|
||||||
GLOBAL_TELEMETRY_SETTING,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Distribution configuration
|
// Distribution configuration
|
||||||
const DISTRIBUTION_SETTING = new Setting("cli", ROOT_SETTING);
|
const DISTRIBUTION_SETTING = new Setting("cli", ROOT_SETTING);
|
||||||
export const CUSTOM_CODEQL_PATH_SETTING = new Setting(
|
export const CUSTOM_CODEQL_PATH_SETTING = new Setting(
|
||||||
@@ -475,6 +463,7 @@ export function allowCanaryQueryServer() {
|
|||||||
return value === undefined ? true : !!value;
|
return value === undefined ? true : !!value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOG_INSIGHTS_SETTING = new Setting("logInsights", ROOT_SETTING);
|
||||||
export const JOIN_ORDER_WARNING_THRESHOLD = new Setting(
|
export const JOIN_ORDER_WARNING_THRESHOLD = new Setting(
|
||||||
"joinOrderWarningThreshold",
|
"joinOrderWarningThreshold",
|
||||||
LOG_INSIGHTS_SETTING,
|
LOG_INSIGHTS_SETTING,
|
||||||
@@ -484,6 +473,7 @@ export function joinOrderWarningThreshold(): number {
|
|||||||
return JOIN_ORDER_WARNING_THRESHOLD.getValue<number>();
|
return JOIN_ORDER_WARNING_THRESHOLD.getValue<number>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AST_VIEWER_SETTING = new Setting("astViewer", ROOT_SETTING);
|
||||||
/**
|
/**
|
||||||
* Hidden setting: Avoids caching in the AST viewer if the user is also a canary user.
|
* Hidden setting: Avoids caching in the AST viewer if the user is also a canary user.
|
||||||
*/
|
*/
|
||||||
@@ -492,6 +482,10 @@ export const NO_CACHE_AST_VIEWER = new Setting(
|
|||||||
AST_VIEWER_SETTING,
|
AST_VIEWER_SETTING,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const CONTEXTUAL_QUERIES_SETTINGS = new Setting(
|
||||||
|
"contextualQueries",
|
||||||
|
ROOT_SETTING,
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* Hidden setting: Avoids caching in jump to def and find refs contextual queries if the user is also a canary user.
|
* Hidden setting: Avoids caching in jump to def and find refs contextual queries if the user is also a canary user.
|
||||||
*/
|
*/
|
||||||
@@ -711,20 +705,33 @@ const LLM_GENERATION = new Setting("llmGeneration", MODEL_SETTING);
|
|||||||
const EXTENSIONS_DIRECTORY = new Setting("extensionsDirectory", MODEL_SETTING);
|
const EXTENSIONS_DIRECTORY = new Setting("extensionsDirectory", MODEL_SETTING);
|
||||||
const SHOW_MULTIPLE_MODELS = new Setting("showMultipleModels", MODEL_SETTING);
|
const SHOW_MULTIPLE_MODELS = new Setting("showMultipleModels", MODEL_SETTING);
|
||||||
|
|
||||||
export function showFlowGeneration(): boolean {
|
export interface ModelConfig {
|
||||||
return !!FLOW_GENERATION.getValue<boolean>();
|
flowGeneration: boolean;
|
||||||
|
llmGeneration: boolean;
|
||||||
|
getExtensionsDirectory(languageId: string): string | undefined;
|
||||||
|
showMultipleModels: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showLlmGeneration(): boolean {
|
export class ModelConfigListener extends ConfigListener implements ModelConfig {
|
||||||
return !!LLM_GENERATION.getValue<boolean>();
|
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
|
||||||
}
|
this.handleDidChangeConfigurationForRelevantSettings([MODEL_SETTING], e);
|
||||||
|
}
|
||||||
|
|
||||||
export function getExtensionsDirectory(languageId: string): string | undefined {
|
public get flowGeneration(): boolean {
|
||||||
return EXTENSIONS_DIRECTORY.getValue<string>({
|
return !!FLOW_GENERATION.getValue<boolean>();
|
||||||
languageId,
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function showMultipleModels(): boolean {
|
public get llmGeneration(): boolean {
|
||||||
return !!SHOW_MULTIPLE_MODELS.getValue<boolean>();
|
return !!LLM_GENERATION.getValue<boolean>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getExtensionsDirectory(languageId: string): string | undefined {
|
||||||
|
return EXTENSIONS_DIRECTORY.getValue<string>({
|
||||||
|
languageId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public get showMultipleModels(): boolean {
|
||||||
|
return !!SHOW_MULTIPLE_MODELS.getValue<boolean>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { getQlPackPath, QLPACK_FILENAMES } from "../common/ql";
|
|||||||
import { getErrorMessage } from "../common/helpers-pure";
|
import { getErrorMessage } from "../common/helpers-pure";
|
||||||
import { ExtensionPack } from "./shared/extension-pack";
|
import { ExtensionPack } from "./shared/extension-pack";
|
||||||
import { NotificationLogger, showAndLogErrorMessage } from "../common/logging";
|
import { NotificationLogger, showAndLogErrorMessage } from "../common/logging";
|
||||||
import { getExtensionsDirectory } from "../config";
|
import { ModelConfig } from "../config";
|
||||||
import {
|
import {
|
||||||
autoNameExtensionPack,
|
autoNameExtensionPack,
|
||||||
ExtensionPackName,
|
ExtensionPackName,
|
||||||
@@ -28,6 +28,7 @@ const extensionPackValidate = ajv.compile(extensionPackMetadataSchemaJson);
|
|||||||
export async function pickExtensionPack(
|
export async function pickExtensionPack(
|
||||||
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
|
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
|
||||||
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
databaseItem: Pick<DatabaseItem, "name" | "language">,
|
||||||
|
modelConfig: ModelConfig,
|
||||||
logger: NotificationLogger,
|
logger: NotificationLogger,
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
maxStep: number,
|
maxStep: number,
|
||||||
@@ -56,7 +57,9 @@ export async function pickExtensionPack(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get the `codeQL.model.extensionsDirectory` setting for the language
|
// Get the `codeQL.model.extensionsDirectory` setting for the language
|
||||||
const userExtensionsDirectory = getExtensionsDirectory(databaseItem.language);
|
const userExtensionsDirectory = modelConfig.getExtensionsDirectory(
|
||||||
|
databaseItem.language,
|
||||||
|
);
|
||||||
|
|
||||||
// If the setting is not set, automatically pick a suitable directory
|
// If the setting is not set, automatically pick a suitable directory
|
||||||
const extensionsDirectory = userExtensionsDirectory
|
const extensionsDirectory = userExtensionsDirectory
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { MethodModelingViewProvider } from "./method-modeling-view-provider";
|
|||||||
import { Method } from "../method";
|
import { Method } from "../method";
|
||||||
import { ModelingStore } from "../modeling-store";
|
import { ModelingStore } from "../modeling-store";
|
||||||
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
|
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
|
||||||
|
import { ModelConfigListener } from "../../config";
|
||||||
|
|
||||||
export class MethodModelingPanel extends DisposableObject {
|
export class MethodModelingPanel extends DisposableObject {
|
||||||
private readonly provider: MethodModelingViewProvider;
|
private readonly provider: MethodModelingViewProvider;
|
||||||
@@ -16,10 +17,16 @@ export class MethodModelingPanel extends DisposableObject {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
// This is here instead of in MethodModelingViewProvider because we need to
|
||||||
|
// dispose this when the extension gets disposed, not when the webview gets
|
||||||
|
// disposed.
|
||||||
|
const modelConfig = this.push(new ModelConfigListener());
|
||||||
|
|
||||||
this.provider = new MethodModelingViewProvider(
|
this.provider = new MethodModelingViewProvider(
|
||||||
app,
|
app,
|
||||||
modelingStore,
|
modelingStore,
|
||||||
editorViewTracker,
|
editorViewTracker,
|
||||||
|
modelConfig,
|
||||||
);
|
);
|
||||||
this.push(
|
this.push(
|
||||||
window.registerWebviewViewProvider(
|
window.registerWebviewViewProvider(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { DbModelingState, ModelingStore } from "../modeling-store";
|
|||||||
import { AbstractWebviewViewProvider } from "../../common/vscode/abstract-webview-view-provider";
|
import { AbstractWebviewViewProvider } from "../../common/vscode/abstract-webview-view-provider";
|
||||||
import { assertNever } from "../../common/helpers-pure";
|
import { assertNever } from "../../common/helpers-pure";
|
||||||
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
|
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
|
||||||
import { showMultipleModels } from "../../config";
|
import { ModelConfigListener } from "../../config";
|
||||||
|
|
||||||
export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
||||||
ToMethodModelingMessage,
|
ToMethodModelingMessage,
|
||||||
@@ -26,6 +26,7 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
|||||||
app: App,
|
app: App,
|
||||||
private readonly modelingStore: ModelingStore,
|
private readonly modelingStore: ModelingStore,
|
||||||
private readonly editorViewTracker: ModelEditorViewTracker,
|
private readonly editorViewTracker: ModelEditorViewTracker,
|
||||||
|
private readonly modelConfig: ModelConfigListener,
|
||||||
) {
|
) {
|
||||||
super(app, "method-modeling");
|
super(app, "method-modeling");
|
||||||
}
|
}
|
||||||
@@ -33,13 +34,14 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
|||||||
protected override async onWebViewLoaded(): Promise<void> {
|
protected override async onWebViewLoaded(): Promise<void> {
|
||||||
await Promise.all([this.setViewState(), this.setInitialState()]);
|
await Promise.all([this.setViewState(), this.setInitialState()]);
|
||||||
this.registerToModelingStoreEvents();
|
this.registerToModelingStoreEvents();
|
||||||
|
this.registerToModelConfigEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setViewState(): Promise<void> {
|
private async setViewState(): Promise<void> {
|
||||||
await this.postMessage({
|
await this.postMessage({
|
||||||
t: "setMethodModelingPanelViewState",
|
t: "setMethodModelingPanelViewState",
|
||||||
viewState: {
|
viewState: {
|
||||||
showMultipleModels: showMultipleModels(),
|
showMultipleModels: this.modelConfig.showMultipleModels,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -198,4 +200,12 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private registerToModelConfigEvents(): void {
|
||||||
|
this.push(
|
||||||
|
this.modelConfig.onDidChangeConfiguration(() => {
|
||||||
|
void this.setViewState();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,10 @@ export class MethodsUsageDataProvider
|
|||||||
const modeledMethod = this.modeledMethods[method.signature];
|
const modeledMethod = this.modeledMethods[method.signature];
|
||||||
const modifiedMethod = this.modifiedMethodSignatures.has(method.signature);
|
const modifiedMethod = this.modifiedMethodSignatures.has(method.signature);
|
||||||
|
|
||||||
const status = getModelingStatus(modeledMethod, modifiedMethod);
|
const status = getModelingStatus(
|
||||||
|
modeledMethod ? [modeledMethod] : [],
|
||||||
|
modifiedMethod,
|
||||||
|
);
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "unmodeled":
|
case "unmodeled":
|
||||||
return new ThemeIcon("error", new ThemeColor("errorForeground"));
|
return new ThemeIcon("error", new ThemeColor("errorForeground"));
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { MethodModelingPanel } from "./method-modeling/method-modeling-panel";
|
|||||||
import { ModelingStore } from "./modeling-store";
|
import { ModelingStore } from "./modeling-store";
|
||||||
import { showResolvableLocation } from "../databases/local-databases/locations";
|
import { showResolvableLocation } from "../databases/local-databases/locations";
|
||||||
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
|
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
|
||||||
|
import { ModelConfigListener } from "../config";
|
||||||
|
|
||||||
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
|
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
|
||||||
|
|
||||||
@@ -150,9 +151,12 @@ export class ModelEditorModule extends DisposableObject {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modelConfig = this.push(new ModelConfigListener());
|
||||||
|
|
||||||
const modelFile = await pickExtensionPack(
|
const modelFile = await pickExtensionPack(
|
||||||
this.cliServer,
|
this.cliServer,
|
||||||
db,
|
db,
|
||||||
|
modelConfig,
|
||||||
this.app.logger,
|
this.app.logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -172,7 +176,12 @@ export class ModelEditorModule extends DisposableObject {
|
|||||||
unsafeCleanup: true,
|
unsafeCleanup: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const success = await setUpPack(this.cliServer, queryDir, language);
|
const success = await setUpPack(
|
||||||
|
this.cliServer,
|
||||||
|
queryDir,
|
||||||
|
language,
|
||||||
|
modelConfig,
|
||||||
|
);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
await cleanupQueryDir();
|
await cleanupQueryDir();
|
||||||
return;
|
return;
|
||||||
@@ -188,6 +197,7 @@ export class ModelEditorModule extends DisposableObject {
|
|||||||
this.app,
|
this.app,
|
||||||
this.modelingStore,
|
this.modelingStore,
|
||||||
this.editorViewTracker,
|
this.editorViewTracker,
|
||||||
|
modelConfig,
|
||||||
this.databaseManager,
|
this.databaseManager,
|
||||||
this.cliServer,
|
this.cliServer,
|
||||||
this.queryRunner,
|
this.queryRunner,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { writeFile } from "fs-extra";
|
|||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { prepareExternalApiQuery } from "./external-api-usage-queries";
|
import { prepareExternalApiQuery } from "./external-api-usage-queries";
|
||||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||||
import { showLlmGeneration } from "../config";
|
import { ModelConfig } from "../config";
|
||||||
import { Mode } from "./shared/mode";
|
import { Mode } from "./shared/mode";
|
||||||
import { resolveQueriesFromPacks } from "../local-queries";
|
import { resolveQueriesFromPacks } from "../local-queries";
|
||||||
import { modeTag } from "./mode-tag";
|
import { modeTag } from "./mode-tag";
|
||||||
@@ -28,12 +28,14 @@ export const syntheticQueryPackName = "codeql/external-api-usage";
|
|||||||
* @param cliServer The CodeQL CLI server to use.
|
* @param cliServer The CodeQL CLI server to use.
|
||||||
* @param queryDir The directory to set up.
|
* @param queryDir The directory to set up.
|
||||||
* @param language The language to use for the queries.
|
* @param language The language to use for the queries.
|
||||||
|
* @param modelConfig The model config to use.
|
||||||
* @returns true if the setup was successful, false otherwise.
|
* @returns true if the setup was successful, false otherwise.
|
||||||
*/
|
*/
|
||||||
export async function setUpPack(
|
export async function setUpPack(
|
||||||
cliServer: CodeQLCliServer,
|
cliServer: CodeQLCliServer,
|
||||||
queryDir: string,
|
queryDir: string,
|
||||||
language: QueryLanguage,
|
language: QueryLanguage,
|
||||||
|
modelConfig: ModelConfig,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// Download the required query packs
|
// Download the required query packs
|
||||||
await cliServer.packDownload([`codeql/${language}-queries`]);
|
await cliServer.packDownload([`codeql/${language}-queries`]);
|
||||||
@@ -84,7 +86,7 @@ export async function setUpPack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Download any other required packs
|
// Download any other required packs
|
||||||
if (language === "java" && showLlmGeneration()) {
|
if (language === "java" && modelConfig.llmGeneration) {
|
||||||
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
|
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import {
|
|||||||
import { ProgressCallback, withProgress } from "../common/vscode/progress";
|
import { ProgressCallback, withProgress } from "../common/vscode/progress";
|
||||||
import { QueryRunner } from "../query-server";
|
import { QueryRunner } from "../query-server";
|
||||||
import {
|
import {
|
||||||
showAndLogExceptionWithTelemetry,
|
|
||||||
showAndLogErrorMessage,
|
showAndLogErrorMessage,
|
||||||
|
showAndLogExceptionWithTelemetry,
|
||||||
} from "../common/logging";
|
} from "../common/logging";
|
||||||
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
|
||||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||||
@@ -34,11 +34,7 @@ import {
|
|||||||
import { Method, Usage } from "./method";
|
import { Method, Usage } from "./method";
|
||||||
import { ModeledMethod } from "./modeled-method";
|
import { ModeledMethod } from "./modeled-method";
|
||||||
import { ExtensionPack } from "./shared/extension-pack";
|
import { ExtensionPack } from "./shared/extension-pack";
|
||||||
import {
|
import { ModelConfigListener } from "../config";
|
||||||
showFlowGeneration,
|
|
||||||
showLlmGeneration,
|
|
||||||
showMultipleModels,
|
|
||||||
} from "../config";
|
|
||||||
import { Mode } from "./shared/mode";
|
import { Mode } from "./shared/mode";
|
||||||
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
|
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
|
||||||
import { pickExtensionPack } from "./extension-pack-picker";
|
import { pickExtensionPack } from "./extension-pack-picker";
|
||||||
@@ -47,6 +43,10 @@ import { AutoModeler } from "./auto-modeler";
|
|||||||
import { telemetryListener } from "../common/vscode/telemetry";
|
import { telemetryListener } from "../common/vscode/telemetry";
|
||||||
import { ModelingStore } from "./modeling-store";
|
import { ModelingStore } from "./modeling-store";
|
||||||
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
|
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
|
||||||
|
import {
|
||||||
|
convertFromLegacyModeledMethods,
|
||||||
|
convertToLegacyModeledMethods,
|
||||||
|
} from "./modeled-methods-legacy";
|
||||||
|
|
||||||
export class ModelEditorView extends AbstractWebview<
|
export class ModelEditorView extends AbstractWebview<
|
||||||
ToModelEditorMessage,
|
ToModelEditorMessage,
|
||||||
@@ -58,6 +58,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
protected readonly app: App,
|
protected readonly app: App,
|
||||||
private readonly modelingStore: ModelingStore,
|
private readonly modelingStore: ModelingStore,
|
||||||
private readonly viewTracker: ModelEditorViewTracker<ModelEditorView>,
|
private readonly viewTracker: ModelEditorViewTracker<ModelEditorView>,
|
||||||
|
private readonly modelConfig: ModelConfigListener,
|
||||||
private readonly databaseManager: DatabaseManager,
|
private readonly databaseManager: DatabaseManager,
|
||||||
private readonly cliServer: CodeQLCliServer,
|
private readonly cliServer: CodeQLCliServer,
|
||||||
private readonly queryRunner: QueryRunner,
|
private readonly queryRunner: QueryRunner,
|
||||||
@@ -71,6 +72,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
|
|
||||||
this.modelingStore.initializeStateForDb(databaseItem);
|
this.modelingStore.initializeStateForDb(databaseItem);
|
||||||
this.registerToModelingStoreEvents();
|
this.registerToModelingStoreEvents();
|
||||||
|
this.registerToModelConfigEvents();
|
||||||
|
|
||||||
this.viewTracker.registerView(this);
|
this.viewTracker.registerView(this);
|
||||||
|
|
||||||
@@ -201,47 +203,58 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case "saveModeledMethods":
|
case "saveModeledMethods":
|
||||||
await withProgress(
|
{
|
||||||
async (progress) => {
|
const methods = this.modelingStore.getMethods(
|
||||||
progress({
|
this.databaseItem,
|
||||||
step: 1,
|
msg.methodSignatures,
|
||||||
maxStep: 500 + externalApiQueriesProgressMaxStep,
|
);
|
||||||
message: "Writing model files",
|
const modeledMethods = this.modelingStore.getModeledMethods(
|
||||||
});
|
this.databaseItem,
|
||||||
await saveModeledMethods(
|
msg.methodSignatures,
|
||||||
this.extensionPack,
|
);
|
||||||
this.databaseItem.language,
|
|
||||||
msg.methods,
|
|
||||||
msg.modeledMethods,
|
|
||||||
this.mode,
|
|
||||||
this.cliServer,
|
|
||||||
this.app.logger,
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all([
|
await withProgress(
|
||||||
this.setViewState(),
|
async (progress) => {
|
||||||
this.loadMethods((update) =>
|
progress({
|
||||||
progress({
|
step: 1,
|
||||||
...update,
|
maxStep: 500 + externalApiQueriesProgressMaxStep,
|
||||||
step: update.step + 500,
|
message: "Writing model files",
|
||||||
maxStep: 500 + externalApiQueriesProgressMaxStep,
|
});
|
||||||
}),
|
await saveModeledMethods(
|
||||||
),
|
this.extensionPack,
|
||||||
]);
|
this.databaseItem.language,
|
||||||
},
|
methods,
|
||||||
{
|
convertFromLegacyModeledMethods(modeledMethods),
|
||||||
cancellable: false,
|
this.mode,
|
||||||
},
|
this.cliServer,
|
||||||
);
|
this.app.logger,
|
||||||
|
);
|
||||||
|
|
||||||
this.modelingStore.removeModifiedMethods(
|
await Promise.all([
|
||||||
this.databaseItem,
|
this.setViewState(),
|
||||||
Object.keys(msg.modeledMethods),
|
this.loadMethods((update) =>
|
||||||
);
|
progress({
|
||||||
|
...update,
|
||||||
|
step: update.step + 500,
|
||||||
|
maxStep: 500 + externalApiQueriesProgressMaxStep,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cancellable: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
void telemetryListener?.sendUIInteraction(
|
this.modelingStore.removeModifiedMethods(
|
||||||
"model-editor-save-modeled-methods",
|
this.databaseItem,
|
||||||
);
|
Object.keys(modeledMethods),
|
||||||
|
);
|
||||||
|
|
||||||
|
void telemetryListener?.sendUIInteraction(
|
||||||
|
"model-editor-save-modeled-methods",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "generateMethod":
|
case "generateMethod":
|
||||||
@@ -328,21 +341,21 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
|
|
||||||
await this.postMessage({
|
await this.postMessage({
|
||||||
t: "revealMethod",
|
t: "revealMethod",
|
||||||
method,
|
methodSignature: method.signature,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setViewState(): Promise<void> {
|
private async setViewState(): Promise<void> {
|
||||||
const showLlmButton =
|
const showLlmButton =
|
||||||
this.databaseItem.language === "java" && showLlmGeneration();
|
this.databaseItem.language === "java" && this.modelConfig.llmGeneration;
|
||||||
|
|
||||||
await this.postMessage({
|
await this.postMessage({
|
||||||
t: "setModelEditorViewState",
|
t: "setModelEditorViewState",
|
||||||
viewState: {
|
viewState: {
|
||||||
extensionPack: this.extensionPack,
|
extensionPack: this.extensionPack,
|
||||||
showFlowGeneration: showFlowGeneration(),
|
showFlowGeneration: this.modelConfig.flowGeneration,
|
||||||
showLlmButton,
|
showLlmButton,
|
||||||
showMultipleModels: showMultipleModels(),
|
showMultipleModels: this.modelConfig.showMultipleModels,
|
||||||
mode: this.mode,
|
mode: this.mode,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -359,7 +372,10 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
this.cliServer,
|
this.cliServer,
|
||||||
this.app.logger,
|
this.app.logger,
|
||||||
);
|
);
|
||||||
this.modelingStore.setModeledMethods(this.databaseItem, modeledMethods);
|
this.modelingStore.setModeledMethods(
|
||||||
|
this.databaseItem,
|
||||||
|
convertToLegacyModeledMethods(modeledMethods),
|
||||||
|
);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
void showAndLogErrorMessage(
|
void showAndLogErrorMessage(
|
||||||
this.app.logger,
|
this.app.logger,
|
||||||
@@ -481,6 +497,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
const modelFile = await pickExtensionPack(
|
const modelFile = await pickExtensionPack(
|
||||||
this.cliServer,
|
this.cliServer,
|
||||||
addedDatabase,
|
addedDatabase,
|
||||||
|
this.modelConfig,
|
||||||
this.app.logger,
|
this.app.logger,
|
||||||
progress,
|
progress,
|
||||||
3,
|
3,
|
||||||
@@ -493,6 +510,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
this.app,
|
this.app,
|
||||||
this.modelingStore,
|
this.modelingStore,
|
||||||
this.viewTracker,
|
this.viewTracker,
|
||||||
|
this.modelConfig,
|
||||||
this.databaseManager,
|
this.databaseManager,
|
||||||
this.cliServer,
|
this.cliServer,
|
||||||
this.queryRunner,
|
this.queryRunner,
|
||||||
@@ -614,6 +632,14 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private registerToModelConfigEvents() {
|
||||||
|
this.push(
|
||||||
|
this.modelConfig.onDidChangeConfiguration(() => {
|
||||||
|
void this.setViewState();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private addModeledMethods(modeledMethods: Record<string, ModeledMethod>) {
|
private addModeledMethods(modeledMethods: Record<string, ModeledMethod>) {
|
||||||
this.modelingStore.addModeledMethods(this.databaseItem, modeledMethods);
|
this.modelingStore.addModeledMethods(this.databaseItem, modeledMethods);
|
||||||
|
|
||||||
|
|||||||
@@ -10,17 +10,12 @@ import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
|||||||
import { load as loadYaml } from "js-yaml";
|
import { load as loadYaml } from "js-yaml";
|
||||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
import { CodeQLCliServer } from "../codeql-cli/cli";
|
||||||
import { pathsEqual } from "../common/files";
|
import { pathsEqual } from "../common/files";
|
||||||
import {
|
|
||||||
convertFromLegacyModeledMethods,
|
|
||||||
convertFromLegacyModeledMethodsFiles,
|
|
||||||
convertToLegacyModeledMethods,
|
|
||||||
} from "./modeled-methods-legacy";
|
|
||||||
|
|
||||||
export async function saveModeledMethods(
|
export async function saveModeledMethods(
|
||||||
extensionPack: ExtensionPack,
|
extensionPack: ExtensionPack,
|
||||||
language: string,
|
language: string,
|
||||||
methods: Method[],
|
methods: Method[],
|
||||||
modeledMethods: Record<string, ModeledMethod>,
|
modeledMethods: Record<string, ModeledMethod[]>,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
cliServer: CodeQLCliServer,
|
cliServer: CodeQLCliServer,
|
||||||
logger: NotificationLogger,
|
logger: NotificationLogger,
|
||||||
@@ -34,8 +29,8 @@ export async function saveModeledMethods(
|
|||||||
const yamls = createDataExtensionYamls(
|
const yamls = createDataExtensionYamls(
|
||||||
language,
|
language,
|
||||||
methods,
|
methods,
|
||||||
convertFromLegacyModeledMethods(modeledMethods),
|
modeledMethods,
|
||||||
convertFromLegacyModeledMethodsFiles(existingModeledMethods),
|
existingModeledMethods,
|
||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -50,12 +45,12 @@ async function loadModeledMethodFiles(
|
|||||||
extensionPack: ExtensionPack,
|
extensionPack: ExtensionPack,
|
||||||
cliServer: CodeQLCliServer,
|
cliServer: CodeQLCliServer,
|
||||||
logger: NotificationLogger,
|
logger: NotificationLogger,
|
||||||
): Promise<Record<string, Record<string, ModeledMethod>>> {
|
): Promise<Record<string, Record<string, ModeledMethod[]>>> {
|
||||||
const modelFiles = await listModelFiles(extensionPack.path, cliServer);
|
const modelFiles = await listModelFiles(extensionPack.path, cliServer);
|
||||||
|
|
||||||
const modeledMethodsByFile: Record<
|
const modeledMethodsByFile: Record<
|
||||||
string,
|
string,
|
||||||
Record<string, ModeledMethod>
|
Record<string, ModeledMethod[]>
|
||||||
> = {};
|
> = {};
|
||||||
|
|
||||||
for (const modelFile of modelFiles) {
|
for (const modelFile of modelFiles) {
|
||||||
@@ -73,8 +68,7 @@ async function loadModeledMethodFiles(
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
modeledMethodsByFile[modelFile] =
|
modeledMethodsByFile[modelFile] = modeledMethods;
|
||||||
convertToLegacyModeledMethods(modeledMethods);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return modeledMethodsByFile;
|
return modeledMethodsByFile;
|
||||||
@@ -84,8 +78,8 @@ export async function loadModeledMethods(
|
|||||||
extensionPack: ExtensionPack,
|
extensionPack: ExtensionPack,
|
||||||
cliServer: CodeQLCliServer,
|
cliServer: CodeQLCliServer,
|
||||||
logger: NotificationLogger,
|
logger: NotificationLogger,
|
||||||
): Promise<Record<string, ModeledMethod>> {
|
): Promise<Record<string, ModeledMethod[]>> {
|
||||||
const existingModeledMethods: Record<string, ModeledMethod> = {};
|
const existingModeledMethods: Record<string, ModeledMethod[]> = {};
|
||||||
|
|
||||||
const modeledMethodsByFile = await loadModeledMethodFiles(
|
const modeledMethodsByFile = await loadModeledMethodFiles(
|
||||||
extensionPack,
|
extensionPack,
|
||||||
@@ -94,7 +88,11 @@ export async function loadModeledMethods(
|
|||||||
);
|
);
|
||||||
for (const modeledMethods of Object.values(modeledMethodsByFile)) {
|
for (const modeledMethods of Object.values(modeledMethodsByFile)) {
|
||||||
for (const [key, value] of Object.entries(modeledMethods)) {
|
for (const [key, value] of Object.entries(modeledMethods)) {
|
||||||
existingModeledMethods[key] = value;
|
if (!(key in existingModeledMethods)) {
|
||||||
|
existingModeledMethods[key] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
existingModeledMethods[key].push(...value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,3 @@ export function convertToLegacyModeledMethods(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertFromLegacyModeledMethodsFiles(
|
|
||||||
modeledMethods: Record<string, Record<string, ModeledMethod>>,
|
|
||||||
): Record<string, Record<string, ModeledMethod[]>> {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(modeledMethods).map(([filename, modeledMethods]) => {
|
|
||||||
return [filename, convertFromLegacyModeledMethods(modeledMethods)];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -169,6 +169,23 @@ export class ModelingStore extends DisposableObject {
|
|||||||
return this.state.size > 0;
|
return this.state.size > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the methods for the given database item and method signatures.
|
||||||
|
* If the `methodSignatures` argument is not provided or is undefined, returns all methods.
|
||||||
|
*/
|
||||||
|
public getMethods(
|
||||||
|
dbItem: DatabaseItem,
|
||||||
|
methodSignatures?: string[],
|
||||||
|
): Method[] {
|
||||||
|
const methods = this.getState(dbItem).methods;
|
||||||
|
if (!methodSignatures) {
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
return methods.filter((method) =>
|
||||||
|
methodSignatures.includes(method.signature),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public setMethods(dbItem: DatabaseItem, methods: Method[]) {
|
public setMethods(dbItem: DatabaseItem, methods: Method[]) {
|
||||||
const dbState = this.getState(dbItem);
|
const dbState = this.getState(dbItem);
|
||||||
const dbUri = dbItem.databaseUri.toString();
|
const dbUri = dbItem.databaseUri.toString();
|
||||||
@@ -197,6 +214,25 @@ export class ModelingStore extends DisposableObject {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the modeled methods for the given database item and method signatures.
|
||||||
|
* If the `methodSignatures` argument is not provided or is undefined, returns all modeled methods.
|
||||||
|
*/
|
||||||
|
public getModeledMethods(
|
||||||
|
dbItem: DatabaseItem,
|
||||||
|
methodSignatures?: string[],
|
||||||
|
): Record<string, ModeledMethod> {
|
||||||
|
const modeledMethods = this.getState(dbItem).modeledMethods;
|
||||||
|
if (!methodSignatures) {
|
||||||
|
return modeledMethods;
|
||||||
|
}
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(modeledMethods).filter(([key]) =>
|
||||||
|
methodSignatures.includes(key),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public addModeledMethods(
|
public addModeledMethods(
|
||||||
dbItem: DatabaseItem,
|
dbItem: DatabaseItem,
|
||||||
methods: Record<string, ModeledMethod>,
|
methods: Record<string, ModeledMethod>,
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { ModeledMethod } from "../modeled-method";
|
|||||||
export type ModelingStatus = "unmodeled" | "unsaved" | "saved";
|
export type ModelingStatus = "unmodeled" | "unsaved" | "saved";
|
||||||
|
|
||||||
export function getModelingStatus(
|
export function getModelingStatus(
|
||||||
modeledMethod: ModeledMethod | undefined,
|
modeledMethods: ModeledMethod[],
|
||||||
methodIsUnsaved: boolean,
|
methodIsUnsaved: boolean,
|
||||||
): ModelingStatus {
|
): ModelingStatus {
|
||||||
if (modeledMethod) {
|
if (modeledMethods.length > 0) {
|
||||||
if (methodIsUnsaved) {
|
if (methodIsUnsaved) {
|
||||||
return "unsaved";
|
return "unsaved";
|
||||||
} else if (modeledMethod.type !== "none") {
|
} else if (modeledMethods.some((m) => m.type !== "none")) {
|
||||||
return "saved";
|
return "saved";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Meta, StoryFn } from "@storybook/react";
|
|||||||
|
|
||||||
import { MethodModeling as MethodModelingComponent } from "../../view/method-modeling/MethodModeling";
|
import { MethodModeling as MethodModelingComponent } from "../../view/method-modeling/MethodModeling";
|
||||||
import { createMethod } from "../../../test/factories/model-editor/method-factories";
|
import { createMethod } from "../../../test/factories/model-editor/method-factories";
|
||||||
|
import { createModeledMethod } from "../../../test/factories/model-editor/modeled-method-factories";
|
||||||
export default {
|
export default {
|
||||||
title: "Method Modeling/Method Modeling",
|
title: "Method Modeling/Method Modeling",
|
||||||
component: MethodModelingComponent,
|
component: MethodModelingComponent,
|
||||||
@@ -18,18 +19,53 @@ const method = createMethod();
|
|||||||
export const MethodUnmodeled = Template.bind({});
|
export const MethodUnmodeled = Template.bind({});
|
||||||
MethodUnmodeled.args = {
|
MethodUnmodeled.args = {
|
||||||
method,
|
method,
|
||||||
|
modeledMethods: [],
|
||||||
modelingStatus: "unmodeled",
|
modelingStatus: "unmodeled",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MethodModeled = Template.bind({});
|
export const MethodModeled = Template.bind({});
|
||||||
MethodModeled.args = {
|
MethodModeled.args = {
|
||||||
method,
|
method,
|
||||||
|
modeledMethods: [],
|
||||||
modelingStatus: "unsaved",
|
modelingStatus: "unsaved",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MethodSaved = Template.bind({});
|
export const MethodSaved = Template.bind({});
|
||||||
MethodSaved.args = {
|
MethodSaved.args = {
|
||||||
method,
|
method,
|
||||||
|
modeledMethods: [],
|
||||||
|
modelingStatus: "saved",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultipleModelingsUnmodeled = Template.bind({});
|
||||||
|
MultipleModelingsUnmodeled.args = {
|
||||||
|
method,
|
||||||
|
modeledMethods: [],
|
||||||
|
showMultipleModels: true,
|
||||||
|
modelingStatus: "saved",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultipleModelingsModeledSingle = Template.bind({});
|
||||||
|
MultipleModelingsModeledSingle.args = {
|
||||||
|
method,
|
||||||
|
modeledMethods: [createModeledMethod(method)],
|
||||||
|
showMultipleModels: true,
|
||||||
|
modelingStatus: "saved",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultipleModelingsModeledMultiple = Template.bind({});
|
||||||
|
MultipleModelingsModeledMultiple.args = {
|
||||||
|
method,
|
||||||
|
modeledMethods: [
|
||||||
|
createModeledMethod(method),
|
||||||
|
createModeledMethod({
|
||||||
|
...method,
|
||||||
|
type: "source",
|
||||||
|
input: "",
|
||||||
|
output: "ReturnValue",
|
||||||
|
kind: "remote",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
showMultipleModels: true,
|
||||||
modelingStatus: "saved",
|
modelingStatus: "saved",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import { CallClassification, Method } from "../../model-editor/method";
|
|||||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||||
import { VSCodeDataGrid } from "@vscode/webview-ui-toolkit/react";
|
import { VSCodeDataGrid } from "@vscode/webview-ui-toolkit/react";
|
||||||
import { GRID_TEMPLATE_COLUMNS } from "../../view/model-editor/ModeledMethodDataGrid";
|
import { GRID_TEMPLATE_COLUMNS } from "../../view/model-editor/ModeledMethodDataGrid";
|
||||||
|
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||||
|
import { createMockExtensionPack } from "../../../test/factories/model-editor/extension-pack";
|
||||||
|
import { Mode } from "../../model-editor/shared/mode";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "CodeQL Model Editor/Method Row",
|
title: "CodeQL Model Editor/Method Row",
|
||||||
@@ -66,51 +69,78 @@ const modeledMethod: ModeledMethod = {
|
|||||||
methodParameters: "()",
|
methodParameters: "()",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const viewState: ModelEditorViewState = {
|
||||||
|
extensionPack: createMockExtensionPack(),
|
||||||
|
showFlowGeneration: true,
|
||||||
|
showLlmButton: true,
|
||||||
|
showMultipleModels: true,
|
||||||
|
mode: Mode.Application,
|
||||||
|
};
|
||||||
|
|
||||||
export const Unmodeled = Template.bind({});
|
export const Unmodeled = Template.bind({});
|
||||||
Unmodeled.args = {
|
Unmodeled.args = {
|
||||||
method,
|
method,
|
||||||
modeledMethod: undefined,
|
modeledMethods: [],
|
||||||
methodCanBeModeled: true,
|
methodCanBeModeled: true,
|
||||||
|
viewState,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Source = Template.bind({});
|
export const Source = Template.bind({});
|
||||||
Source.args = {
|
Source.args = {
|
||||||
method,
|
method,
|
||||||
modeledMethod: { ...modeledMethod, type: "source" },
|
modeledMethods: [{ ...modeledMethod, type: "source" }],
|
||||||
methodCanBeModeled: true,
|
methodCanBeModeled: true,
|
||||||
|
viewState,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Sink = Template.bind({});
|
export const Sink = Template.bind({});
|
||||||
Sink.args = {
|
Sink.args = {
|
||||||
method,
|
method,
|
||||||
modeledMethod: { ...modeledMethod, type: "sink" },
|
modeledMethods: [{ ...modeledMethod, type: "sink" }],
|
||||||
methodCanBeModeled: true,
|
methodCanBeModeled: true,
|
||||||
|
viewState,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Summary = Template.bind({});
|
export const Summary = Template.bind({});
|
||||||
Summary.args = {
|
Summary.args = {
|
||||||
method,
|
method,
|
||||||
modeledMethod: { ...modeledMethod, type: "summary" },
|
modeledMethods: [{ ...modeledMethod, type: "summary" }],
|
||||||
methodCanBeModeled: true,
|
methodCanBeModeled: true,
|
||||||
|
viewState,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Neutral = Template.bind({});
|
export const Neutral = Template.bind({});
|
||||||
Neutral.args = {
|
Neutral.args = {
|
||||||
method,
|
method,
|
||||||
modeledMethod: { ...modeledMethod, type: "neutral" },
|
modeledMethods: [{ ...modeledMethod, type: "neutral" }],
|
||||||
methodCanBeModeled: true,
|
methodCanBeModeled: true,
|
||||||
|
viewState,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AlreadyModeled = Template.bind({});
|
export const AlreadyModeled = Template.bind({});
|
||||||
AlreadyModeled.args = {
|
AlreadyModeled.args = {
|
||||||
method: { ...method, supported: true },
|
method: { ...method, supported: true },
|
||||||
modeledMethod: undefined,
|
modeledMethods: [],
|
||||||
|
viewState,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModelingInProgress = Template.bind({});
|
export const ModelingInProgress = Template.bind({});
|
||||||
ModelingInProgress.args = {
|
ModelingInProgress.args = {
|
||||||
method,
|
method,
|
||||||
modeledMethod,
|
modeledMethods: [modeledMethod],
|
||||||
modelingInProgress: true,
|
modelingInProgress: true,
|
||||||
methodCanBeModeled: true,
|
methodCanBeModeled: true,
|
||||||
|
viewState,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultipleModelings = Template.bind({});
|
||||||
|
MultipleModelings.args = {
|
||||||
|
method,
|
||||||
|
modeledMethods: [
|
||||||
|
{ ...modeledMethod, type: "source" },
|
||||||
|
{ ...modeledMethod, type: "sink" },
|
||||||
|
{ ...modeledMethod },
|
||||||
|
],
|
||||||
|
methodCanBeModeled: true,
|
||||||
|
viewState,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import classNames from "classnames";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
slot?: string;
|
slot?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { ModelingStatusIndicator } from "../model-editor/ModelingStatusIndicator
|
|||||||
import { Method } from "../../model-editor/method";
|
import { Method } from "../../model-editor/method";
|
||||||
import { MethodName } from "../model-editor/MethodName";
|
import { MethodName } from "../model-editor/MethodName";
|
||||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||||
import { MethodModelingInputs } from "./MethodModelingInputs";
|
|
||||||
import { VSCodeTag } from "@vscode/webview-ui-toolkit/react";
|
import { VSCodeTag } from "@vscode/webview-ui-toolkit/react";
|
||||||
import { ReviewInEditorButton } from "./ReviewInEditorButton";
|
import { ReviewInEditorButton } from "./ReviewInEditorButton";
|
||||||
|
import { ModeledMethodsPanel } from "./ModeledMethodsPanel";
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
padding-top: 0.5rem;
|
padding-top: 0.5rem;
|
||||||
@@ -38,10 +38,6 @@ const DependencyContainer = styled.div`
|
|||||||
margin-bottom: 0.8rem;
|
margin-bottom: 0.8rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledMethodModelingInputs = styled(MethodModelingInputs)`
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledVSCodeTag = styled(VSCodeTag)<{ visible: boolean }>`
|
const StyledVSCodeTag = styled(VSCodeTag)<{ visible: boolean }>`
|
||||||
visibility: ${(props) => (props.visible ? "visible" : "hidden")};
|
visibility: ${(props) => (props.visible ? "visible" : "hidden")};
|
||||||
`;
|
`;
|
||||||
@@ -55,15 +51,16 @@ const UnsavedTag = ({ modelingStatus }: { modelingStatus: ModelingStatus }) => (
|
|||||||
export type MethodModelingProps = {
|
export type MethodModelingProps = {
|
||||||
modelingStatus: ModelingStatus;
|
modelingStatus: ModelingStatus;
|
||||||
method: Method;
|
method: Method;
|
||||||
modeledMethod: ModeledMethod | undefined;
|
modeledMethods: ModeledMethod[];
|
||||||
showMultipleModels?: boolean;
|
showMultipleModels?: boolean;
|
||||||
onChange: (modeledMethod: ModeledMethod) => void;
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MethodModeling = ({
|
export const MethodModeling = ({
|
||||||
modelingStatus,
|
modelingStatus,
|
||||||
modeledMethod,
|
modeledMethods,
|
||||||
method,
|
method,
|
||||||
|
showMultipleModels = false,
|
||||||
onChange,
|
onChange,
|
||||||
}: MethodModelingProps): JSX.Element => {
|
}: MethodModelingProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
@@ -77,9 +74,10 @@ export const MethodModeling = ({
|
|||||||
<ModelingStatusIndicator status={modelingStatus} />
|
<ModelingStatusIndicator status={modelingStatus} />
|
||||||
<MethodName {...method} />
|
<MethodName {...method} />
|
||||||
</DependencyContainer>
|
</DependencyContainer>
|
||||||
<StyledMethodModelingInputs
|
<ModeledMethodsPanel
|
||||||
method={method}
|
method={method}
|
||||||
modeledMethod={modeledMethod}
|
modeledMethods={modeledMethods}
|
||||||
|
showMultipleModels={showMultipleModels}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
<ReviewInEditorButton method={method} />
|
<ReviewInEditorButton method={method} />
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export function MethodModelingView({ initialViewState }: Props): JSX.Element {
|
|||||||
const [isMethodModified, setIsMethodModified] = useState<boolean>(false);
|
const [isMethodModified, setIsMethodModified] = useState<boolean>(false);
|
||||||
|
|
||||||
const modelingStatus = useMemo(
|
const modelingStatus = useMemo(
|
||||||
() => getModelingStatus(modeledMethod, isMethodModified),
|
() =>
|
||||||
|
getModelingStatus(modeledMethod ? [modeledMethod] : [], isMethodModified),
|
||||||
[modeledMethod, isMethodModified],
|
[modeledMethod, isMethodModified],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -94,7 +95,7 @@ export function MethodModelingView({ initialViewState }: Props): JSX.Element {
|
|||||||
<MethodModeling
|
<MethodModeling
|
||||||
modelingStatus={modelingStatus}
|
modelingStatus={modelingStatus}
|
||||||
method={method}
|
method={method}
|
||||||
modeledMethod={modeledMethod}
|
modeledMethods={modeledMethod ? [modeledMethod] : []}
|
||||||
showMultipleModels={viewState?.showMultipleModels}
|
showMultipleModels={viewState?.showMultipleModels}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||||
|
import { MethodModelingInputs } from "./MethodModelingInputs";
|
||||||
|
import { Method } from "../../model-editor/method";
|
||||||
|
import { styled } from "styled-components";
|
||||||
|
import { MultipleModeledMethodsPanel } from "./MultipleModeledMethodsPanel";
|
||||||
|
|
||||||
|
export type ModeledMethodsPanelProps = {
|
||||||
|
method: Method;
|
||||||
|
modeledMethods: ModeledMethod[];
|
||||||
|
showMultipleModels: boolean;
|
||||||
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SingleMethodModelingInputs = styled(MethodModelingInputs)`
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ModeledMethodsPanel = ({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
showMultipleModels,
|
||||||
|
onChange,
|
||||||
|
}: ModeledMethodsPanelProps) => {
|
||||||
|
if (!showMultipleModels) {
|
||||||
|
return (
|
||||||
|
<SingleMethodModelingInputs
|
||||||
|
method={method}
|
||||||
|
modeledMethod={
|
||||||
|
modeledMethods.length > 0 ? modeledMethods[0] : undefined
|
||||||
|
}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MultipleModeledMethodsPanel
|
||||||
|
method={method}
|
||||||
|
modeledMethods={modeledMethods}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import { Method } from "../../model-editor/method";
|
||||||
|
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||||
|
import { styled } from "styled-components";
|
||||||
|
import { MethodModelingInputs } from "./MethodModelingInputs";
|
||||||
|
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react";
|
||||||
|
import { Codicon } from "../common";
|
||||||
|
|
||||||
|
export type MultipleModeledMethodsPanelProps = {
|
||||||
|
method: Method;
|
||||||
|
modeledMethods: ModeledMethod[];
|
||||||
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 0.05rem solid var(--vscode-panelSection-border);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Footer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PaginationActions = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0.5rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MultipleModeledMethodsPanel = ({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
}: MultipleModeledMethodsPanelProps) => {
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||||
|
|
||||||
|
const handlePreviousClick = useCallback(() => {
|
||||||
|
setSelectedIndex((previousIndex) => previousIndex - 1);
|
||||||
|
}, []);
|
||||||
|
const handleNextClick = useCallback(() => {
|
||||||
|
setSelectedIndex((previousIndex) => previousIndex + 1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
{modeledMethods.length > 0 ? (
|
||||||
|
<MethodModelingInputs
|
||||||
|
method={method}
|
||||||
|
modeledMethod={modeledMethods[selectedIndex]}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MethodModelingInputs
|
||||||
|
method={method}
|
||||||
|
modeledMethod={undefined}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Footer>
|
||||||
|
<PaginationActions>
|
||||||
|
<VSCodeButton
|
||||||
|
appearance="icon"
|
||||||
|
aria-label="Previous modeling"
|
||||||
|
onClick={handlePreviousClick}
|
||||||
|
disabled={modeledMethods.length < 2 || selectedIndex === 0}
|
||||||
|
>
|
||||||
|
<Codicon name="chevron-left" />
|
||||||
|
</VSCodeButton>
|
||||||
|
{modeledMethods.length > 1 && (
|
||||||
|
<div>
|
||||||
|
{selectedIndex + 1}/{modeledMethods.length}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<VSCodeButton
|
||||||
|
appearance="icon"
|
||||||
|
aria-label="Next modeling"
|
||||||
|
onClick={handleNextClick}
|
||||||
|
disabled={
|
||||||
|
modeledMethods.length < 2 ||
|
||||||
|
selectedIndex === modeledMethods.length - 1
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Codicon name="chevron-right" />
|
||||||
|
</VSCodeButton>
|
||||||
|
</PaginationActions>
|
||||||
|
</Footer>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -16,7 +16,7 @@ describe(MethodModeling.name, () => {
|
|||||||
render({
|
render({
|
||||||
modelingStatus: "saved",
|
modelingStatus: "saved",
|
||||||
method,
|
method,
|
||||||
modeledMethod,
|
modeledMethods: [modeledMethod],
|
||||||
onChange,
|
onChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { render as reactRender, screen } from "@testing-library/react";
|
||||||
|
import { createMethod } from "../../../../test/factories/model-editor/method-factories";
|
||||||
|
import { createModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||||
|
import {
|
||||||
|
ModeledMethodsPanel,
|
||||||
|
ModeledMethodsPanelProps,
|
||||||
|
} from "../ModeledMethodsPanel";
|
||||||
|
|
||||||
|
describe(ModeledMethodsPanel.name, () => {
|
||||||
|
const render = (props: ModeledMethodsPanelProps) =>
|
||||||
|
reactRender(<ModeledMethodsPanel {...props} />);
|
||||||
|
|
||||||
|
const method = createMethod();
|
||||||
|
const modeledMethods = [createModeledMethod(), createModeledMethod()];
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
describe("when show multiple models is disabled", () => {
|
||||||
|
const showMultipleModels = false;
|
||||||
|
|
||||||
|
it("renders the method modeling inputs", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
showMultipleModels,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getAllByRole("combobox")).toHaveLength(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render the pagination", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
showMultipleModels,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByLabelText("Previous modeling"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByLabelText("Next modeling")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when show multiple models is enabled", () => {
|
||||||
|
const showMultipleModels = true;
|
||||||
|
|
||||||
|
it("renders the method modeling inputs once", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
showMultipleModels,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getAllByRole("combobox")).toHaveLength(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the pagination", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
showMultipleModels,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("Previous modeling")).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText("Next modeling")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("1/2")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { render as reactRender, screen, waitFor } from "@testing-library/react";
|
||||||
|
import { createMethod } from "../../../../test/factories/model-editor/method-factories";
|
||||||
|
import { createModeledMethod } from "../../../../test/factories/model-editor/modeled-method-factories";
|
||||||
|
import {
|
||||||
|
MultipleModeledMethodsPanel,
|
||||||
|
MultipleModeledMethodsPanelProps,
|
||||||
|
} from "../MultipleModeledMethodsPanel";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { ModeledMethod } from "../../../model-editor/modeled-method";
|
||||||
|
|
||||||
|
describe(MultipleModeledMethodsPanel.name, () => {
|
||||||
|
const render = (props: MultipleModeledMethodsPanelProps) =>
|
||||||
|
reactRender(<MultipleModeledMethodsPanel {...props} />);
|
||||||
|
|
||||||
|
const method = createMethod();
|
||||||
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
describe("with no modeled methods", () => {
|
||||||
|
const modeledMethods: ModeledMethod[] = [];
|
||||||
|
|
||||||
|
it("renders the method modeling inputs once", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getAllByRole("combobox")).toHaveLength(4);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("combobox", {
|
||||||
|
name: "Model type",
|
||||||
|
}),
|
||||||
|
).toHaveValue("none");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables all pagination", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText("Next modeling").getElementsByTagName("input")[0],
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(screen.queryByText("0/0")).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("1/0")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with one modeled method", () => {
|
||||||
|
const modeledMethods = [
|
||||||
|
createModeledMethod({
|
||||||
|
...method,
|
||||||
|
type: "sink",
|
||||||
|
input: "Argument[this]",
|
||||||
|
output: "",
|
||||||
|
kind: "path-injection",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
it("renders the method modeling inputs once", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getAllByRole("combobox")).toHaveLength(4);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("combobox", {
|
||||||
|
name: "Model type",
|
||||||
|
}),
|
||||||
|
).toHaveValue("sink");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables all pagination", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText("Next modeling").getElementsByTagName("input")[0],
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(screen.queryByText("1/1")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with two modeled methods", () => {
|
||||||
|
const modeledMethods = [
|
||||||
|
createModeledMethod({
|
||||||
|
...method,
|
||||||
|
type: "sink",
|
||||||
|
input: "Argument[this]",
|
||||||
|
output: "",
|
||||||
|
kind: "path-injection",
|
||||||
|
}),
|
||||||
|
createModeledMethod({
|
||||||
|
...method,
|
||||||
|
type: "source",
|
||||||
|
input: "",
|
||||||
|
output: "ReturnValue",
|
||||||
|
kind: "remote",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
it("renders the method modeling inputs once", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getAllByRole("combobox")).toHaveLength(4);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("combobox", {
|
||||||
|
name: "Model type",
|
||||||
|
}),
|
||||||
|
).toHaveValue("sink");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the pagination", () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("Previous modeling")).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText("Next modeling")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("1/2")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables the correct pagination", async () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText("Next modeling").getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can use the pagination", async () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByLabelText("Next modeling"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText("Next modeling").getElementsByTagName("input")[0],
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(screen.getByText("2/2")).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole("combobox", {
|
||||||
|
name: "Model type",
|
||||||
|
}),
|
||||||
|
).toHaveValue("source");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with three modeled methods", () => {
|
||||||
|
const modeledMethods = [
|
||||||
|
createModeledMethod({
|
||||||
|
...method,
|
||||||
|
type: "sink",
|
||||||
|
input: "Argument[this]",
|
||||||
|
output: "",
|
||||||
|
kind: "path-injection",
|
||||||
|
}),
|
||||||
|
createModeledMethod({
|
||||||
|
...method,
|
||||||
|
type: "source",
|
||||||
|
input: "",
|
||||||
|
output: "ReturnValue",
|
||||||
|
kind: "remote",
|
||||||
|
}),
|
||||||
|
createModeledMethod({
|
||||||
|
...method,
|
||||||
|
type: "source",
|
||||||
|
input: "",
|
||||||
|
output: "ReturnValue",
|
||||||
|
kind: "local",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
it("can use the pagination", async () => {
|
||||||
|
render({
|
||||||
|
method,
|
||||||
|
modeledMethods,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText("Next modeling").getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
expect(screen.getByText("1/3")).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByLabelText("Next modeling"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText("Next modeling").getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
expect(screen.getByText("2/3")).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole("combobox", {
|
||||||
|
name: "Model type",
|
||||||
|
}),
|
||||||
|
).toHaveValue("source");
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByLabelText("Next modeling"));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText("Next modeling").getElementsByTagName("input")[0],
|
||||||
|
).toBeDisabled();
|
||||||
|
expect(screen.getByText("3/3")).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole("combobox", {
|
||||||
|
name: "Kind",
|
||||||
|
}),
|
||||||
|
).toHaveValue("local");
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByLabelText("Previous modeling"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Next modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen
|
||||||
|
.getByLabelText("Previous modeling")
|
||||||
|
.getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
expect(
|
||||||
|
screen.getByLabelText("Next modeling").getElementsByTagName("input")[0],
|
||||||
|
).toBeEnabled();
|
||||||
|
expect(screen.getByText("2/3")).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole("combobox", {
|
||||||
|
name: "Kind",
|
||||||
|
}),
|
||||||
|
).toHaveValue("remote");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -78,10 +78,7 @@ export type LibraryRowProps = {
|
|||||||
hideModeledMethods: boolean;
|
hideModeledMethods: boolean;
|
||||||
revealedMethodSignature: string | null;
|
revealedMethodSignature: string | null;
|
||||||
onChange: (modeledMethod: ModeledMethod) => void;
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
onSaveModelClick: (
|
onSaveModelClick: (methodSignatures: string[]) => void;
|
||||||
methods: Method[],
|
|
||||||
modeledMethods: Record<string, ModeledMethod>,
|
|
||||||
) => void;
|
|
||||||
onGenerateFromLlmClick: (
|
onGenerateFromLlmClick: (
|
||||||
dependencyName: string,
|
dependencyName: string,
|
||||||
methods: Method[],
|
methods: Method[],
|
||||||
@@ -165,11 +162,11 @@ export const LibraryRow = ({
|
|||||||
|
|
||||||
const handleSave = useCallback(
|
const handleSave = useCallback(
|
||||||
async (e: React.MouseEvent) => {
|
async (e: React.MouseEvent) => {
|
||||||
onSaveModelClick(methods, modeledMethods);
|
onSaveModelClick(methods.map((m) => m.signature));
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
},
|
||||||
[methods, modeledMethods, onSaveModelClick],
|
[methods, onSaveModelClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasUnsavedChanges = useMemo(() => {
|
const hasUnsavedChanges = useMemo(() => {
|
||||||
@@ -235,7 +232,7 @@ export const LibraryRow = ({
|
|||||||
modeledMethods={modeledMethods}
|
modeledMethods={modeledMethods}
|
||||||
modifiedSignatures={modifiedSignatures}
|
modifiedSignatures={modifiedSignatures}
|
||||||
inProgressMethods={inProgressMethods}
|
inProgressMethods={inProgressMethods}
|
||||||
mode={viewState.mode}
|
viewState={viewState}
|
||||||
hideModeledMethods={hideModeledMethods}
|
hideModeledMethods={hideModeledMethods}
|
||||||
revealedMethodSignature={revealedMethodSignature}
|
revealedMethodSignature={revealedMethodSignature}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -21,8 +21,16 @@ import { MethodName } from "./MethodName";
|
|||||||
import { ModelTypeDropdown } from "./ModelTypeDropdown";
|
import { ModelTypeDropdown } from "./ModelTypeDropdown";
|
||||||
import { ModelInputDropdown } from "./ModelInputDropdown";
|
import { ModelInputDropdown } from "./ModelInputDropdown";
|
||||||
import { ModelOutputDropdown } from "./ModelOutputDropdown";
|
import { ModelOutputDropdown } from "./ModelOutputDropdown";
|
||||||
|
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||||
|
|
||||||
const ApiOrMethodCell = styled(VSCodeDataGridCell)`
|
const MultiModelColumn = styled(VSCodeDataGridCell)`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ApiOrMethodRow = styled.div`
|
||||||
|
min-height: calc(var(--input-height) * 1px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -55,10 +63,10 @@ const DataGridRow = styled(VSCodeDataGridRow)<{ focused?: boolean }>`
|
|||||||
export type MethodRowProps = {
|
export type MethodRowProps = {
|
||||||
method: Method;
|
method: Method;
|
||||||
methodCanBeModeled: boolean;
|
methodCanBeModeled: boolean;
|
||||||
modeledMethod: ModeledMethod | undefined;
|
modeledMethods: ModeledMethod[];
|
||||||
methodIsUnsaved: boolean;
|
methodIsUnsaved: boolean;
|
||||||
modelingInProgress: boolean;
|
modelingInProgress: boolean;
|
||||||
mode: Mode;
|
viewState: ModelEditorViewState;
|
||||||
revealedMethodSignature: string | null;
|
revealedMethodSignature: string | null;
|
||||||
onChange: (modeledMethod: ModeledMethod) => void;
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
};
|
};
|
||||||
@@ -88,19 +96,23 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
|
|||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const {
|
const {
|
||||||
method,
|
method,
|
||||||
modeledMethod,
|
modeledMethods: modeledMethodsProp,
|
||||||
methodIsUnsaved,
|
methodIsUnsaved,
|
||||||
mode,
|
viewState,
|
||||||
revealedMethodSignature,
|
revealedMethodSignature,
|
||||||
onChange,
|
onChange,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const modeledMethods = viewState.showMultipleModels
|
||||||
|
? modeledMethodsProp
|
||||||
|
: modeledMethodsProp.slice(0, 1);
|
||||||
|
|
||||||
const jumpToUsage = useCallback(
|
const jumpToUsage = useCallback(
|
||||||
() => sendJumpToUsageMessage(method),
|
() => sendJumpToUsageMessage(method),
|
||||||
[method],
|
[method],
|
||||||
);
|
);
|
||||||
|
|
||||||
const modelingStatus = getModelingStatus(modeledMethod, methodIsUnsaved);
|
const modelingStatus = getModelingStatus(modeledMethods, methodIsUnsaved);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataGridRow
|
<DataGridRow
|
||||||
@@ -108,18 +120,20 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
focused={revealedMethodSignature === method.signature}
|
focused={revealedMethodSignature === method.signature}
|
||||||
>
|
>
|
||||||
<ApiOrMethodCell gridColumn={1}>
|
<VSCodeDataGridCell gridColumn={1}>
|
||||||
<ModelingStatusIndicator status={modelingStatus} />
|
<ApiOrMethodRow>
|
||||||
<MethodClassifications method={method} />
|
<ModelingStatusIndicator status={modelingStatus} />
|
||||||
<MethodName {...props.method} />
|
<MethodClassifications method={method} />
|
||||||
{mode === Mode.Application && (
|
<MethodName {...props.method} />
|
||||||
<UsagesButton onClick={jumpToUsage}>
|
{viewState.mode === Mode.Application && (
|
||||||
{method.usages.length}
|
<UsagesButton onClick={jumpToUsage}>
|
||||||
</UsagesButton>
|
{method.usages.length}
|
||||||
)}
|
</UsagesButton>
|
||||||
<ViewLink onClick={jumpToUsage}>View</ViewLink>
|
)}
|
||||||
{props.modelingInProgress && <ProgressRing />}
|
<ViewLink onClick={jumpToUsage}>View</ViewLink>
|
||||||
</ApiOrMethodCell>
|
{props.modelingInProgress && <ProgressRing />}
|
||||||
|
</ApiOrMethodRow>
|
||||||
|
</VSCodeDataGridCell>
|
||||||
{props.modelingInProgress && (
|
{props.modelingInProgress && (
|
||||||
<>
|
<>
|
||||||
<VSCodeDataGridCell gridColumn={2}>
|
<VSCodeDataGridCell gridColumn={2}>
|
||||||
@@ -138,34 +152,46 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
|
|||||||
)}
|
)}
|
||||||
{!props.modelingInProgress && (
|
{!props.modelingInProgress && (
|
||||||
<>
|
<>
|
||||||
<VSCodeDataGridCell gridColumn={2}>
|
<MultiModelColumn gridColumn={2}>
|
||||||
<ModelTypeDropdown
|
{forEachModeledMethod(modeledMethods, (modeledMethod, index) => (
|
||||||
method={method}
|
<ModelTypeDropdown
|
||||||
modeledMethod={modeledMethod}
|
key={index}
|
||||||
onChange={onChange}
|
method={method}
|
||||||
/>
|
modeledMethod={modeledMethod}
|
||||||
</VSCodeDataGridCell>
|
onChange={onChange}
|
||||||
<VSCodeDataGridCell gridColumn={3}>
|
/>
|
||||||
<ModelInputDropdown
|
))}
|
||||||
method={method}
|
</MultiModelColumn>
|
||||||
modeledMethod={modeledMethod}
|
<MultiModelColumn gridColumn={3}>
|
||||||
onChange={onChange}
|
{forEachModeledMethod(modeledMethods, (modeledMethod, index) => (
|
||||||
/>
|
<ModelInputDropdown
|
||||||
</VSCodeDataGridCell>
|
key={index}
|
||||||
<VSCodeDataGridCell gridColumn={4}>
|
method={method}
|
||||||
<ModelOutputDropdown
|
modeledMethod={modeledMethod}
|
||||||
method={method}
|
onChange={onChange}
|
||||||
modeledMethod={modeledMethod}
|
/>
|
||||||
onChange={onChange}
|
))}
|
||||||
/>
|
</MultiModelColumn>
|
||||||
</VSCodeDataGridCell>
|
<MultiModelColumn gridColumn={4}>
|
||||||
<VSCodeDataGridCell gridColumn={5}>
|
{forEachModeledMethod(modeledMethods, (modeledMethod, index) => (
|
||||||
<ModelKindDropdown
|
<ModelOutputDropdown
|
||||||
method={method}
|
key={index}
|
||||||
modeledMethod={modeledMethod}
|
method={method}
|
||||||
onChange={onChange}
|
modeledMethod={modeledMethod}
|
||||||
/>
|
onChange={onChange}
|
||||||
</VSCodeDataGridCell>
|
/>
|
||||||
|
))}
|
||||||
|
</MultiModelColumn>
|
||||||
|
<MultiModelColumn gridColumn={5}>
|
||||||
|
{forEachModeledMethod(modeledMethods, (modeledMethod, index) => (
|
||||||
|
<ModelKindDropdown
|
||||||
|
key={index}
|
||||||
|
method={method}
|
||||||
|
modeledMethod={modeledMethod}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</MultiModelColumn>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DataGridRow>
|
</DataGridRow>
|
||||||
@@ -178,7 +204,7 @@ const UnmodelableMethodRow = forwardRef<
|
|||||||
HTMLElement | undefined,
|
HTMLElement | undefined,
|
||||||
MethodRowProps
|
MethodRowProps
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const { method, mode, revealedMethodSignature } = props;
|
const { method, viewState, revealedMethodSignature } = props;
|
||||||
|
|
||||||
const jumpToUsage = useCallback(
|
const jumpToUsage = useCallback(
|
||||||
() => sendJumpToUsageMessage(method),
|
() => sendJumpToUsageMessage(method),
|
||||||
@@ -191,17 +217,19 @@ const UnmodelableMethodRow = forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
focused={revealedMethodSignature === method.signature}
|
focused={revealedMethodSignature === method.signature}
|
||||||
>
|
>
|
||||||
<ApiOrMethodCell gridColumn={1}>
|
<VSCodeDataGridCell gridColumn={1}>
|
||||||
<ModelingStatusIndicator status="saved" />
|
<ApiOrMethodRow>
|
||||||
<MethodName {...props.method} />
|
<ModelingStatusIndicator status="saved" />
|
||||||
{mode === Mode.Application && (
|
<MethodName {...props.method} />
|
||||||
<UsagesButton onClick={jumpToUsage}>
|
{viewState.mode === Mode.Application && (
|
||||||
{method.usages.length}
|
<UsagesButton onClick={jumpToUsage}>
|
||||||
</UsagesButton>
|
{method.usages.length}
|
||||||
)}
|
</UsagesButton>
|
||||||
<ViewLink onClick={jumpToUsage}>View</ViewLink>
|
)}
|
||||||
<MethodClassifications method={method} />
|
<ViewLink onClick={jumpToUsage}>View</ViewLink>
|
||||||
</ApiOrMethodCell>
|
<MethodClassifications method={method} />
|
||||||
|
</ApiOrMethodRow>
|
||||||
|
</VSCodeDataGridCell>
|
||||||
<VSCodeDataGridCell gridColumn="span 4">
|
<VSCodeDataGridCell gridColumn="span 4">
|
||||||
Method already modeled
|
Method already modeled
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
@@ -218,3 +246,17 @@ function sendJumpToUsageMessage(method: Method) {
|
|||||||
usage: method.usages[0],
|
usage: method.usages[0],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forEachModeledMethod(
|
||||||
|
modeledMethods: ModeledMethod[],
|
||||||
|
renderer: (
|
||||||
|
modeledMethod: ModeledMethod | undefined,
|
||||||
|
index: number,
|
||||||
|
) => JSX.Element,
|
||||||
|
): JSX.Element | JSX.Element[] {
|
||||||
|
if (modeledMethods.length === 0) {
|
||||||
|
return renderer(undefined, 0);
|
||||||
|
} else {
|
||||||
|
return modeledMethods.map(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export function ModelEditor({
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "revealMethod":
|
case "revealMethod":
|
||||||
setRevealedMethodSignature(msg.method.signature);
|
setRevealedMethodSignature(msg.methodSignature);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -196,21 +196,15 @@ export function ModelEditor({
|
|||||||
const onSaveAllClick = useCallback(() => {
|
const onSaveAllClick = useCallback(() => {
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
t: "saveModeledMethods",
|
t: "saveModeledMethods",
|
||||||
methods,
|
|
||||||
modeledMethods,
|
|
||||||
});
|
});
|
||||||
}, [methods, modeledMethods]);
|
}, []);
|
||||||
|
|
||||||
const onSaveModelClick = useCallback(
|
const onSaveModelClick = useCallback((methodSignatures: string[]) => {
|
||||||
(methods: Method[], modeledMethods: Record<string, ModeledMethod>) => {
|
vscode.postMessage({
|
||||||
vscode.postMessage({
|
t: "saveModeledMethods",
|
||||||
t: "saveModeledMethods",
|
methodSignatures,
|
||||||
methods,
|
});
|
||||||
modeledMethods,
|
}, []);
|
||||||
});
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onGenerateFromSourceClick = useCallback(() => {
|
const onGenerateFromSourceClick = useCallback(() => {
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import { MethodRow } from "./MethodRow";
|
|||||||
import { Method } from "../../model-editor/method";
|
import { Method } from "../../model-editor/method";
|
||||||
import { ModeledMethod } from "../../model-editor/modeled-method";
|
import { ModeledMethod } from "../../model-editor/modeled-method";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Mode } from "../../model-editor/shared/mode";
|
|
||||||
import { sortMethods } from "../../model-editor/shared/sorting";
|
import { sortMethods } from "../../model-editor/shared/sorting";
|
||||||
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
|
import { InProgressMethods } from "../../model-editor/shared/in-progress-methods";
|
||||||
import { HiddenMethodsRow } from "./HiddenMethodsRow";
|
import { HiddenMethodsRow } from "./HiddenMethodsRow";
|
||||||
|
import { ModelEditorViewState } from "../../model-editor/shared/view-state";
|
||||||
|
|
||||||
export const GRID_TEMPLATE_COLUMNS = "0.5fr 0.125fr 0.125fr 0.125fr 0.125fr";
|
export const GRID_TEMPLATE_COLUMNS = "0.5fr 0.125fr 0.125fr 0.125fr 0.125fr";
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ export type ModeledMethodDataGridProps = {
|
|||||||
modeledMethods: Record<string, ModeledMethod>;
|
modeledMethods: Record<string, ModeledMethod>;
|
||||||
modifiedSignatures: Set<string>;
|
modifiedSignatures: Set<string>;
|
||||||
inProgressMethods: InProgressMethods;
|
inProgressMethods: InProgressMethods;
|
||||||
mode: Mode;
|
viewState: ModelEditorViewState;
|
||||||
hideModeledMethods: boolean;
|
hideModeledMethods: boolean;
|
||||||
revealedMethodSignature: string | null;
|
revealedMethodSignature: string | null;
|
||||||
onChange: (modeledMethod: ModeledMethod) => void;
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
@@ -33,7 +33,7 @@ export const ModeledMethodDataGrid = ({
|
|||||||
modeledMethods,
|
modeledMethods,
|
||||||
modifiedSignatures,
|
modifiedSignatures,
|
||||||
inProgressMethods,
|
inProgressMethods,
|
||||||
mode,
|
viewState,
|
||||||
hideModeledMethods,
|
hideModeledMethods,
|
||||||
revealedMethodSignature,
|
revealedMethodSignature,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -84,22 +84,25 @@ export const ModeledMethodDataGrid = ({
|
|||||||
Kind
|
Kind
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
</VSCodeDataGridRow>
|
</VSCodeDataGridRow>
|
||||||
{methodsWithModelability.map(({ method, methodCanBeModeled }) => (
|
{methodsWithModelability.map(({ method, methodCanBeModeled }) => {
|
||||||
<MethodRow
|
const modeledMethod = modeledMethods[method.signature];
|
||||||
key={method.signature}
|
return (
|
||||||
method={method}
|
<MethodRow
|
||||||
methodCanBeModeled={methodCanBeModeled}
|
key={method.signature}
|
||||||
modeledMethod={modeledMethods[method.signature]}
|
method={method}
|
||||||
methodIsUnsaved={modifiedSignatures.has(method.signature)}
|
methodCanBeModeled={methodCanBeModeled}
|
||||||
modelingInProgress={inProgressMethods.hasMethod(
|
modeledMethods={modeledMethod ? [modeledMethod] : []}
|
||||||
packageName,
|
methodIsUnsaved={modifiedSignatures.has(method.signature)}
|
||||||
method.signature,
|
modelingInProgress={inProgressMethods.hasMethod(
|
||||||
)}
|
packageName,
|
||||||
mode={mode}
|
method.signature,
|
||||||
revealedMethodSignature={revealedMethodSignature}
|
)}
|
||||||
onChange={onChange}
|
viewState={viewState}
|
||||||
/>
|
revealedMethodSignature={revealedMethodSignature}
|
||||||
))}
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<HiddenMethodsRow
|
<HiddenMethodsRow
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ export type ModeledMethodsListProps = {
|
|||||||
viewState: ModelEditorViewState;
|
viewState: ModelEditorViewState;
|
||||||
hideModeledMethods: boolean;
|
hideModeledMethods: boolean;
|
||||||
onChange: (modeledMethod: ModeledMethod) => void;
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
onSaveModelClick: (
|
onSaveModelClick: (methodSignatures: string[]) => void;
|
||||||
methods: Method[],
|
|
||||||
modeledMethods: Record<string, ModeledMethod>,
|
|
||||||
) => void;
|
|
||||||
onGenerateFromLlmClick: (
|
onGenerateFromLlmClick: (
|
||||||
packageName: string,
|
packageName: string,
|
||||||
methods: Method[],
|
methods: Method[],
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { Mode } from "../../../model-editor/shared/mode";
|
|||||||
import { MethodRow, MethodRowProps } from "../MethodRow";
|
import { MethodRow, MethodRowProps } from "../MethodRow";
|
||||||
import { ModeledMethod } from "../../../model-editor/modeled-method";
|
import { ModeledMethod } from "../../../model-editor/modeled-method";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { ModelEditorViewState } from "../../../model-editor/shared/view-state";
|
||||||
|
import { createMockExtensionPack } from "../../../../test/factories/model-editor/extension-pack";
|
||||||
|
|
||||||
describe(MethodRow.name, () => {
|
describe(MethodRow.name, () => {
|
||||||
const method = createMethod({
|
const method = createMethod({
|
||||||
@@ -31,16 +33,24 @@ describe(MethodRow.name, () => {
|
|||||||
};
|
};
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
const viewState: ModelEditorViewState = {
|
||||||
|
mode: Mode.Application,
|
||||||
|
showFlowGeneration: false,
|
||||||
|
showLlmButton: false,
|
||||||
|
showMultipleModels: false,
|
||||||
|
extensionPack: createMockExtensionPack(),
|
||||||
|
};
|
||||||
|
|
||||||
const render = (props: Partial<MethodRowProps> = {}) =>
|
const render = (props: Partial<MethodRowProps> = {}) =>
|
||||||
reactRender(
|
reactRender(
|
||||||
<MethodRow
|
<MethodRow
|
||||||
method={method}
|
method={method}
|
||||||
methodCanBeModeled={true}
|
methodCanBeModeled={true}
|
||||||
modeledMethod={modeledMethod}
|
modeledMethods={[modeledMethod]}
|
||||||
methodIsUnsaved={false}
|
methodIsUnsaved={false}
|
||||||
modelingInProgress={false}
|
modelingInProgress={false}
|
||||||
revealedMethodSignature={null}
|
revealedMethodSignature={null}
|
||||||
mode={Mode.Application}
|
viewState={viewState}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
{...props}
|
{...props}
|
||||||
/>,
|
/>,
|
||||||
@@ -54,6 +64,14 @@ describe(MethodRow.name, () => {
|
|||||||
expect(screen.queryByLabelText("Loading")).not.toBeInTheDocument();
|
expect(screen.queryByLabelText("Loading")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders when there is no modeled method", () => {
|
||||||
|
render({ modeledMethods: [] });
|
||||||
|
|
||||||
|
expect(screen.queryAllByRole("combobox")).toHaveLength(4);
|
||||||
|
expect(screen.getByLabelText("Method not modeled")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByLabelText("Loading")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("can change the kind", async () => {
|
it("can change the kind", async () => {
|
||||||
render();
|
render();
|
||||||
|
|
||||||
@@ -110,7 +128,7 @@ describe(MethodRow.name, () => {
|
|||||||
|
|
||||||
it("shows the modeling status indicator when unmodeled", () => {
|
it("shows the modeling status indicator when unmodeled", () => {
|
||||||
render({
|
render({
|
||||||
modeledMethod: undefined,
|
modeledMethods: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.getByLabelText("Method not modeled")).toBeInTheDocument();
|
expect(screen.getByLabelText("Method not modeled")).toBeInTheDocument();
|
||||||
@@ -124,10 +142,48 @@ describe(MethodRow.name, () => {
|
|||||||
expect(screen.getByLabelText("Loading")).toBeInTheDocument();
|
expect(screen.getByLabelText("Loading")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("can render multiple models", () => {
|
||||||
|
render({
|
||||||
|
modeledMethods: [
|
||||||
|
{ ...modeledMethod, type: "source" },
|
||||||
|
{ ...modeledMethod, type: "sink" },
|
||||||
|
{ ...modeledMethod, type: "summary" },
|
||||||
|
],
|
||||||
|
viewState: {
|
||||||
|
...viewState,
|
||||||
|
showMultipleModels: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const kindInputs = screen.getAllByRole("combobox", { name: "Model type" });
|
||||||
|
expect(kindInputs).toHaveLength(3);
|
||||||
|
expect(kindInputs[0]).toHaveValue("source");
|
||||||
|
expect(kindInputs[1]).toHaveValue("sink");
|
||||||
|
expect(kindInputs[2]).toHaveValue("summary");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders only first model when showMultipleModels feature flag is disabled", () => {
|
||||||
|
render({
|
||||||
|
modeledMethods: [
|
||||||
|
{ ...modeledMethod, type: "source" },
|
||||||
|
{ ...modeledMethod, type: "sink" },
|
||||||
|
{ ...modeledMethod, type: "summary" },
|
||||||
|
],
|
||||||
|
viewState: {
|
||||||
|
...viewState,
|
||||||
|
showMultipleModels: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const kindInputs = screen.getAllByRole("combobox", { name: "Model type" });
|
||||||
|
expect(kindInputs.length).toBe(1);
|
||||||
|
expect(kindInputs[0]).toHaveValue("source");
|
||||||
|
});
|
||||||
|
|
||||||
it("renders an unmodelable method", () => {
|
it("renders an unmodelable method", () => {
|
||||||
render({
|
render({
|
||||||
methodCanBeModeled: false,
|
methodCanBeModeled: false,
|
||||||
modeledMethod: undefined,
|
modeledMethods: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.queryByRole("combobox")).not.toBeInTheDocument();
|
expect(screen.queryByRole("combobox")).not.toBeInTheDocument();
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ describe(ModelKindDropdown.name, () => {
|
|||||||
// Changing the type to sink should update the supported kinds
|
// Changing the type to sink should update the supported kinds
|
||||||
const updatedModeledMethod = createModeledMethod({
|
const updatedModeledMethod = createModeledMethod({
|
||||||
type: "sink",
|
type: "sink",
|
||||||
|
kind: "local",
|
||||||
});
|
});
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {
|
|||||||
ModeledMethodDataGrid,
|
ModeledMethodDataGrid,
|
||||||
ModeledMethodDataGridProps,
|
ModeledMethodDataGridProps,
|
||||||
} from "../ModeledMethodDataGrid";
|
} from "../ModeledMethodDataGrid";
|
||||||
|
import { ModelEditorViewState } from "../../../model-editor/shared/view-state";
|
||||||
|
import { createMockExtensionPack } from "../../../../test/factories/model-editor/extension-pack";
|
||||||
|
|
||||||
describe(ModeledMethodDataGrid.name, () => {
|
describe(ModeledMethodDataGrid.name, () => {
|
||||||
const method1 = createMethod({
|
const method1 = createMethod({
|
||||||
@@ -41,6 +43,14 @@ describe(ModeledMethodDataGrid.name, () => {
|
|||||||
});
|
});
|
||||||
const onChange = jest.fn();
|
const onChange = jest.fn();
|
||||||
|
|
||||||
|
const viewState: ModelEditorViewState = {
|
||||||
|
mode: Mode.Application,
|
||||||
|
showFlowGeneration: false,
|
||||||
|
showLlmButton: false,
|
||||||
|
showMultipleModels: false,
|
||||||
|
extensionPack: createMockExtensionPack(),
|
||||||
|
};
|
||||||
|
|
||||||
const render = (props: Partial<ModeledMethodDataGridProps> = {}) =>
|
const render = (props: Partial<ModeledMethodDataGridProps> = {}) =>
|
||||||
reactRender(
|
reactRender(
|
||||||
<ModeledMethodDataGrid
|
<ModeledMethodDataGrid
|
||||||
@@ -58,7 +68,7 @@ describe(ModeledMethodDataGrid.name, () => {
|
|||||||
}}
|
}}
|
||||||
modifiedSignatures={new Set([method1.signature])}
|
modifiedSignatures={new Set([method1.signature])}
|
||||||
inProgressMethods={new InProgressMethods()}
|
inProgressMethods={new InProgressMethods()}
|
||||||
mode={Mode.Application}
|
viewState={viewState}
|
||||||
hideModeledMethods={false}
|
hideModeledMethods={false}
|
||||||
revealedMethodSignature={null}
|
revealedMethodSignature={null}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
@@ -170,6 +170,14 @@ repository:
|
|||||||
match: '\]'
|
match: '\]'
|
||||||
name: punctuation.squarebracket.close.ql
|
name: punctuation.squarebracket.close.ql
|
||||||
|
|
||||||
|
open-angle:
|
||||||
|
match: '<'
|
||||||
|
name: punctuation.anglebracket.open.ql
|
||||||
|
|
||||||
|
close-angle:
|
||||||
|
match: '>'
|
||||||
|
name: punctuation.anglebracket.close.ql
|
||||||
|
|
||||||
operator-or-punctuation:
|
operator-or-punctuation:
|
||||||
patterns:
|
patterns:
|
||||||
- include: '#relational-operator'
|
- include: '#relational-operator'
|
||||||
@@ -186,6 +194,8 @@ repository:
|
|||||||
- include: '#close-brace'
|
- include: '#close-brace'
|
||||||
- include: '#open-bracket'
|
- include: '#open-bracket'
|
||||||
- include: '#close-bracket'
|
- include: '#close-bracket'
|
||||||
|
- include: '#open-angle'
|
||||||
|
- include: '#close-angle'
|
||||||
|
|
||||||
# Keywords
|
# Keywords
|
||||||
dont-care:
|
dont-care:
|
||||||
@@ -651,18 +661,36 @@ repository:
|
|||||||
- include: '#non-context-sensitive'
|
- include: '#non-context-sensitive'
|
||||||
- include: '#annotation'
|
- include: '#annotation'
|
||||||
|
|
||||||
|
# The argument list of an instantiation, enclosed in angle brackets.
|
||||||
|
instantiation-args:
|
||||||
|
beginPattern: '#open-angle'
|
||||||
|
endPattern: '#close-angle'
|
||||||
|
name: meta.type.parameters.ql
|
||||||
|
patterns:
|
||||||
|
# Include `#instantiation-args` first so that `#open-angle` and `#close-angle` take precedence
|
||||||
|
# over `#relational-operator`.
|
||||||
|
- include: '#instantiation-args'
|
||||||
|
- include: '#non-context-sensitive'
|
||||||
|
- match: '(?#simple-id)'
|
||||||
|
name: entity.name.type.namespace.ql
|
||||||
|
|
||||||
# An `import` directive. Note that we parse the optional `as` clause as a separate top-level
|
# An `import` directive. Note that we parse the optional `as` clause as a separate top-level
|
||||||
# directive, because otherwise it's too hard to figure out where the `import` directive ends.
|
# directive, because otherwise it's too hard to figure out where the `import` directive ends.
|
||||||
import-directive:
|
import-directive:
|
||||||
beginPattern: '#import'
|
beginPattern: '#import'
|
||||||
# Ends with a simple-id that is not followed by a `.` or a `::`. This does not handle comments or
|
# TextMate makes it tricky to tell whether an identifier that we encounter is part of the
|
||||||
# line breaks between the simple-id and the `.` or `::`.
|
# `import` directive or whether it's the first token of the next module-level declaration.
|
||||||
end: '(?#simple-id) (?!\s*(\.|\:\:))'
|
# To find the end of the import directive, we'll look for a zero-width match where the previous
|
||||||
endCaptures:
|
# token is either an identifier (other than `import`) or a `>`, and the next token is not a `.`,
|
||||||
'0':
|
# `<`, `,`, or `::`. This works for nearly all real-world `import` directives, but it will end the
|
||||||
name: entity.name.type.namespace.ql
|
# `import` directive too early if there is a comment or line break between two components of the
|
||||||
|
# module expression.
|
||||||
|
end: '(?<!\bimport)(?<=(?:\>)|[A-Za-z0-9_]) (?!\s*(\.|\:\:|\,|(?#open-angle)))'
|
||||||
name: meta.block.import-directive.ql
|
name: meta.block.import-directive.ql
|
||||||
patterns:
|
patterns:
|
||||||
|
# Include `#instantiation-args` first so that `#open-angle` and `#close-angle` take precedence
|
||||||
|
# over `#relational-operator`.
|
||||||
|
- include: '#instantiation-args'
|
||||||
- include: '#non-context-sensitive'
|
- include: '#non-context-sensitive'
|
||||||
- match: '(?#simple-id)'
|
- match: '(?#simple-id)'
|
||||||
name: entity.name.type.namespace.ql
|
name: entity.name.type.namespace.ql
|
||||||
@@ -703,7 +731,6 @@ repository:
|
|||||||
- match: '(?#simple-id)|(?#at-lower-id)'
|
- match: '(?#simple-id)|(?#at-lower-id)'
|
||||||
name: entity.name.type.ql
|
name: entity.name.type.ql
|
||||||
|
|
||||||
|
|
||||||
# A `module` declaration, whether a module definition or an alias declaration.
|
# A `module` declaration, whether a module definition or an alias declaration.
|
||||||
module-declaration:
|
module-declaration:
|
||||||
# Starts with the `module` keyword.
|
# Starts with the `module` keyword.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function createModeledMethod(
|
|||||||
type: "sink",
|
type: "sink",
|
||||||
input: "Argument[0]",
|
input: "Argument[0]",
|
||||||
output: "",
|
output: "",
|
||||||
kind: "jndi-injection",
|
kind: "path-injection",
|
||||||
provenance: "manual",
|
provenance: "manual",
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import {
|
import { Uri, workspace, WorkspaceFolder } from "vscode";
|
||||||
ConfigurationScope,
|
|
||||||
Uri,
|
|
||||||
workspace,
|
|
||||||
WorkspaceConfiguration as VSCodeWorkspaceConfiguration,
|
|
||||||
WorkspaceFolder,
|
|
||||||
} from "vscode";
|
|
||||||
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
||||||
import { outputFile, readFile } from "fs-extra";
|
import { outputFile, readFile } from "fs-extra";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
@@ -14,7 +8,8 @@ import { QlpacksInfo } from "../../../../src/codeql-cli/cli";
|
|||||||
import { pickExtensionPack } from "../../../../src/model-editor/extension-pack-picker";
|
import { pickExtensionPack } from "../../../../src/model-editor/extension-pack-picker";
|
||||||
import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
|
import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
|
||||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||||
import { vscodeGetConfigurationMock } from "../../test-config";
|
import { ModelConfig } from "../../../../src/config";
|
||||||
|
import { mockedObject } from "../../utils/mocking.helpers";
|
||||||
|
|
||||||
describe("pickExtensionPack", () => {
|
describe("pickExtensionPack", () => {
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
@@ -32,6 +27,7 @@ describe("pickExtensionPack", () => {
|
|||||||
let workspaceFoldersSpy: jest.SpyInstance;
|
let workspaceFoldersSpy: jest.SpyInstance;
|
||||||
let additionalPacks: string[];
|
let additionalPacks: string[];
|
||||||
let workspaceFolder: WorkspaceFolder;
|
let workspaceFolder: WorkspaceFolder;
|
||||||
|
let modelConfig: ModelConfig;
|
||||||
|
|
||||||
const logger = createMockLogger();
|
const logger = createMockLogger();
|
||||||
const maxStep = 4;
|
const maxStep = 4;
|
||||||
@@ -67,41 +63,20 @@ describe("pickExtensionPack", () => {
|
|||||||
workspaceFoldersSpy = jest
|
workspaceFoldersSpy = jest
|
||||||
.spyOn(workspace, "workspaceFolders", "get")
|
.spyOn(workspace, "workspaceFolders", "get")
|
||||||
.mockReturnValue([workspaceFolder]);
|
.mockReturnValue([workspaceFolder]);
|
||||||
|
|
||||||
|
modelConfig = mockedObject<ModelConfig>({
|
||||||
|
getExtensionsDirectory: jest.fn().mockReturnValue(undefined),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("selects an existing extension pack", async () => {
|
it("selects an existing extension pack", async () => {
|
||||||
vscodeGetConfigurationMock.mockImplementation(
|
|
||||||
(
|
|
||||||
section?: string,
|
|
||||||
scope?: ConfigurationScope | null,
|
|
||||||
): VSCodeWorkspaceConfiguration => {
|
|
||||||
expect(section).toEqual("codeQL.model");
|
|
||||||
expect((scope as any)?.languageId).toEqual("java");
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: (key: string) => {
|
|
||||||
expect(key).toEqual("extensionsDirectory");
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
has: (key: string) => {
|
|
||||||
return key === "extensionsDirectory";
|
|
||||||
},
|
|
||||||
inspect: () => {
|
|
||||||
throw new Error("inspect not implemented");
|
|
||||||
},
|
|
||||||
update: () => {
|
|
||||||
throw new Error("update not implemented");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const cliServer = mockCliServer(qlPacks);
|
const cliServer = mockCliServer(qlPacks);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -112,35 +87,10 @@ describe("pickExtensionPack", () => {
|
|||||||
additionalPacks,
|
additionalPacks,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
expect(modelConfig.getExtensionsDirectory).toHaveBeenCalledWith("java");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates a new extension pack using default extensions directory", async () => {
|
it("creates a new extension pack using default extensions directory", async () => {
|
||||||
vscodeGetConfigurationMock.mockImplementation(
|
|
||||||
(
|
|
||||||
section?: string,
|
|
||||||
scope?: ConfigurationScope | null,
|
|
||||||
): VSCodeWorkspaceConfiguration => {
|
|
||||||
expect(section).toEqual("codeQL.model");
|
|
||||||
expect((scope as any)?.languageId).toEqual("java");
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: (key: string) => {
|
|
||||||
expect(key).toEqual("extensionsDirectory");
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
has: (key: string) => {
|
|
||||||
return key === "extensionsDirectory";
|
|
||||||
},
|
|
||||||
inspect: () => {
|
|
||||||
throw new Error("inspect not implemented");
|
|
||||||
},
|
|
||||||
update: () => {
|
|
||||||
throw new Error("update not implemented");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const tmpDir = await dir({
|
const tmpDir = await dir({
|
||||||
unsafeCleanup: true,
|
unsafeCleanup: true,
|
||||||
});
|
});
|
||||||
@@ -183,6 +133,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -199,6 +150,7 @@ describe("pickExtensionPack", () => {
|
|||||||
dataExtensions: ["models/**/*.yml"],
|
dataExtensions: ["models/**/*.yml"],
|
||||||
});
|
});
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(modelConfig.getExtensionsDirectory).toHaveBeenCalledWith("java");
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
||||||
@@ -223,31 +175,9 @@ describe("pickExtensionPack", () => {
|
|||||||
"my-custom-extensions-directory",
|
"my-custom-extensions-directory",
|
||||||
);
|
);
|
||||||
|
|
||||||
vscodeGetConfigurationMock.mockImplementation(
|
const modelConfig = mockedObject<ModelConfig>({
|
||||||
(
|
getExtensionsDirectory: jest.fn().mockReturnValue(configExtensionsDir),
|
||||||
section?: string,
|
});
|
||||||
scope?: ConfigurationScope | null,
|
|
||||||
): VSCodeWorkspaceConfiguration => {
|
|
||||||
expect(section).toEqual("codeQL.model");
|
|
||||||
expect((scope as any)?.languageId).toEqual("java");
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: (key: string) => {
|
|
||||||
expect(key).toEqual("extensionsDirectory");
|
|
||||||
return configExtensionsDir;
|
|
||||||
},
|
|
||||||
has: (key: string) => {
|
|
||||||
return key === "extensionsDirectory";
|
|
||||||
},
|
|
||||||
inspect: () => {
|
|
||||||
throw new Error("inspect not implemented");
|
|
||||||
},
|
|
||||||
update: () => {
|
|
||||||
throw new Error("update not implemented");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const newPackDir = join(configExtensionsDir, "vscode-codeql-java");
|
const newPackDir = join(configExtensionsDir, "vscode-codeql-java");
|
||||||
|
|
||||||
@@ -257,6 +187,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -273,6 +204,7 @@ describe("pickExtensionPack", () => {
|
|||||||
dataExtensions: ["models/**/*.yml"],
|
dataExtensions: ["models/**/*.yml"],
|
||||||
});
|
});
|
||||||
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
expect(cliServer.resolveQlpacks).toHaveBeenCalled();
|
||||||
|
expect(modelConfig.getExtensionsDirectory).toHaveBeenCalledWith("java");
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
loadYaml(await readFile(join(newPackDir, "codeql-pack.yml"), "utf8")),
|
||||||
@@ -299,6 +231,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -324,6 +257,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -351,6 +285,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -388,6 +323,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -425,6 +361,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -465,6 +402,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
@@ -522,6 +460,7 @@ describe("pickExtensionPack", () => {
|
|||||||
await pickExtensionPack(
|
await pickExtensionPack(
|
||||||
cliServer,
|
cliServer,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
|
modelConfig,
|
||||||
logger,
|
logger,
|
||||||
progress,
|
progress,
|
||||||
maxStep,
|
maxStep,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { QueryLanguage } from "../../../../src/common/query-language";
|
|||||||
import { Mode } from "../../../../src/model-editor/shared/mode";
|
import { Mode } from "../../../../src/model-editor/shared/mode";
|
||||||
import { mockedObject } from "../../utils/mocking.helpers";
|
import { mockedObject } from "../../utils/mocking.helpers";
|
||||||
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
||||||
|
import { ModelConfig } from "../../../../src/config";
|
||||||
|
|
||||||
describe("setUpPack", () => {
|
describe("setUpPack", () => {
|
||||||
let queryDir: string;
|
let queryDir: string;
|
||||||
@@ -32,8 +33,11 @@ describe("setUpPack", () => {
|
|||||||
packInstall: jest.fn(),
|
packInstall: jest.fn(),
|
||||||
resolveQueriesInSuite: jest.fn().mockResolvedValue([]),
|
resolveQueriesInSuite: jest.fn().mockResolvedValue([]),
|
||||||
});
|
});
|
||||||
|
const modelConfig = mockedObject<ModelConfig>({
|
||||||
|
llmGeneration: false,
|
||||||
|
});
|
||||||
|
|
||||||
await setUpPack(cliServer, queryDir, language);
|
await setUpPack(cliServer, queryDir, language, modelConfig);
|
||||||
|
|
||||||
const queryFiles = await readdir(queryDir);
|
const queryFiles = await readdir(queryDir);
|
||||||
expect(queryFiles.sort()).toEqual(
|
expect(queryFiles.sort()).toEqual(
|
||||||
@@ -89,8 +93,11 @@ describe("setUpPack", () => {
|
|||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]),
|
.mockResolvedValue(["/a/b/c/ApplicationModeEndpoints.ql"]),
|
||||||
});
|
});
|
||||||
|
const modelConfig = mockedObject<ModelConfig>({
|
||||||
|
llmGeneration: false,
|
||||||
|
});
|
||||||
|
|
||||||
await setUpPack(cliServer, queryDir, language);
|
await setUpPack(cliServer, queryDir, language, modelConfig);
|
||||||
|
|
||||||
const queryFiles = await readdir(queryDir);
|
const queryFiles = await readdir(queryDir);
|
||||||
expect(queryFiles.sort()).toEqual(["codeql-pack.yml"].sort());
|
expect(queryFiles.sort()).toEqual(["codeql-pack.yml"].sort());
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ import { QueryRunner } from "../../../../src/query-server";
|
|||||||
import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
|
import { ExtensionPack } from "../../../../src/model-editor/shared/extension-pack";
|
||||||
import { createMockModelingStore } from "../../../__mocks__/model-editor/modelingStoreMock";
|
import { createMockModelingStore } from "../../../__mocks__/model-editor/modelingStoreMock";
|
||||||
import { createMockModelEditorViewTracker } from "../../../__mocks__/model-editor/modelEditorViewTrackerMock";
|
import { createMockModelEditorViewTracker } from "../../../__mocks__/model-editor/modelEditorViewTrackerMock";
|
||||||
|
import { ModelConfigListener } from "../../../../src/config";
|
||||||
|
|
||||||
describe("ModelEditorView", () => {
|
describe("ModelEditorView", () => {
|
||||||
const app = createMockApp({});
|
const app = createMockApp({});
|
||||||
const modelingStore = createMockModelingStore();
|
const modelingStore = createMockModelingStore();
|
||||||
const viewTracker = createMockModelEditorViewTracker();
|
const viewTracker = createMockModelEditorViewTracker();
|
||||||
|
const modelConfig = mockedObject<ModelConfigListener>({
|
||||||
|
onDidChangeConfiguration: jest.fn(),
|
||||||
|
});
|
||||||
const databaseManager = mockEmptyDatabaseManager();
|
const databaseManager = mockEmptyDatabaseManager();
|
||||||
const cliServer = mockedObject<CodeQLCliServer>({});
|
const cliServer = mockedObject<CodeQLCliServer>({});
|
||||||
const queryRunner = mockedObject<QueryRunner>({});
|
const queryRunner = mockedObject<QueryRunner>({});
|
||||||
@@ -41,6 +45,7 @@ describe("ModelEditorView", () => {
|
|||||||
app,
|
app,
|
||||||
modelingStore,
|
modelingStore,
|
||||||
viewTracker,
|
viewTracker,
|
||||||
|
modelConfig,
|
||||||
databaseManager,
|
databaseManager,
|
||||||
cliServer,
|
cliServer,
|
||||||
queryRunner,
|
queryRunner,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
workspace,
|
workspace,
|
||||||
ConfigurationTarget,
|
ConfigurationTarget,
|
||||||
window,
|
window,
|
||||||
|
env,
|
||||||
} from "vscode";
|
} from "vscode";
|
||||||
import {
|
import {
|
||||||
ExtensionTelemetryListener,
|
ExtensionTelemetryListener,
|
||||||
@@ -30,13 +31,18 @@ describe("telemetry reporting", () => {
|
|||||||
let sendTelemetryEventSpy: jest.SpiedFunction<
|
let sendTelemetryEventSpy: jest.SpiedFunction<
|
||||||
typeof TelemetryReporter.prototype.sendTelemetryEvent
|
typeof TelemetryReporter.prototype.sendTelemetryEvent
|
||||||
>;
|
>;
|
||||||
let sendTelemetryExceptionSpy: jest.SpiedFunction<
|
let sendTelemetryErrorEventSpy: jest.SpiedFunction<
|
||||||
typeof TelemetryReporter.prototype.sendTelemetryException
|
typeof TelemetryReporter.prototype.sendTelemetryErrorEvent
|
||||||
>;
|
>;
|
||||||
let disposeSpy: jest.SpiedFunction<
|
let disposeSpy: jest.SpiedFunction<
|
||||||
typeof TelemetryReporter.prototype.dispose
|
typeof TelemetryReporter.prototype.dispose
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
let isTelemetryEnabledSpy: jest.SpyInstance<
|
||||||
|
typeof env.isTelemetryEnabled,
|
||||||
|
[]
|
||||||
|
>;
|
||||||
|
|
||||||
let showInformationMessageSpy: jest.SpiedFunction<
|
let showInformationMessageSpy: jest.SpiedFunction<
|
||||||
typeof window.showInformationMessage
|
typeof window.showInformationMessage
|
||||||
>;
|
>;
|
||||||
@@ -56,8 +62,8 @@ describe("telemetry reporting", () => {
|
|||||||
sendTelemetryEventSpy = jest
|
sendTelemetryEventSpy = jest
|
||||||
.spyOn(TelemetryReporter.prototype, "sendTelemetryEvent")
|
.spyOn(TelemetryReporter.prototype, "sendTelemetryEvent")
|
||||||
.mockReturnValue(undefined);
|
.mockReturnValue(undefined);
|
||||||
sendTelemetryExceptionSpy = jest
|
sendTelemetryErrorEventSpy = jest
|
||||||
.spyOn(TelemetryReporter.prototype, "sendTelemetryException")
|
.spyOn(TelemetryReporter.prototype, "sendTelemetryErrorEvent")
|
||||||
.mockReturnValue(undefined);
|
.mockReturnValue(undefined);
|
||||||
disposeSpy = jest
|
disposeSpy = jest
|
||||||
.spyOn(TelemetryReporter.prototype, "dispose")
|
.spyOn(TelemetryReporter.prototype, "dispose")
|
||||||
@@ -78,6 +84,9 @@ describe("telemetry reporting", () => {
|
|||||||
.get<boolean>("codeQL.canary")).toString();
|
.get<boolean>("codeQL.canary")).toString();
|
||||||
|
|
||||||
// each test will default to telemetry being enabled
|
// each test will default to telemetry being enabled
|
||||||
|
isTelemetryEnabledSpy = jest
|
||||||
|
.spyOn(env, "isTelemetryEnabled", "get")
|
||||||
|
.mockReturnValue(true);
|
||||||
await enableTelemetry("telemetry", true);
|
await enableTelemetry("telemetry", true);
|
||||||
await enableTelemetry("codeQL.telemetry", true);
|
await enableTelemetry("codeQL.telemetry", true);
|
||||||
|
|
||||||
@@ -116,6 +125,7 @@ describe("telemetry reporting", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize telemetry when global option disabled", async () => {
|
it("should initialize telemetry when global option disabled", async () => {
|
||||||
|
isTelemetryEnabledSpy.mockReturnValue(false);
|
||||||
await enableTelemetry("telemetry", false);
|
await enableTelemetry("telemetry", false);
|
||||||
await telemetryListener.initialize();
|
await telemetryListener.initialize();
|
||||||
expect(telemetryListener._reporter).toBeDefined();
|
expect(telemetryListener._reporter).toBeDefined();
|
||||||
@@ -133,6 +143,7 @@ describe("telemetry reporting", () => {
|
|||||||
|
|
||||||
it("should not initialize telemetry when both options disabled", async () => {
|
it("should not initialize telemetry when both options disabled", async () => {
|
||||||
await enableTelemetry("codeQL.telemetry", false);
|
await enableTelemetry("codeQL.telemetry", false);
|
||||||
|
isTelemetryEnabledSpy.mockReturnValue(false);
|
||||||
await enableTelemetry("telemetry", false);
|
await enableTelemetry("telemetry", false);
|
||||||
await telemetryListener.initialize();
|
await telemetryListener.initialize();
|
||||||
expect(telemetryListener._reporter).toBeUndefined();
|
expect(telemetryListener._reporter).toBeUndefined();
|
||||||
@@ -179,6 +190,7 @@ describe("telemetry reporting", () => {
|
|||||||
const reporter: any = telemetryListener._reporter;
|
const reporter: any = telemetryListener._reporter;
|
||||||
expect(reporter.userOptIn).toBe(true); // enabled
|
expect(reporter.userOptIn).toBe(true); // enabled
|
||||||
|
|
||||||
|
isTelemetryEnabledSpy.mockReturnValue(false);
|
||||||
await enableTelemetry("telemetry", false);
|
await enableTelemetry("telemetry", false);
|
||||||
expect(reporter.userOptIn).toBe(false); // disabled
|
expect(reporter.userOptIn).toBe(false); // disabled
|
||||||
});
|
});
|
||||||
@@ -198,8 +210,7 @@ describe("telemetry reporting", () => {
|
|||||||
},
|
},
|
||||||
{ executionTime: 1234 },
|
{ executionTime: 1234 },
|
||||||
);
|
);
|
||||||
|
expect(sendTelemetryErrorEventSpy).not.toBeCalled();
|
||||||
expect(sendTelemetryExceptionSpy).not.toBeCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should send a command usage event with an error", async () => {
|
it("should send a command usage event with an error", async () => {
|
||||||
@@ -221,8 +232,7 @@ describe("telemetry reporting", () => {
|
|||||||
},
|
},
|
||||||
{ executionTime: 1234 },
|
{ executionTime: 1234 },
|
||||||
);
|
);
|
||||||
|
expect(sendTelemetryErrorEventSpy).not.toBeCalled();
|
||||||
expect(sendTelemetryExceptionSpy).not.toBeCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should send a command usage event with a cli version", async () => {
|
it("should send a command usage event with a cli version", async () => {
|
||||||
@@ -245,8 +255,7 @@ describe("telemetry reporting", () => {
|
|||||||
},
|
},
|
||||||
{ executionTime: 1234 },
|
{ executionTime: 1234 },
|
||||||
);
|
);
|
||||||
|
expect(sendTelemetryErrorEventSpy).not.toBeCalled();
|
||||||
expect(sendTelemetryExceptionSpy).not.toBeCalled();
|
|
||||||
|
|
||||||
// Verify that if the cli version is not set, then the telemetry falls back to "not-set"
|
// Verify that if the cli version is not set, then the telemetry falls back to "not-set"
|
||||||
sendTelemetryEventSpy.mockClear();
|
sendTelemetryEventSpy.mockClear();
|
||||||
@@ -268,6 +277,7 @@ describe("telemetry reporting", () => {
|
|||||||
},
|
},
|
||||||
{ executionTime: 5678 },
|
{ executionTime: 5678 },
|
||||||
);
|
);
|
||||||
|
expect(sendTelemetryErrorEventSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should avoid sending an event when telemetry is disabled", async () => {
|
it("should avoid sending an event when telemetry is disabled", async () => {
|
||||||
@@ -278,7 +288,7 @@ describe("telemetry reporting", () => {
|
|||||||
telemetryListener.sendCommandUsage("command-id", 1234, new Error());
|
telemetryListener.sendCommandUsage("command-id", 1234, new Error());
|
||||||
|
|
||||||
expect(sendTelemetryEventSpy).not.toBeCalled();
|
expect(sendTelemetryEventSpy).not.toBeCalled();
|
||||||
expect(sendTelemetryExceptionSpy).not.toBeCalled();
|
expect(sendTelemetryErrorEventSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should send an event when telemetry is re-enabled", async () => {
|
it("should send an event when telemetry is re-enabled", async () => {
|
||||||
@@ -298,6 +308,7 @@ describe("telemetry reporting", () => {
|
|||||||
},
|
},
|
||||||
{ executionTime: 1234 },
|
{ executionTime: 1234 },
|
||||||
);
|
);
|
||||||
|
expect(sendTelemetryErrorEventSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should filter undesired properties from telemetry payload", async () => {
|
it("should filter undesired properties from telemetry payload", async () => {
|
||||||
@@ -345,6 +356,8 @@ describe("telemetry reporting", () => {
|
|||||||
resolveArg(3 /* "yes" item */),
|
resolveArg(3 /* "yes" item */),
|
||||||
);
|
);
|
||||||
await ctx.globalState.update("telemetry-request-viewed", false);
|
await ctx.globalState.update("telemetry-request-viewed", false);
|
||||||
|
expect(env.isTelemetryEnabled).toBe(true);
|
||||||
|
|
||||||
await enableTelemetry("codeQL.telemetry", false);
|
await enableTelemetry("codeQL.telemetry", false);
|
||||||
|
|
||||||
await telemetryListener.initialize();
|
await telemetryListener.initialize();
|
||||||
@@ -411,6 +424,7 @@ describe("telemetry reporting", () => {
|
|||||||
// If the user ever turns global telemetry back on, then we can
|
// If the user ever turns global telemetry back on, then we can
|
||||||
// show the dialog.
|
// show the dialog.
|
||||||
|
|
||||||
|
isTelemetryEnabledSpy.mockReturnValue(false);
|
||||||
await enableTelemetry("telemetry", false);
|
await enableTelemetry("telemetry", false);
|
||||||
await ctx.globalState.update("telemetry-request-viewed", false);
|
await ctx.globalState.update("telemetry-request-viewed", false);
|
||||||
|
|
||||||
@@ -455,6 +469,7 @@ describe("telemetry reporting", () => {
|
|||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
expect(sendTelemetryErrorEventSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should send a ui-interaction telementry event with a cli version", async () => {
|
it("should send a ui-interaction telementry event with a cli version", async () => {
|
||||||
@@ -472,6 +487,7 @@ describe("telemetry reporting", () => {
|
|||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
expect(sendTelemetryErrorEventSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should send an error telementry event", async () => {
|
it("should send an error telementry event", async () => {
|
||||||
@@ -479,7 +495,8 @@ describe("telemetry reporting", () => {
|
|||||||
|
|
||||||
telemetryListener.sendError(redactableError`test`);
|
telemetryListener.sendError(redactableError`test`);
|
||||||
|
|
||||||
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
|
expect(sendTelemetryEventSpy).not.toBeCalled();
|
||||||
|
expect(sendTelemetryErrorEventSpy).toHaveBeenCalledWith(
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
message: "test",
|
message: "test",
|
||||||
@@ -497,7 +514,8 @@ describe("telemetry reporting", () => {
|
|||||||
|
|
||||||
telemetryListener.sendError(redactableError`test`);
|
telemetryListener.sendError(redactableError`test`);
|
||||||
|
|
||||||
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
|
expect(sendTelemetryEventSpy).not.toBeCalled();
|
||||||
|
expect(sendTelemetryErrorEventSpy).toHaveBeenCalledWith(
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
message: "test",
|
message: "test",
|
||||||
@@ -516,7 +534,8 @@ describe("telemetry reporting", () => {
|
|||||||
redactableError`test message with secret information: ${42} and more ${"secret"} parts`,
|
redactableError`test message with secret information: ${42} and more ${"secret"} parts`,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
|
expect(sendTelemetryEventSpy).not.toBeCalled();
|
||||||
|
expect(sendTelemetryErrorEventSpy).toHaveBeenCalledWith(
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
|
|||||||
@@ -108,6 +108,14 @@
|
|||||||
"match": "(?x)\\]",
|
"match": "(?x)\\]",
|
||||||
"name": "punctuation.squarebracket.close.ql"
|
"name": "punctuation.squarebracket.close.ql"
|
||||||
},
|
},
|
||||||
|
"open-angle": {
|
||||||
|
"match": "(?x)<",
|
||||||
|
"name": "punctuation.anglebracket.open.ql"
|
||||||
|
},
|
||||||
|
"close-angle": {
|
||||||
|
"match": "(?x)>",
|
||||||
|
"name": "punctuation.anglebracket.close.ql"
|
||||||
|
},
|
||||||
"operator-or-punctuation": {
|
"operator-or-punctuation": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
@@ -151,6 +159,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"include": "#close-bracket"
|
"include": "#close-bracket"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#open-angle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#close-angle"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -661,9 +675,9 @@
|
|||||||
"begin": "(?x)(?<=/\\*\\*)([^*]|\\*(?!/))*$",
|
"begin": "(?x)(?<=/\\*\\*)([^*]|\\*(?!/))*$",
|
||||||
"while": "(?x)(^|\\G)\\s*([^*]|\\*(?!/))(?=([^*]|[*](?!/))*$)",
|
"while": "(?x)(^|\\G)\\s*([^*]|\\*(?!/))(?=([^*]|[*](?!/))*$)",
|
||||||
"patterns": [
|
"patterns": [
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"match": "(?x)\\G\\s* (@\\S+)",
|
"match": "(?x)\\G\\s* (@\\S+)",
|
||||||
"name": "keyword.tag.ql"
|
"name": "keyword.tag.ql"
|
||||||
@@ -723,15 +737,48 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"import-directive": {
|
"instantiation-args": {
|
||||||
"end": "(?x)(?:\\b [A-Za-z][0-9A-Za-z_]* (?:(?!(?:[0-9A-Za-z_])))) (?!\\s*(\\.|\\:\\:))",
|
"name": "meta.type.parameters.ql",
|
||||||
"endCaptures": {
|
"patterns": [
|
||||||
"0": {
|
{
|
||||||
|
"include": "#instantiation-args"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#non-context-sensitive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": "(?x)(?:\\b [A-Za-z][0-9A-Za-z_]* (?:(?!(?:[0-9A-Za-z_]))))",
|
||||||
"name": "entity.name.type.namespace.ql"
|
"name": "entity.name.type.namespace.ql"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"begin": "(?x)((?:<))",
|
||||||
|
"beginCaptures": {
|
||||||
|
"1": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#open-angle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
"end": "(?x)((?:>))",
|
||||||
|
"endCaptures": {
|
||||||
|
"1": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#close-angle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"import-directive": {
|
||||||
|
"end": "(?x)(?<!\\bimport)(?<=(?:\\>)|[A-Za-z0-9_]) (?!\\s*(\\.|\\:\\:|\\,|(?:<)))",
|
||||||
"name": "meta.block.import-directive.ql",
|
"name": "meta.block.import-directive.ql",
|
||||||
"patterns": [
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#instantiation-args"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"include": "#non-context-sensitive"
|
"include": "#non-context-sensitive"
|
||||||
},
|
},
|
||||||
@@ -1493,4 +1540,4 @@
|
|||||||
"name": "constant.character.escape.ql"
|
"name": "constant.character.escape.ql"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user