diff --git a/extensions/ql-vscode/src/data-extensions-editor/bqrs.ts b/extensions/ql-vscode/src/data-extensions-editor/bqrs.ts index ce8f6eb57..0adf4009d 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/bqrs.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/bqrs.ts @@ -5,6 +5,7 @@ import { ExternalApiUsage, } from "./external-api-usage"; import { ModeledMethodType } from "./modeled-method"; +import { parseLibraryFilename } from "./library"; export function decodeBqrsToExternalApiUsages( chunk: DecodedBqrsChunk, @@ -15,7 +16,8 @@ export function decodeBqrsToExternalApiUsages( const usage = tuple[0] as Call; const signature = tuple[1] as string; const supported = (tuple[2] as string) === "true"; - const library = tuple[4] as string; + let library = tuple[4] as string; + let libraryVersion: string | undefined = tuple[5] as string; const type = tuple[6] as ModeledMethodType; const classification = tuple[8] as CallClassification; @@ -37,9 +39,25 @@ export function decodeBqrsToExternalApiUsages( methodDeclaration.indexOf("("), ); + // For Java, we'll always get back a .jar file, and the library version may be bad because not all library authors + // properly specify the version. Therefore, we'll always try to parse the name and version from the library filename + // for Java. + if (library.endsWith(".jar") || libraryVersion === "") { + const { name, version } = parseLibraryFilename(library); + library = name; + if (version) { + libraryVersion = version; + } + } + + if (libraryVersion === "") { + libraryVersion = undefined; + } + if (!methodsByApiName.has(signature)) { methodsByApiName.set(signature, { library, + libraryVersion, signature, packageName, typeName, diff --git a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage.ts b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage.ts index d65efbe04..ea0c1361d 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/external-api-usage.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/external-api-usage.ts @@ -18,6 +18,10 @@ export type Usage = Call & { }; export interface MethodSignature { + /** + * Contains the version of the library if it can be determined by CodeQL, e.g. `4.2.2.2` + */ + libraryVersion?: string; /** * A unique signature that can be used to identify this external API usage. * diff --git a/extensions/ql-vscode/src/data-extensions-editor/library.ts b/extensions/ql-vscode/src/data-extensions-editor/library.ts new file mode 100644 index 000000000..292b899cb --- /dev/null +++ b/extensions/ql-vscode/src/data-extensions-editor/library.ts @@ -0,0 +1,58 @@ +import { basename, extname } from "../common/path"; + +// From the semver package using +// const { re, t } = require("semver/internal/re"); +// console.log(re[t.LOOSE]); +// Modifications: +// - Added version named group which does not capture the v prefix +// - Removed the ^ and $ anchors +// - Made the minor and patch versions optional +// - Added a hyphen to the start of the version +// - Added a dot as a valid separator between the version and the label +// - Made the patch version optional even if a label is given +// This will match any semver string at the end of a larger string +const semverRegex = + /-[v=\s]*(?([0-9]+)(\.([0-9]+)(?:(\.([0-9]+))?(?:[-.]?((?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*)(?:\.(?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?)?)?)/g; + +export interface Library { + name: string; + version?: string; +} + +export function parseLibraryFilename(filename: string): Library { + let libraryName = basename(filename); + const extension = extname(libraryName); + libraryName = libraryName.slice(0, -extension.length); + + let libraryVersion: string | undefined; + + let match: RegExpMatchArray | null = null; + + // Reset the regex + semverRegex.lastIndex = 0; + + // Find the last occurence of the regex within the library name + // eslint-disable-next-line no-constant-condition + while (true) { + const currentMatch = semverRegex.exec(libraryName); + if (currentMatch === null) { + break; + } + + match = currentMatch; + } + + if (match?.groups) { + libraryVersion = match.groups?.version; + // Remove everything after the start of the match + libraryName = libraryName.slice(0, match.index); + } + + // Remove any leading or trailing hyphens or dots + libraryName = libraryName.replaceAll(/^[.-]+|[.-]+$/g, ""); + + return { + name: libraryName, + version: libraryVersion, + }; +} diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts index 8738c21e5..e882f84be 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts @@ -30,8 +30,8 @@ where usage = aUsage(api) and type = supportedType(api) and classification = methodClassification(usage) -select usage, apiName, supported.toString(), "supported", api.getFile().getBaseName(), "library", - type, "type", classification, "classification" +select usage, apiName, supported.toString(), "supported", api.dllName(), api.dllVersion(), type, + "type", classification, "classification" `, frameworkModeQuery: `/** * @name Public methods @@ -111,7 +111,7 @@ class CallableMethod extends DotNet::Declaration { bindingset[this] private string getSignature() { result = - nestedName(this.getDeclaringType().getUnboundDeclaration()) + "." + this.getName() + "(" + + nestedName(this.getDeclaringType().getUnboundDeclaration()) + "#" + this.getName() + "(" + parameterQualifiedTypeNamesToString(this) + ")" } @@ -125,7 +125,23 @@ class CallableMethod extends DotNet::Declaration { * Gets the namespace and signature of this API. */ bindingset[this] - string getApiName() { result = this.getNamespace() + "#" + this.getSignature() } + string getApiName() { result = this.getNamespace() + "." + this.getSignature() } + + private string getDllName() { result = this.getLocation().(Assembly).getName() } + + private string getDllVersion() { result = this.getLocation().(Assembly).getVersion().toString() } + + string dllName() { + result = this.getDllName() + or + not exists(this.getDllName()) and result = this.getFile().getBaseName() + } + + string dllVersion() { + result = this.getDllVersion() + or + not exists(this.getDllVersion()) and result = "" + } /** Gets a node that is an input to a call to this API. */ private ArgumentNode getAnInput() { @@ -195,7 +211,7 @@ string supportedType(CallableMethod method) { or method.isNeutral() and result = "neutral" or - not method.isSupported() and result = "none" + not method.isSupported() and result = "" } string methodClassification(Call method) { diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts index 58812dda1..d1ecfad93 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/java.ts @@ -27,8 +27,8 @@ where usage = aUsage(externalApi) and type = supportedType(externalApi) and classification = methodClassification(usage) -select usage, apiName, supported.toString(), "supported", externalApi.jarContainer(), "library", - type, "type", classification, "classification" +select usage, apiName, supported.toString(), "supported", externalApi.jarContainer(), + externalApi.jarVersion(), type, "type", classification, "classification" `, frameworkModeQuery: `/** * @name Public methods @@ -75,7 +75,7 @@ private predicate isUninteresting(Callable c) { /** * A callable method from either the Standard Library, a 3rd party library or from the source. */ -class CallableMethod extends Method { +class CallableMethod extends Callable { CallableMethod() { not isUninteresting(this) } /** @@ -91,6 +91,10 @@ class CallableMethod extends Method { result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName() } + private string getJarVersion() { + result = this.getCompilationUnit().getParentContainer*().(JarFile).getSpecificationVersion() + } + /** * Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules. */ @@ -100,6 +104,15 @@ class CallableMethod extends Method { not exists(this.getJarName()) and result = "rt.jar" } + /** + * Gets the version of the JAR file containing this API. Empty if no version is found in the JAR. + */ + string jarVersion() { + result = this.getJarVersion() + or + not exists(this.getJarVersion()) and result = "" + } + /** Gets a node that is an input to a call to this API. */ private DataFlow::Node getAnInput() { exists(Call call | call.getCallee().getSourceDeclaration() = this | @@ -160,7 +173,7 @@ string supportedType(CallableMethod method) { or method.isNeutral() and result = "neutral" or - not method.isSupported() and result = "none" + not method.isSupported() and result = "" } string methodClassification(Call method) { diff --git a/extensions/ql-vscode/src/data-extensions-editor/queries/query.ts b/extensions/ql-vscode/src/data-extensions-editor/queries/query.ts index 8aa96d77b..e78e72ed6 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/queries/query.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/queries/query.ts @@ -8,7 +8,7 @@ export type Query = { * - supported: whether the external API is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query. * - "supported": a string literal. This is required to make the query a valid problem query. * - libraryName: the name of the library that contains the external API. This is a string and usually the basename of a file. - * - "library": a string literal. This is required to make the query a valid problem query. + * - libraryVersion: the version of the library that contains the external API. This is a string and can be empty if the version cannot be determined. * - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral" * - "type": a string literal. This is required to make the query a valid problem query. * - classification: the classification of the use of the method, either "source", "test", "generated", or "unknown" @@ -25,7 +25,7 @@ export type Query = { * - supported: whether this method is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query. * - "supported": a string literal. This is required to make the query a valid problem query. * - libraryName: an arbitrary string. This is required to make it match the structure of the application query. - * - "library": a string literal. This is required to make the query a valid problem query. + * - libraryVersion: an arbitrary string. This is required to make it match the structure of the application query. * - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral" * - "type": a string literal. This is required to make the query a valid problem query. * - "unknown": a string literal. This is required to make it match the structure of the application query. diff --git a/extensions/ql-vscode/src/data-extensions-editor/yaml.ts b/extensions/ql-vscode/src/data-extensions-editor/yaml.ts index aa67245c9..04d85c11e 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/yaml.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/yaml.ts @@ -1,6 +1,5 @@ import Ajv from "ajv"; -import { basename, extname } from "../common/path"; import { ExternalApiUsage } from "./external-api-usage"; import { ModeledMethod, ModeledMethodType } from "./modeled-method"; import { @@ -122,29 +121,12 @@ export function createDataExtensionYamlsForFrameworkMode( }; } -// From the semver package using -// const { re, t } = require("semver/internal/re"); -// console.log(re[t.LOOSE]); -// Modified to remove the ^ and $ anchors -// This will match any semver string at the end of a larger string -const semverRegex = - /[v=\s]*([0-9]+)\.([0-9]+)\.([0-9]+)(?:-?((?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*)(?:\.(?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?/; - export function createFilenameForLibrary( library: string, prefix = "models/", suffix = ".model", ) { - let libraryName = basename(library); - const extension = extname(libraryName); - libraryName = libraryName.slice(0, -extension.length); - - const match = semverRegex.exec(libraryName); - - if (match !== null) { - // Remove everything after the start of the match - libraryName = libraryName.slice(0, match.index); - } + let libraryName = library; // Lowercase everything libraryName = libraryName.toLowerCase(); diff --git a/extensions/ql-vscode/src/stories/data-extensions-editor/DataExtensionsEditor.stories.tsx b/extensions/ql-vscode/src/stories/data-extensions-editor/DataExtensionsEditor.stories.tsx index 75efd4aa7..9daf90e64 100644 --- a/extensions/ql-vscode/src/stories/data-extensions-editor/DataExtensionsEditor.stories.tsx +++ b/extensions/ql-vscode/src/stories/data-extensions-editor/DataExtensionsEditor.stories.tsx @@ -33,7 +33,8 @@ DataExtensionsEditor.args = { }, initialExternalApiUsages: [ { - library: "sql2o-1.6.0.jar", + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Connection#createQuery(String)", packageName: "org.sql2o", typeName: "Connection", @@ -54,7 +55,8 @@ DataExtensionsEditor.args = { }), }, { - library: "sql2o-1.6.0.jar", + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Query#executeScalar(Class)", packageName: "org.sql2o", typeName: "Query", @@ -75,7 +77,8 @@ DataExtensionsEditor.args = { }), }, { - library: "sql2o-1.6.0.jar", + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Sql2o#open()", packageName: "org.sql2o", typeName: "Sql2o", @@ -96,7 +99,7 @@ DataExtensionsEditor.args = { }), }, { - library: "rt.jar", + library: "rt", signature: "java.io.PrintStream#println(String)", packageName: "java.io", typeName: "PrintStream", @@ -130,7 +133,8 @@ DataExtensionsEditor.args = { ], }, { - library: "spring-boot-3.0.2.jar", + library: "spring-boot", + libraryVersion: "3.0.2", signature: "org.springframework.boot.SpringApplication#run(Class,String[])", packageName: "org.springframework.boot", @@ -152,7 +156,8 @@ DataExtensionsEditor.args = { }), }, { - library: "sql2o-1.6.0.jar", + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", packageName: "org.sql2o", typeName: "Sql2o", @@ -173,7 +178,8 @@ DataExtensionsEditor.args = { }), }, { - library: "sql2o-1.6.0.jar", + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Sql2o#Sql2o(String)", packageName: "org.sql2o", typeName: "Sql2o", diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/LibraryRow.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/LibraryRow.tsx index 48d8deb65..1500ff278 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/LibraryRow.tsx +++ b/extensions/ql-vscode/src/view/data-extensions-editor/LibraryRow.tsx @@ -68,6 +68,7 @@ const ButtonsContainer = styled.div` type Props = { title: string; + libraryVersion?: string; externalApiUsages: ExternalApiUsage[]; modeledMethods: Record; viewState: DataExtensionEditorViewState; @@ -91,6 +92,7 @@ type Props = { export const LibraryRow = ({ title, + libraryVersion, externalApiUsages, modeledMethods, viewState, @@ -158,7 +160,10 @@ export const LibraryRow = ({ )} - {title} + + {title} + {libraryVersion && <>@{libraryVersion}} + {percentFormatter.format(modeledPercentage / 100)} modeled diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx index 9b1bd6d2d..bbc9746f2 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx +++ b/extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx @@ -271,8 +271,10 @@ function UnmodelableMethodRow(props: Props) { function ExternalApiUsageName(props: { externalApiUsage: ExternalApiUsage }) { return ( - {props.externalApiUsage.packageName}.{props.externalApiUsage.typeName}. - {props.externalApiUsage.methodName} + {props.externalApiUsage.packageName && ( + <>{props.externalApiUsage.packageName}. + )} + {props.externalApiUsage.typeName}.{props.externalApiUsage.methodName} {props.externalApiUsage.methodParameters} ); diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/ModeledMethodsList.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/ModeledMethodsList.tsx index a70cbc84f..376edef32 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/ModeledMethodsList.tsx +++ b/extensions/ql-vscode/src/view/data-extensions-editor/ModeledMethodsList.tsx @@ -3,6 +3,7 @@ import { useMemo } from "react"; import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage"; import { ModeledMethod } from "../../data-extensions-editor/modeled-method"; import { LibraryRow } from "./LibraryRow"; +import { Mode } from "../../data-extensions-editor/shared/mode"; import { groupMethods, sortGroupNames, @@ -31,6 +32,10 @@ type Props = { onGenerateFromSourceClick: () => void; }; +const libraryNameOverrides: Record = { + rt: "Java Runtime", +}; + export const ModeledMethodsList = ({ externalApiUsages, unsavedModels, @@ -46,6 +51,24 @@ export const ModeledMethodsList = ({ [externalApiUsages, viewState.mode], ); + const libraryVersions = useMemo(() => { + if (viewState.mode !== Mode.Application) { + return {}; + } + + const libraryVersions: Record = {}; + + for (const externalApiUsage of externalApiUsages) { + const { library, libraryVersion } = externalApiUsage; + + if (library && libraryVersion) { + libraryVersions[library] = libraryVersion; + } + } + + return libraryVersions; + }, [externalApiUsages, viewState.mode]); + const sortedGroupNames = useMemo(() => sortGroupNames(grouped), [grouped]); return ( @@ -53,7 +76,8 @@ export const ModeledMethodsList = ({ {sortedGroupNames.map((libraryName) => ( { const chunk: DecodedBqrsChunk = { @@ -8,6 +9,12 @@ describe("decodeBqrsToExternalApiUsages", () => { { name: "apiName", kind: "String" }, { kind: "String" }, { kind: "String" }, + { kind: "String" }, + { kind: "String" }, + { name: "type", kind: "String" }, + { kind: "String" }, + { name: "classification", kind: "String" }, + { kind: "String" }, ], tuples: [ [ @@ -24,6 +31,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "java.io.PrintStream#println(String)", "true", "supported", + "rt.jar", + "", + "sink", + "type", + "source", + "classification", ], [ { @@ -39,6 +52,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.springframework.boot.SpringApplication#run(Class,String[])", "false", "supported", + "spring-boot-3.0.2.jar", + "", + "none", + "type", + "source", + "classification", ], [ { @@ -54,6 +73,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.sql2o.Connection#createQuery(String)", "true", "supported", + "sql2o-1.6.0.jar", + "", + "sink", + "type", + "source", + "classification", ], [ { @@ -69,6 +94,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.sql2o.Connection#createQuery(String)", "true", "supported", + "sql2o-1.6.0.jar", + "", + "sink", + "type", + "source", + "classification", ], [ { @@ -84,6 +115,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.sql2o.Query#executeScalar(Class)", "true", "supported", + "sql2o-1.6.0.jar", + "", + "sink", + "type", + "source", + "classification", ], [ { @@ -99,6 +136,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.sql2o.Query#executeScalar(Class)", "true", "supported", + "sql2o-1.6.0.jar", + "", + "sink", + "type", + "source", + "classification", ], [ { @@ -114,6 +157,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.sql2o.Sql2o#open()", "true", "supported", + "sql2o-1.6.0.jar", + "", + "sink", + "type", + "source", + "classification", ], [ { @@ -129,6 +178,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.sql2o.Sql2o#open()", "true", "supported", + "sql2o-1.6.0.jar", + "", + "sink", + "type", + "source", + "classification", ], [ { @@ -144,6 +199,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.sql2o.Sql2o#Sql2o(String,String,String)", "true", "supported", + "sql2o-1.6.0.jar", + "", + "sink", + "type", + "source", + "classification", ], [ { @@ -159,6 +220,12 @@ describe("decodeBqrsToExternalApiUsages", () => { "org.sql2o.Sql2o#Sql2o(String)", "true", "supported", + "sql2o-1.6.0.jar", + "", + "sink", + "type", + "source", + "classification", ], ], }; @@ -169,12 +236,15 @@ describe("decodeBqrsToExternalApiUsages", () => { // - Sorting the array of usages is guaranteed to be a stable sort expect(decodeBqrsToExternalApiUsages(chunk)).toEqual([ { + library: "rt", + libraryVersion: undefined, signature: "java.io.PrintStream#println(String)", packageName: "java.io", typeName: "PrintStream", methodName: "println", methodParameters: "(String)", supported: true, + supportedType: "sink", usages: [ { label: "println(...)", @@ -185,10 +255,13 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 29, endColumn: 49, }, + classification: CallClassification.Source, }, ], }, { + library: "spring-boot", + libraryVersion: "3.0.2", signature: "org.springframework.boot.SpringApplication#run(Class,String[])", packageName: "org.springframework.boot", @@ -196,6 +269,7 @@ describe("decodeBqrsToExternalApiUsages", () => { methodName: "run", methodParameters: "(Class,String[])", supported: false, + supportedType: "none", usages: [ { label: "run(...)", @@ -206,16 +280,20 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 9, endColumn: 66, }, + classification: CallClassification.Source, }, ], }, { + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Connection#createQuery(String)", packageName: "org.sql2o", typeName: "Connection", methodName: "createQuery", methodParameters: "(String)", supported: true, + supportedType: "sink", usages: [ { label: "createQuery(...)", @@ -226,6 +304,7 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 15, endColumn: 56, }, + classification: CallClassification.Source, }, { label: "createQuery(...)", @@ -236,16 +315,20 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 26, endColumn: 39, }, + classification: CallClassification.Source, }, ], }, { + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Query#executeScalar(Class)", packageName: "org.sql2o", typeName: "Query", methodName: "executeScalar", methodParameters: "(Class)", supported: true, + supportedType: "sink", usages: [ { label: "executeScalar(...)", @@ -256,6 +339,7 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 15, endColumn: 85, }, + classification: CallClassification.Source, }, { label: "executeScalar(...)", @@ -266,16 +350,20 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 26, endColumn: 68, }, + classification: CallClassification.Source, }, ], }, { + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Sql2o#open()", packageName: "org.sql2o", typeName: "Sql2o", methodName: "open", methodParameters: "()", supported: true, + supportedType: "sink", usages: [ { label: "open(...)", @@ -286,6 +374,7 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 14, endColumn: 35, }, + classification: CallClassification.Source, }, { label: "open(...)", @@ -296,16 +385,20 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 25, endColumn: 35, }, + classification: CallClassification.Source, }, ], }, { + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", packageName: "org.sql2o", typeName: "Sql2o", methodName: "Sql2o", methodParameters: "(String,String,String)", supported: true, + supportedType: "sink", usages: [ { label: "new Sql2o(...)", @@ -316,16 +409,20 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 10, endColumn: 88, }, + classification: CallClassification.Source, }, ], }, { + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Sql2o#Sql2o(String)", packageName: "org.sql2o", typeName: "Sql2o", methodName: "Sql2o", methodParameters: "(String)", supported: true, + supportedType: "sink", usages: [ { label: "new Sql2o(...)", @@ -336,6 +433,7 @@ describe("decodeBqrsToExternalApiUsages", () => { endLine: 23, endColumn: 36, }, + classification: CallClassification.Source, }, ], }, diff --git a/extensions/ql-vscode/test/unit-tests/data-extensions-editor/library.test.ts b/extensions/ql-vscode/test/unit-tests/data-extensions-editor/library.test.ts new file mode 100644 index 000000000..8d3d67a2b --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/data-extensions-editor/library.test.ts @@ -0,0 +1,99 @@ +import { parseLibraryFilename } from "../../../src/data-extensions-editor/library"; + +describe("parseLibraryFilename", () => { + const testCases = [ + { filename: "sql2o-1.6.0.jar", name: "sql2o", version: "1.6.0" }, + { + filename: "spring-boot-3.0.2.jar", + name: "spring-boot", + version: "3.0.2", + }, + { filename: "rt.jar", name: "rt", version: undefined }, + { filename: "guava-15.0.jar", name: "guava", version: "15.0" }, + { + filename: "embedded-db-junit-1.0.0.jar", + name: "embedded-db-junit", + version: "1.0.0", + }, + { + filename: "h2-1.3.160.jar", + name: "h2", + version: "1.3.160", + }, + { + filename: "joda-time-2.0.jar", + name: "joda-time", + version: "2.0", + }, + { + filename: "System.Runtime.dll", + name: "System.Runtime", + version: undefined, + }, + { + filename: "System.Linq.Expressions.dll", + name: "System.Linq.Expressions", + version: undefined, + }, + { + filename: "System.Diagnostics.Debug.dll", + name: "System.Diagnostics.Debug", + version: undefined, + }, + { + filename: "spring-boot-3.1.0-rc2.jar", + name: "spring-boot", + version: "3.1.0-rc2", + }, + { + filename: "org.eclipse.sisu.plexus-0.9.0.M2.jar", + name: "org.eclipse.sisu.plexus", + version: "0.9.0.M2", + }, + { + filename: "org.eclipse.sisu.inject-0.9.0.M2.jar", + name: "org.eclipse.sisu.inject", + version: "0.9.0.M2", + }, + { + filename: "slf4j-api-1.7.36.jar", + name: "slf4j-api", + version: "1.7.36", + }, + { + filename: "guava-30.1.1-jre.jar", + name: "guava", + version: "30.1.1-jre", + }, + { + filename: "caliper-1.0-beta-3.jar", + name: "caliper", + version: "1.0-beta-3", + }, + { + filename: "protobuf-java-4.0.0-rc-2.jar", + name: "protobuf-java", + version: "4.0.0-rc-2", + }, + { + filename: "jetty-util-9.4.51.v20230217.jar", + name: "jetty-util", + version: "9.4.51.v20230217", + }, + { + filename: "jetty-servlet-9.4.51.v20230217.jar", + name: "jetty-servlet", + version: "9.4.51.v20230217", + }, + ]; + + test.each(testCases)( + "$filename is $name@$version", + ({ filename, name, version }) => { + expect(parseLibraryFilename(filename)).toEqual({ + name, + version, + }); + }, + ); +}); diff --git a/extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts b/extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts index f345f1147..0928e76e1 100644 --- a/extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts +++ b/extensions/ql-vscode/test/unit-tests/data-extensions-editor/yaml.test.ts @@ -81,7 +81,8 @@ describe("createDataExtensionYamlsForApplicationMode", () => { "java", [ { - library: "sql2o-1.6.0.jar", + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Connection#createQuery(String)", packageName: "org.sql2o", typeName: "Connection", @@ -115,7 +116,8 @@ describe("createDataExtensionYamlsForApplicationMode", () => { ], }, { - library: "sql2o-1.6.0.jar", + library: "sql2o", + libraryVersion: "1.6.0", signature: "org.sql2o.Query#executeScalar(Class)", packageName: "org.sql2o", typeName: "Query", @@ -149,7 +151,8 @@ describe("createDataExtensionYamlsForApplicationMode", () => { ], }, { - library: "sql2o-2.5.0-alpha1.jar", + library: "sql2o", + libraryVersion: "2.5.0-alpha1", signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", packageName: "org.sql2o", typeName: "Sql2o", @@ -172,7 +175,8 @@ describe("createDataExtensionYamlsForApplicationMode", () => { ], }, { - library: "spring-boot-3.0.2.jar", + library: "spring-boot", + libraryVersion: "3.0.2", signature: "org.springframework.boot.SpringApplication#run(Class,String[])", packageName: "org.springframework.boot", @@ -196,7 +200,7 @@ describe("createDataExtensionYamlsForApplicationMode", () => { ], }, { - library: "rt.jar", + library: "rt", signature: "java.io.PrintStream#println(String)", packageName: "java.io", typeName: "PrintStream", @@ -530,37 +534,28 @@ describe("loadDataExtensionYaml", () => { describe("createFilenameForLibrary", () => { const testCases = [ - { library: "sql2o.jar", filename: "models/sql2o.model.yml" }, { - library: "sql2o-1.6.0.jar", + library: "sql2o", filename: "models/sql2o.model.yml", }, { - library: "spring-boot-3.0.2.jar", + library: "spring-boot", filename: "models/spring-boot.model.yml", }, { - library: "spring-boot-v3.0.2.jar", + library: "spring--boot", filename: "models/spring-boot.model.yml", }, { - library: "spring-boot-3.0.2-alpha1.jar", - filename: "models/spring-boot.model.yml", - }, - { - library: "spring-boot-3.0.2beta2.jar", - filename: "models/spring-boot.model.yml", - }, - { - library: "rt.jar", + library: "rt", filename: "models/rt.model.yml", }, { - library: "System.Runtime.dll", + library: "System.Runtime", filename: "models/system.runtime.model.yml", }, { - library: "System.Runtime.1.5.0.dll", + library: "System..Runtime", filename: "models/system.runtime.model.yml", }, ];