Skip to content

Commit 14200a5

Browse files
Merge branch 'main' into robertbrignull/data-modeled-methods-tests
2 parents 421fe11 + 5631d33 commit 14200a5

26 files changed

Lines changed: 869 additions & 294 deletions
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

extensions/ql-vscode/src/common/vscode/abstract-webview.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type WebviewPanelConfig = {
1919
viewColumn: ViewColumn;
2020
view: WebviewView;
2121
preserveFocus?: boolean;
22+
iconPath?: Uri | { dark: Uri; light: Uri };
2223
additionalOptions?: WebviewPanelOptions & WebviewOptions;
2324
allowWasmEval?: boolean;
2425
};
@@ -86,6 +87,8 @@ export abstract class AbstractWebview<
8687
);
8788
this.panel = panel;
8889

90+
this.panel.iconPath = config.iconPath;
91+
8992
this.setupPanel(panel, config);
9093

9194
this.panelResolves.forEach((resolve) => resolve(panel));

extensions/ql-vscode/src/data-extensions-editor/auto-model.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ export function parsePredictedClassifications(
140140
input: "",
141141
output: "",
142142
provenance: "ai-generated",
143+
signature,
144+
// predictedBySignature[signature] always has at least element
145+
packageName: predictedMethods[0].package,
146+
typeName: predictedMethods[0].type,
147+
methodName: predictedMethods[0].name,
148+
methodParameters: predictedMethods[0].signature,
143149
};
144150
continue;
145151
}
@@ -157,6 +163,11 @@ export function parsePredictedClassifications(
157163
input: sink.input ?? "",
158164
output: sink.output ?? "",
159165
provenance: "ai-generated",
166+
signature,
167+
packageName: sink.package,
168+
typeName: sink.type,
169+
methodName: sink.name,
170+
methodParameters: sink.signature,
160171
};
161172
}
162173

extensions/ql-vscode/src/data-extensions-editor/bqrs.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { DecodedBqrsChunk } from "../common/bqrs-cli-types";
2-
import { Call, ExternalApiUsage } from "./external-api-usage";
2+
import {
3+
Call,
4+
CallClassification,
5+
ExternalApiUsage,
6+
} from "./external-api-usage";
7+
import { ModeledMethodType } from "./modeled-method";
8+
import { parseLibraryFilename } from "./library";
39

410
export function decodeBqrsToExternalApiUsages(
511
chunk: DecodedBqrsChunk,
@@ -10,7 +16,10 @@ export function decodeBqrsToExternalApiUsages(
1016
const usage = tuple[0] as Call;
1117
const signature = tuple[1] as string;
1218
const supported = (tuple[2] as string) === "true";
13-
const library = tuple[4] as string;
19+
let library = tuple[4] as string;
20+
let libraryVersion: string | undefined = tuple[5] as string;
21+
const type = tuple[6] as ModeledMethodType;
22+
const classification = tuple[8] as CallClassification;
1423

1524
const [packageWithType, methodDeclaration] = signature.split("#");
1625

@@ -30,21 +39,41 @@ export function decodeBqrsToExternalApiUsages(
3039
methodDeclaration.indexOf("("),
3140
);
3241

42+
// For Java, we'll always get back a .jar file, and the library version may be bad because not all library authors
43+
// properly specify the version. Therefore, we'll always try to parse the name and version from the library filename
44+
// for Java.
45+
if (library.endsWith(".jar") || libraryVersion === "") {
46+
const { name, version } = parseLibraryFilename(library);
47+
library = name;
48+
if (version) {
49+
libraryVersion = version;
50+
}
51+
}
52+
53+
if (libraryVersion === "") {
54+
libraryVersion = undefined;
55+
}
56+
3357
if (!methodsByApiName.has(signature)) {
3458
methodsByApiName.set(signature, {
3559
library,
60+
libraryVersion,
3661
signature,
3762
packageName,
3863
typeName,
3964
methodName,
4065
methodParameters,
4166
supported,
67+
supportedType: type,
4268
usages: [],
4369
});
4470
}
4571

4672
const method = methodsByApiName.get(signature)!;
47-
method.usages.push(usage);
73+
method.usages.push({
74+
...usage,
75+
classification,
76+
});
4877
});
4978

5079
return Array.from(methodsByApiName.values());

extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ export class DataExtensionsEditorView extends AbstractWebview<
7676
viewColumn: ViewColumn.Active,
7777
preserveFocus: true,
7878
view: "data-extensions-editor",
79+
iconPath: {
80+
dark: Uri.file(
81+
join(this.ctx.extensionPath, "media/dark/symbol-misc.svg"),
82+
),
83+
light: Uri.file(
84+
join(this.ctx.extensionPath, "media/light/symbol-misc.svg"),
85+
),
86+
},
7987
};
8088
}
8189

@@ -311,11 +319,11 @@ export class DataExtensionsEditorView extends AbstractWebview<
311319
queryRunner: this.queryRunner,
312320
queryStorageDir: this.queryStorageDir,
313321
databaseItem: addedDatabase ?? this.databaseItem,
314-
onResults: async (results) => {
322+
onResults: async (modeledMethods) => {
315323
const modeledMethodsByName: Record<string, ModeledMethod> = {};
316324

317-
for (const result of results) {
318-
modeledMethodsByName[result.signature] = result.modeledMethod;
325+
for (const modeledMethod of modeledMethods) {
326+
modeledMethodsByName[modeledMethod.signature] = modeledMethod;
319327
}
320328

321329
await this.postMessage({

extensions/ql-vscode/src/data-extensions-editor/external-api-usage.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
import { ResolvableLocationValue } from "../common/bqrs-cli-types";
2+
import { ModeledMethodType } from "./modeled-method";
23

34
export type Call = {
45
label: string;
56
url: ResolvableLocationValue;
67
};
78

8-
export type ExternalApiUsage = {
9+
export enum CallClassification {
10+
Unknown = "unknown",
11+
Source = "source",
12+
Test = "test",
13+
Generated = "generated",
14+
}
15+
16+
export type Usage = Call & {
17+
classification: CallClassification;
18+
};
19+
20+
export interface MethodSignature {
921
/**
10-
* Contains the name of the library containing the method declaration, e.g. `sql2o-1.6.0.jar` or `System.Runtime.dll`
22+
* Contains the version of the library if it can be determined by CodeQL, e.g. `4.2.2.2`
1123
*/
12-
library: string;
24+
libraryVersion?: string;
1325
/**
1426
* A unique signature that can be used to identify this external API usage.
1527
*
@@ -25,10 +37,18 @@ export type ExternalApiUsage = {
2537
* The method parameters, including enclosing parentheses, e.g. `(String, String)`
2638
*/
2739
methodParameters: string;
40+
}
41+
42+
export interface ExternalApiUsage extends MethodSignature {
43+
/**
44+
* Contains the name of the library containing the method declaration, e.g. `sql2o-1.6.0.jar` or `System.Runtime.dll`
45+
*/
46+
library: string;
2847
/**
2948
* Is this method already supported by CodeQL standard libraries.
3049
* If so, there is no need for the user to model it themselves.
3150
*/
3251
supported: boolean;
33-
usages: Call[];
34-
};
52+
supportedType: ModeledMethodType;
53+
usages: Usage[];
54+
}

extensions/ql-vscode/src/data-extensions-editor/generate-flow-model.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import { extLogger } from "../common/logging/vscode";
88
import { extensiblePredicateDefinitions } from "./predicates";
99
import { ProgressCallback } from "../common/vscode/progress";
1010
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
11-
import {
12-
ModeledMethodType,
13-
ModeledMethodWithSignature,
14-
} from "./modeled-method";
11+
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
1512
import { redactableError } from "../common/errors";
1613
import { QueryResultType } from "../query-server/new-messages";
1714
import { file } from "tmp-promise";
@@ -27,7 +24,7 @@ type FlowModelOptions = {
2724
databaseItem: DatabaseItem;
2825
progress: ProgressCallback;
2926
token: CancellationToken;
30-
onResults: (results: ModeledMethodWithSignature[]) => void | Promise<void>;
27+
onResults: (results: ModeledMethod[]) => void | Promise<void>;
3128
};
3229

3330
async function resolveQueries(
@@ -79,7 +76,7 @@ async function getModeledMethodsFromFlow(
7976
progress,
8077
token,
8178
}: Omit<FlowModelOptions, "onResults">,
82-
): Promise<ModeledMethodWithSignature[]> {
79+
): Promise<ModeledMethod[]> {
8380
if (queryPath === undefined) {
8481
void showAndLogExceptionWithTelemetry(
8582
extLogger,
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { basename, extname } from "../common/path";
2+
3+
// From the semver package using
4+
// const { re, t } = require("semver/internal/re");
5+
// console.log(re[t.LOOSE]);
6+
// Modifications:
7+
// - Added version named group which does not capture the v prefix
8+
// - Removed the ^ and $ anchors
9+
// - Made the minor and patch versions optional
10+
// - Added a hyphen to the start of the version
11+
// - Added a dot as a valid separator between the version and the label
12+
// - Made the patch version optional even if a label is given
13+
// This will match any semver string at the end of a larger string
14+
const semverRegex =
15+
/-[v=\s]*(?<version>([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;
16+
17+
export interface Library {
18+
name: string;
19+
version?: string;
20+
}
21+
22+
export function parseLibraryFilename(filename: string): Library {
23+
let libraryName = basename(filename);
24+
const extension = extname(libraryName);
25+
libraryName = libraryName.slice(0, -extension.length);
26+
27+
let libraryVersion: string | undefined;
28+
29+
let match: RegExpMatchArray | null = null;
30+
31+
// Reset the regex
32+
semverRegex.lastIndex = 0;
33+
34+
// Find the last occurence of the regex within the library name
35+
// eslint-disable-next-line no-constant-condition
36+
while (true) {
37+
const currentMatch = semverRegex.exec(libraryName);
38+
if (currentMatch === null) {
39+
break;
40+
}
41+
42+
match = currentMatch;
43+
}
44+
45+
if (match?.groups) {
46+
libraryVersion = match.groups?.version;
47+
// Remove everything after the start of the match
48+
libraryName = libraryName.slice(0, match.index);
49+
}
50+
51+
// Remove any leading or trailing hyphens or dots
52+
libraryName = libraryName.replaceAll(/^[.-]+|[.-]+$/g, "");
53+
54+
return {
55+
name: libraryName,
56+
version: libraryVersion,
57+
};
58+
}

extensions/ql-vscode/src/data-extensions-editor/modeled-method.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { MethodSignature } from "./external-api-usage";
2+
13
export type ModeledMethodType =
24
| "none"
35
| "source"
@@ -17,15 +19,10 @@ export type Provenance =
1719
// Entered by the user in the editor manually
1820
| "manual";
1921

20-
export type ModeledMethod = {
22+
export interface ModeledMethod extends MethodSignature {
2123
type: ModeledMethodType;
2224
input: string;
2325
output: string;
2426
kind: string;
2527
provenance: Provenance;
26-
};
27-
28-
export type ModeledMethodWithSignature = {
29-
signature: string;
30-
modeledMethod: ModeledMethod;
31-
};
28+
}

0 commit comments

Comments
 (0)