From d9ae0c6d17651322ec33871361fa940f1c7cf5b9 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 20 Feb 2024 12:31:47 +0000 Subject: [PATCH 1/3] Disable the automodel button if there are no methods that can be modeled --- .../src/view/model-editor/LibraryRow.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx index cbe288b39..0c5c8bc4a 100644 --- a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx @@ -14,6 +14,7 @@ import { } from "@vscode/webview-ui-toolkit/react"; import type { ModelEditorViewState } from "../../model-editor/shared/view-state"; import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions"; +import { getCandidates } from "../../model-editor/auto-model"; const LibraryContainer = styled.div` background-color: var(--vscode-peekViewResult-background); @@ -186,6 +187,17 @@ export const LibraryRow = ({ return methods.some((method) => inProgressMethods.has(method.signature)); }, [methods, inProgressMethods]); + const modelWithAIDisabled = useMemo(() => { + return ( + getCandidates( + viewState.mode, + methods, + modeledMethodsMap, + processedByAutoModelMethods, + ).length === 0 + ); + }, [methods, modeledMethodsMap, processedByAutoModelMethods, viewState.mode]); + return ( @@ -205,7 +217,11 @@ export const LibraryRow = ({ {hasUnsavedChanges ? UNSAVED : null} {viewState.showLlmButton && !canStopAutoModeling && ( - +  Model with AI From e78f7e62fb6b17f9af278d488392a7efe43ee03f Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Wed, 21 Feb 2024 11:42:08 +0000 Subject: [PATCH 2/3] Moved logic to get standard pack for variant analysis (#3384) --- .../variant-analysis/code-scanning-pack.ts | 77 +++++++++++++++++++ .../variant-analysis-manager.ts | 69 ++--------------- .../code-scanning-pack.test.ts | 34 ++++++++ .../variant-analysis-manager.test.ts | 37 --------- 4 files changed, 119 insertions(+), 98 deletions(-) create mode 100644 extensions/ql-vscode/src/variant-analysis/code-scanning-pack.ts create mode 100644 extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/code-scanning-pack.test.ts diff --git a/extensions/ql-vscode/src/variant-analysis/code-scanning-pack.ts b/extensions/ql-vscode/src/variant-analysis/code-scanning-pack.ts new file mode 100644 index 000000000..94542b4c3 --- /dev/null +++ b/extensions/ql-vscode/src/variant-analysis/code-scanning-pack.ts @@ -0,0 +1,77 @@ +import { join } from "path"; +import type { BaseLogger } from "../common/logging"; +import type { QueryLanguage } from "../common/query-language"; +import type { CodeQLCliServer } from "../codeql-cli/cli"; +import type { QlPackDetails } from "./ql-pack-details"; +import { getQlPackFilePath } from "../common/ql"; + +export async function resolveCodeScanningQueryPack( + logger: BaseLogger, + cliServer: CodeQLCliServer, + language: QueryLanguage, +): Promise { + // Get pack + void logger.log(`Downloading pack for language: ${language}`); + const packName = `codeql/${language}-queries`; + const packDownloadResult = await cliServer.packDownload([packName]); + const downloadedPack = packDownloadResult.packs[0]; + + const packDir = join( + packDownloadResult.packDir, + downloadedPack.name, + downloadedPack.version, + ); + + // Resolve queries + void logger.log(`Resolving queries for pack: ${packName}`); + const suitePath = join( + packDir, + "codeql-suites", + `${language}-code-scanning.qls`, + ); + const resolvedQueries = await cliServer.resolveQueries(suitePath); + + const problemQueries = await filterToOnlyProblemQueries( + logger, + cliServer, + resolvedQueries, + ); + + if (problemQueries.length === 0) { + throw Error( + `No problem queries found in published query pack: ${packName}.`, + ); + } + + // Return pack details + const qlPackFilePath = await getQlPackFilePath(packDir); + + const qlPackDetails: QlPackDetails = { + queryFiles: problemQueries, + qlPackRootPath: packDir, + qlPackFilePath, + language, + }; + + return qlPackDetails; +} + +async function filterToOnlyProblemQueries( + logger: BaseLogger, + cliServer: CodeQLCliServer, + queries: string[], +): Promise { + const problemQueries: string[] = []; + for (const query of queries) { + const queryMetadata = await cliServer.resolveMetadata(query); + if ( + queryMetadata.kind === "problem" || + queryMetadata.kind === "path-problem" + ) { + problemQueries.push(query); + } else { + void logger.log(`Skipping non-problem query ${query}`); + } + } + return problemQueries; +} diff --git a/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts b/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts index 515551bde..3fc650f9f 100644 --- a/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts +++ b/extensions/ql-vscode/src/variant-analysis/variant-analysis-manager.ts @@ -94,6 +94,7 @@ import { getQlPackFilePath } from "../common/ql"; import { tryGetQueryMetadata } from "../codeql-cli/query-metadata"; import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders"; import { findVariantAnalysisQlPackRoot } from "./ql"; +import { resolveCodeScanningQueryPack } from "./code-scanning-pack"; const maxRetryCount = 3; @@ -219,7 +220,7 @@ export class VariantAnalysisManager public async runVariantAnalysisFromPublishedPack(): Promise { return withProgress(async (progress, token) => { progress({ - maxStep: 8, + maxStep: 7, step: 0, message: "Determining query language", }); @@ -230,53 +231,17 @@ export class VariantAnalysisManager } progress({ - maxStep: 8, - step: 1, - message: "Downloading query pack", - }); - - const packName = `codeql/${language}-queries`; - const packDownloadResult = await this.cliServer.packDownload([packName]); - const downloadedPack = packDownloadResult.packs[0]; - - const packDir = join( - packDownloadResult.packDir, - downloadedPack.name, - downloadedPack.version, - ); - - progress({ - maxStep: 8, + maxStep: 7, step: 2, - message: "Resolving queries in pack", + message: "Downloading query pack and resolving queries", }); - const suitePath = join( - packDir, - "codeql-suites", - `${language}-code-scanning.qls`, - ); - const resolvedQueries = await this.cliServer.resolveQueries(suitePath); - - const problemQueries = - await this.filterToOnlyProblemQueries(resolvedQueries); - - if (problemQueries.length === 0) { - void this.app.logger.showErrorMessage( - `Unable to trigger variant analysis. No problem queries found in published query pack: ${packName}.`, - ); - return; - } - - const qlPackFilePath = await getQlPackFilePath(packDir); - // Build up details to pass to the functions that run the variant analysis. - const qlPackDetails: QlPackDetails = { - queryFiles: problemQueries, - qlPackRootPath: packDir, - qlPackFilePath, + const qlPackDetails = await resolveCodeScanningQueryPack( + this.app.logger, + this.cliServer, language, - }; + ); await this.runVariantAnalysis( qlPackDetails, @@ -291,24 +256,6 @@ export class VariantAnalysisManager }); } - private async filterToOnlyProblemQueries( - queries: string[], - ): Promise { - const problemQueries: string[] = []; - for (const query of queries) { - const queryMetadata = await this.cliServer.resolveMetadata(query); - if ( - queryMetadata.kind === "problem" || - queryMetadata.kind === "path-problem" - ) { - problemQueries.push(query); - } else { - void this.app.logger.log(`Skipping non-problem query ${query}`); - } - } - return problemQueries; - } - private async runVariantAnalysisCommand(queryFiles: Uri[]): Promise { if (queryFiles.length === 0) { throw new Error("Please select a .ql file to run as a variant analysis"); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/code-scanning-pack.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/code-scanning-pack.test.ts new file mode 100644 index 000000000..8f3836f03 --- /dev/null +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/code-scanning-pack.test.ts @@ -0,0 +1,34 @@ +import type { CodeQLCliServer } from "../../../../src/codeql-cli/cli"; +import type { App } from "../../../../src/common/app"; +import { QueryLanguage } from "../../../../src/common/query-language"; +import { ExtensionApp } from "../../../../src/common/vscode/vscode-app"; +import { resolveCodeScanningQueryPack } from "../../../../src/variant-analysis/code-scanning-pack"; +import { getActivatedExtension } from "../../global.helper"; + +describe("Code Scanning pack", () => { + let cli: CodeQLCliServer; + let app: App; + + beforeEach(async () => { + const extension = await getActivatedExtension(); + cli = extension.cliServer; + app = new ExtensionApp(extension.ctx); + }); + + it("should download pack for correct language and identify problem queries", async () => { + const pack = await resolveCodeScanningQueryPack( + app.logger, + cli, + QueryLanguage.Javascript, + ); + // Should include queries. Just check that at least one known query exists. + // It doesn't particularly matter which query we check for. + expect( + pack.queryFiles.some((q) => q.includes("PostMessageStar.ql")), + ).toBeTruthy(); + // Should not include non-problem queries. + expect( + pack.queryFiles.some((q) => q.includes("LinesOfCode.ql")), + ).toBeFalsy(); + }); +}); diff --git a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts index e8539c3eb..fdb9e6356 100644 --- a/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/cli-integration/variant-analysis/variant-analysis-manager.test.ts @@ -477,41 +477,4 @@ describe("Variant Analysis Manager", () => { } } }); - - describe("runVariantAnalysisFromPublishedPack", () => { - // Temporarily disabling this until we add a way to receive multiple queries in the - // runVariantAnalysis function. - it("should download pack for correct language and identify problem queries", async () => { - const showQuickPickSpy = jest - .spyOn(window, "showQuickPick") - .mockResolvedValue( - mockedQuickPickItem({ - label: "JavaScript", - description: "javascript", - language: "javascript", - }), - ); - - const runVariantAnalysisMock = jest.fn(); - variantAnalysisManager.runVariantAnalysis = runVariantAnalysisMock; - - await variantAnalysisManager.runVariantAnalysisFromPublishedPack(); - - expect(showQuickPickSpy).toHaveBeenCalledTimes(1); - expect(runVariantAnalysisMock).toHaveBeenCalledTimes(1); - - console.log(runVariantAnalysisMock.mock.calls[0][0]); - const queries: string[] = - runVariantAnalysisMock.mock.calls[0][0].queryFiles; - // Should include queries. Just check that at least one known query exists. - // It doesn't particularly matter which query we check for. - expect( - queries.find((q) => q.includes("PostMessageStar.ql")), - ).toBeDefined(); - // Should not include non-problem queries. - expect( - queries.find((q) => q.includes("LinesOfCode.ql")), - ).not.toBeDefined(); - }); - }); }); From 2fdafaf616ad5553d226d89f82d99ac9fd4c046c Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 21 Feb 2024 12:29:43 +0000 Subject: [PATCH 3/3] Move getCandidates to the shared directory --- .../ql-vscode/src/model-editor/auto-model.ts | 53 -------- .../src/model-editor/auto-modeler.ts | 3 +- .../shared/auto-model-candidates.ts | 55 ++++++++ .../src/view/model-editor/LibraryRow.tsx | 2 +- .../model-editor/auto-model.test.ts | 119 ----------------- .../shared/auto-model-candidates.test.ts | 120 ++++++++++++++++++ 6 files changed, 178 insertions(+), 174 deletions(-) create mode 100644 extensions/ql-vscode/src/model-editor/shared/auto-model-candidates.ts create mode 100644 extensions/ql-vscode/test/unit-tests/model-editor/shared/auto-model-candidates.test.ts diff --git a/extensions/ql-vscode/src/model-editor/auto-model.ts b/extensions/ql-vscode/src/model-editor/auto-model.ts index 42020db13..e0099fae5 100644 --- a/extensions/ql-vscode/src/model-editor/auto-model.ts +++ b/extensions/ql-vscode/src/model-editor/auto-model.ts @@ -5,59 +5,6 @@ import type { AutoModelQueriesResult } from "./auto-model-codeml-queries"; import { assertNever } from "../common/helpers-pure"; import type { Log } from "sarif"; import { gzipEncode } from "../common/zlib"; -import type { Method, MethodSignature } from "./method"; -import type { ModeledMethod } from "./modeled-method"; -import { groupMethods, sortGroupNames, sortMethods } from "./shared/sorting"; - -/** - * Return the candidates that the model should be run on. This includes limiting the number of - * candidates to the candidate limit and filtering out anything that is already modeled and respecting - * the order in the UI. - * @param mode Whether it is application or framework mode. - * @param methods all methods. - * @param modeledMethodsBySignature the currently modeled methods. - * @returns list of modeled methods that are candidates for modeling. - */ -export function getCandidates( - mode: Mode, - methods: readonly Method[], - modeledMethodsBySignature: Record, - processedByAutoModelMethods: Set, -): MethodSignature[] { - // Filter out any methods already processed by auto-model - methods = methods.filter( - (m) => !processedByAutoModelMethods.has(m.signature), - ); - - // Sort the same way as the UI so we send the first ones listed in the UI first - const grouped = groupMethods(methods, mode); - const sortedGroupNames = sortGroupNames(grouped); - const sortedMethods = sortedGroupNames.flatMap((name) => - sortMethods(grouped[name]), - ); - - const candidates: MethodSignature[] = []; - - for (const method of sortedMethods) { - const modeledMethods: ModeledMethod[] = [ - ...(modeledMethodsBySignature[method.signature] ?? []), - ]; - - // Anything that is modeled is not a candidate - if (modeledMethods.some((m) => m.type !== "none")) { - continue; - } - - // A method that is supported is modeled outside of the model file, so it is not a candidate. - if (method.supported) { - continue; - } - - // The rest are candidates - candidates.push(method); - } - return candidates; -} /** * Encode a SARIF log to the format expected by the server: JSON, GZIP-compressed, base64-encoded diff --git a/extensions/ql-vscode/src/model-editor/auto-modeler.ts b/extensions/ql-vscode/src/model-editor/auto-modeler.ts index 96695f669..e3f62ba3b 100644 --- a/extensions/ql-vscode/src/model-editor/auto-modeler.ts +++ b/extensions/ql-vscode/src/model-editor/auto-modeler.ts @@ -3,7 +3,8 @@ import type { ModeledMethod } from "./modeled-method"; import { load as loadYaml } from "js-yaml"; import type { ProgressCallback } from "../common/vscode/progress"; import { withProgress } from "../common/vscode/progress"; -import { createAutoModelRequest, getCandidates } from "./auto-model"; +import { createAutoModelRequest } from "./auto-model"; +import { getCandidates } from "./shared/auto-model-candidates"; import { runAutoModelQueries } from "./auto-model-codeml-queries"; import { loadDataExtensionYaml } from "./yaml"; import type { ModelRequest, ModelResponse } from "./auto-model-api"; diff --git a/extensions/ql-vscode/src/model-editor/shared/auto-model-candidates.ts b/extensions/ql-vscode/src/model-editor/shared/auto-model-candidates.ts new file mode 100644 index 000000000..bfc6e5a82 --- /dev/null +++ b/extensions/ql-vscode/src/model-editor/shared/auto-model-candidates.ts @@ -0,0 +1,55 @@ +import type { Method, MethodSignature } from "../method"; +import type { ModeledMethod } from "../modeled-method"; +import type { Mode } from "./mode"; +import { groupMethods, sortGroupNames, sortMethods } from "./sorting"; + +/** + * Return the candidates that the model should be run on. This includes limiting the number of + * candidates to the candidate limit and filtering out anything that is already modeled and respecting + * the order in the UI. + * @param mode Whether it is application or framework mode. + * @param methods all methods. + * @param modeledMethodsBySignature the currently modeled methods. + * @returns list of modeled methods that are candidates for modeling. + */ + +export function getCandidates( + mode: Mode, + methods: readonly Method[], + modeledMethodsBySignature: Record, + processedByAutoModelMethods: Set, +): MethodSignature[] { + // Filter out any methods already processed by auto-model + methods = methods.filter( + (m) => !processedByAutoModelMethods.has(m.signature), + ); + + // Sort the same way as the UI so we send the first ones listed in the UI first + const grouped = groupMethods(methods, mode); + const sortedGroupNames = sortGroupNames(grouped); + const sortedMethods = sortedGroupNames.flatMap((name) => + sortMethods(grouped[name]), + ); + + const candidates: MethodSignature[] = []; + + for (const method of sortedMethods) { + const modeledMethods: ModeledMethod[] = [ + ...(modeledMethodsBySignature[method.signature] ?? []), + ]; + + // Anything that is modeled is not a candidate + if (modeledMethods.some((m) => m.type !== "none")) { + continue; + } + + // A method that is supported is modeled outside of the model file, so it is not a candidate. + if (method.supported) { + continue; + } + + // The rest are candidates + candidates.push(method); + } + return candidates; +} diff --git a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx index 0c5c8bc4a..40fe4359b 100644 --- a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx @@ -14,7 +14,7 @@ import { } from "@vscode/webview-ui-toolkit/react"; import type { ModelEditorViewState } from "../../model-editor/shared/view-state"; import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions"; -import { getCandidates } from "../../model-editor/auto-model"; +import { getCandidates } from "../../model-editor/shared/auto-model-candidates"; const LibraryContainer = styled.div` background-color: var(--vscode-peekViewResult-background); diff --git a/extensions/ql-vscode/test/unit-tests/model-editor/auto-model.test.ts b/extensions/ql-vscode/test/unit-tests/model-editor/auto-model.test.ts index 4693f349a..50e67fffd 100644 --- a/extensions/ql-vscode/test/unit-tests/model-editor/auto-model.test.ts +++ b/extensions/ql-vscode/test/unit-tests/model-editor/auto-model.test.ts @@ -1,16 +1,12 @@ import { createAutoModelRequest, encodeSarif, - getCandidates, } from "../../../src/model-editor/auto-model"; import { Mode } from "../../../src/model-editor/shared/mode"; import { AutomodelMode } from "../../../src/model-editor/auto-model-api"; import type { AutoModelQueriesResult } from "../../../src/model-editor/auto-model-codeml-queries"; import type { Log } from "sarif"; import { gzipDecode } from "../../../src/common/zlib"; -import type { Method } from "../../../src/model-editor/method"; -import { EndpointType } from "../../../src/model-editor/method"; -import type { ModeledMethod } from "../../../src/model-editor/modeled-method"; describe("createAutoModelRequest", () => { const createSarifLog = (queryId: string): Log => { @@ -84,118 +80,3 @@ describe("createAutoModelRequest", () => { expect(parsed).toEqual(result.candidates); }); }); - -describe("getCandidates", () => { - it("doesn't return methods that are already modelled", () => { - const methods: Method[] = [ - { - library: "my.jar", - signature: "org.my.A#x()", - endpointType: EndpointType.Method, - packageName: "org.my", - typeName: "A", - methodName: "x", - methodParameters: "()", - supported: false, - supportedType: "none", - usages: [], - }, - ]; - const modeledMethods: Record = { - "org.my.A#x()": [ - { - type: "neutral", - kind: "sink", - provenance: "manual", - signature: "org.my.A#x()", - endpointType: EndpointType.Method, - packageName: "org.my", - typeName: "A", - methodName: "x", - methodParameters: "()", - }, - ], - }; - const candidates = getCandidates( - Mode.Application, - methods, - modeledMethods, - new Set(), - ); - expect(candidates.length).toEqual(0); - }); - - it("doesn't return methods that are supported from other sources", () => { - const methods: Method[] = [ - { - library: "my.jar", - signature: "org.my.A#x()", - endpointType: EndpointType.Method, - packageName: "org.my", - typeName: "A", - methodName: "x", - methodParameters: "()", - supported: true, - supportedType: "none", - usages: [], - }, - ]; - const modeledMethods = {}; - const candidates = getCandidates( - Mode.Application, - methods, - modeledMethods, - new Set(), - ); - expect(candidates.length).toEqual(0); - }); - - it("doesn't return methods that are already processed by auto model", () => { - const methods: Method[] = [ - { - library: "my.jar", - signature: "org.my.A#x()", - endpointType: EndpointType.Method, - packageName: "org.my", - typeName: "A", - methodName: "x", - methodParameters: "()", - supported: false, - supportedType: "none", - usages: [], - }, - ]; - const modeledMethods = {}; - const candidates = getCandidates( - Mode.Application, - methods, - modeledMethods, - new Set(["org.my.A#x()"]), - ); - expect(candidates.length).toEqual(0); - }); - - it("returns methods that are neither modeled nor supported from other sources", () => { - const methods: Method[] = []; - methods.push({ - library: "my.jar", - signature: "org.my.A#x()", - endpointType: EndpointType.Method, - packageName: "org.my", - typeName: "A", - methodName: "x", - methodParameters: "()", - supported: false, - supportedType: "none", - usages: [], - }); - const modeledMethods = {}; - const candidates = getCandidates( - Mode.Application, - methods, - modeledMethods, - new Set(), - ); - expect(candidates.length).toEqual(1); - }); -}); diff --git a/extensions/ql-vscode/test/unit-tests/model-editor/shared/auto-model-candidates.test.ts b/extensions/ql-vscode/test/unit-tests/model-editor/shared/auto-model-candidates.test.ts new file mode 100644 index 000000000..c89e604c5 --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/model-editor/shared/auto-model-candidates.test.ts @@ -0,0 +1,120 @@ +import type { Method } from "../../../../src/model-editor/method"; +import { EndpointType } from "../../../../src/model-editor/method"; +import type { ModeledMethod } from "../../../../src/model-editor/modeled-method"; +import { getCandidates } from "../../../../src/model-editor/shared/auto-model-candidates"; +import { Mode } from "../../../../src/model-editor/shared/mode"; + +describe("getCandidates", () => { + it("doesn't return methods that are already modelled", () => { + const methods: Method[] = [ + { + library: "my.jar", + signature: "org.my.A#x()", + endpointType: EndpointType.Method, + packageName: "org.my", + typeName: "A", + methodName: "x", + methodParameters: "()", + supported: false, + supportedType: "none", + usages: [], + }, + ]; + const modeledMethods: Record = { + "org.my.A#x()": [ + { + type: "neutral", + kind: "sink", + provenance: "manual", + signature: "org.my.A#x()", + endpointType: EndpointType.Method, + packageName: "org.my", + typeName: "A", + methodName: "x", + methodParameters: "()", + }, + ], + }; + const candidates = getCandidates( + Mode.Application, + methods, + modeledMethods, + new Set(), + ); + expect(candidates.length).toEqual(0); + }); + + it("doesn't return methods that are supported from other sources", () => { + const methods: Method[] = [ + { + library: "my.jar", + signature: "org.my.A#x()", + endpointType: EndpointType.Method, + packageName: "org.my", + typeName: "A", + methodName: "x", + methodParameters: "()", + supported: true, + supportedType: "none", + usages: [], + }, + ]; + const modeledMethods = {}; + const candidates = getCandidates( + Mode.Application, + methods, + modeledMethods, + new Set(), + ); + expect(candidates.length).toEqual(0); + }); + + it("doesn't return methods that are already processed by auto model", () => { + const methods: Method[] = [ + { + library: "my.jar", + signature: "org.my.A#x()", + endpointType: EndpointType.Method, + packageName: "org.my", + typeName: "A", + methodName: "x", + methodParameters: "()", + supported: false, + supportedType: "none", + usages: [], + }, + ]; + const modeledMethods = {}; + const candidates = getCandidates( + Mode.Application, + methods, + modeledMethods, + new Set(["org.my.A#x()"]), + ); + expect(candidates.length).toEqual(0); + }); + + it("returns methods that are neither modeled nor supported from other sources", () => { + const methods: Method[] = []; + methods.push({ + library: "my.jar", + signature: "org.my.A#x()", + endpointType: EndpointType.Method, + packageName: "org.my", + typeName: "A", + methodName: "x", + methodParameters: "()", + supported: false, + supportedType: "none", + usages: [], + }); + const modeledMethods = {}; + const candidates = getCandidates( + Mode.Application, + methods, + modeledMethods, + new Set(), + ); + expect(candidates.length).toEqual(1); + }); +});