Skip to content

Commit d7e9606

Browse files
authored
Merge pull request #3331 from github/koesie10/endpoint-type-supported
Add supported endpoint types to predicates
2 parents be61664 + 0b4c611 commit d7e9606

File tree

9 files changed

+111
-26
lines changed

9 files changed

+111
-26
lines changed

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function decodeBqrsToMethods(
3333
let libraryVersion: string | undefined;
3434
let type: ModeledMethodType;
3535
let classification: CallClassification;
36-
let endpointType = EndpointType.Method;
36+
let endpointType: EndpointType | undefined = undefined;
3737

3838
if (mode === Mode.Application) {
3939
[
@@ -67,8 +67,19 @@ export function decodeBqrsToMethods(
6767
type = "none";
6868
}
6969

70-
if (methodName === "") {
71-
endpointType = EndpointType.Class;
70+
if (definition.endpointTypeForEndpoint) {
71+
endpointType = definition.endpointTypeForEndpoint({
72+
endpointType,
73+
packageName,
74+
typeName,
75+
methodName,
76+
methodParameters,
77+
});
78+
}
79+
80+
if (endpointType === undefined) {
81+
endpointType =
82+
methodName === "" ? EndpointType.Class : EndpointType.Method;
7283
}
7384

7485
const signature = definition.createMethodSignature({

extensions/ql-vscode/src/model-editor/languages/models-as-data.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { MethodArgument, MethodDefinition } from "../method";
1+
import type { EndpointType, MethodArgument, MethodDefinition } from "../method";
22
import type {
33
ModeledMethod,
44
NeutralModeledMethod,
@@ -23,6 +23,11 @@ type ReadModeledMethod = (row: DataTuple[]) => ModeledMethod;
2323
export type ModelsAsDataLanguagePredicate<T> = {
2424
extensiblePredicate: string;
2525
supportedKinds?: string[];
26+
/**
27+
* The endpoint types that this predicate supports. If not specified, the predicate supports all
28+
* endpoint types.
29+
*/
30+
supportedEndpointTypes?: EndpointType[];
2631
generateMethodDefinition: GenerateMethodDefinition<T>;
2732
readModeledMethod: ReadModeledMethod;
2833
};
@@ -76,6 +81,18 @@ export type ModelsAsDataLanguage = {
7681
*/
7782
availableModes?: Mode[];
7883
createMethodSignature: (method: MethodDefinition) => string;
84+
/**
85+
* This allows modifying the endpoint type automatically assigned to an endpoint. The default
86+
* endpoint type is undefined, and if this method returns undefined, the default endpoint type will
87+
* be determined by heuristics.
88+
* @param method The method to get the endpoint type for. The endpoint type can be undefined if the
89+
* query does not return an endpoint type.
90+
*/
91+
endpointTypeForEndpoint?: (
92+
method: Omit<MethodDefinition, "endpointType"> & {
93+
endpointType: EndpointType | undefined;
94+
},
95+
) => EndpointType | undefined;
7996
predicates: ModelsAsDataLanguagePredicates;
8097
modelGeneration?: ModelsAsDataLanguageModelGeneration;
8198
accessPathSuggestions?: ModelsAsDataLanguageAccessPathSuggestions;

extensions/ql-vscode/src/model-editor/languages/ruby/access-paths.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,27 @@ export function rubyPath(methodName: string, path: string) {
6464
return `${methodPath}.${path}`;
6565
}
6666

67-
export function rubyEndpointType(methodName: string) {
68-
return methodName === "" ? EndpointType.Class : EndpointType.Method;
67+
/** For the purpose of the model editor, we are defining the endpoint types as follows:
68+
* - Class: A class instance
69+
* - Module: The class itself
70+
* - Method: A method in a class
71+
* - Constructor: A constructor method
72+
* @param typeName
73+
* @param methodName
74+
*/
75+
export function rubyEndpointType(typeName: string, methodName: string) {
76+
if (typeName.endsWith("!") && methodName === "new") {
77+
// This is a constructor
78+
return EndpointType.Constructor;
79+
}
80+
81+
if (typeName.endsWith("!") && methodName === "") {
82+
return EndpointType.Module;
83+
}
84+
85+
if (methodName === "") {
86+
return EndpointType.Class;
87+
}
88+
89+
return EndpointType.Method;
6990
}

extensions/ql-vscode/src/model-editor/languages/ruby/index.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { sharedExtensiblePredicates, sharedKinds } from "../shared";
33
import { Mode } from "../../shared/mode";
44
import { parseGenerateModelResults } from "./generate";
55
import type { MethodArgument } from "../../method";
6-
import { getArgumentsList } from "../../method";
6+
import { EndpointType, getArgumentsList } from "../../method";
77
import {
88
parseRubyAccessPath,
99
parseRubyMethodFromPath,
@@ -19,10 +19,13 @@ export const ruby: ModelsAsDataLanguage = {
1919
availableModes: [Mode.Framework],
2020
createMethodSignature: ({ typeName, methodName }) =>
2121
`${typeName}#${methodName}`,
22+
endpointTypeForEndpoint: ({ typeName, methodName }) =>
23+
rubyEndpointType(typeName, methodName),
2224
predicates: {
2325
source: {
2426
extensiblePredicate: sharedExtensiblePredicates.source,
2527
supportedKinds: sharedKinds.source,
28+
supportedEndpointTypes: [EndpointType.Method, EndpointType.Class],
2629
// extensible predicate sourceModel(
2730
// string type, string path, string kind
2831
// );
@@ -42,7 +45,7 @@ export const ruby: ModelsAsDataLanguage = {
4245
kind: row[2] as string,
4346
provenance: "manual",
4447
signature: rubyMethodSignature(typeName, methodName),
45-
endpointType: rubyEndpointType(methodName),
48+
endpointType: rubyEndpointType(typeName, methodName),
4649
packageName: "",
4750
typeName,
4851
methodName,
@@ -53,6 +56,7 @@ export const ruby: ModelsAsDataLanguage = {
5356
sink: {
5457
extensiblePredicate: sharedExtensiblePredicates.sink,
5558
supportedKinds: sharedKinds.sink,
59+
supportedEndpointTypes: [EndpointType.Method, EndpointType.Constructor],
5660
// extensible predicate sinkModel(
5761
// string type, string path, string kind
5862
// );
@@ -74,7 +78,7 @@ export const ruby: ModelsAsDataLanguage = {
7478
kind: row[2] as string,
7579
provenance: "manual",
7680
signature: rubyMethodSignature(typeName, methodName),
77-
endpointType: rubyEndpointType(methodName),
81+
endpointType: rubyEndpointType(typeName, methodName),
7882
packageName: "",
7983
typeName,
8084
methodName,
@@ -85,6 +89,7 @@ export const ruby: ModelsAsDataLanguage = {
8589
summary: {
8690
extensiblePredicate: sharedExtensiblePredicates.summary,
8791
supportedKinds: sharedKinds.summary,
92+
supportedEndpointTypes: [EndpointType.Method, EndpointType.Constructor],
8893
// extensible predicate summaryModel(
8994
// string type, string path, string input, string output, string kind
9095
// );
@@ -105,7 +110,7 @@ export const ruby: ModelsAsDataLanguage = {
105110
kind: row[4] as string,
106111
provenance: "manual",
107112
signature: rubyMethodSignature(typeName, methodName),
108-
endpointType: rubyEndpointType(methodName),
113+
endpointType: rubyEndpointType(typeName, methodName),
109114
packageName: "",
110115
typeName,
111116
methodName,
@@ -132,7 +137,7 @@ export const ruby: ModelsAsDataLanguage = {
132137
kind: row[2] as string,
133138
provenance: "manual",
134139
signature: rubyMethodSignature(typeName, methodName),
135-
endpointType: rubyEndpointType(methodName),
140+
endpointType: rubyEndpointType(typeName, methodName),
136141
packageName: "",
137142
typeName,
138143
methodName,
@@ -157,7 +162,7 @@ export const ruby: ModelsAsDataLanguage = {
157162
relatedTypeName: row[0] as string,
158163
path,
159164
signature: rubyMethodSignature(typeName, methodName),
160-
endpointType: rubyEndpointType(methodName),
165+
endpointType: rubyEndpointType(typeName, methodName),
161166
packageName: "",
162167
typeName,
163168
methodName,

extensions/ql-vscode/src/model-editor/languages/ruby/suggestions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function parseAccessPathSuggestionsResults(
6868
return {
6969
method: {
7070
packageName: "",
71-
endpointType: rubyEndpointType(methodName),
71+
endpointType: rubyEndpointType(type, methodName),
7272
typeName: type,
7373
methodName,
7474
methodParameters: "",

extensions/ql-vscode/src/model-editor/method.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ export type Usage = Call & {
1717
readonly classification: CallClassification;
1818
};
1919

20+
/**
21+
* Endpoint types are generic and can be used to represent different types of endpoints in different languages.
22+
*
23+
* For a reference of symbol kinds used in the LSP protocol (which is a good reference for widely supported features), see
24+
* https://github.com/microsoft/vscode-languageserver-node/blob/4c8115f40b52f2e13adab41109c5b1208fc155ab/types/src/main.ts#L2890-L2920
25+
*/
2026
export enum EndpointType {
21-
Method = "method",
27+
Module = "module",
2228
Class = "class",
29+
Method = "method",
30+
Constructor = "constructor",
2331
}
2432

2533
export interface MethodDefinition {

extensions/ql-vscode/src/view/model-editor/ModelTypeDropdown.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import type { Method } from "../../model-editor/method";
1212
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
1313
import type { Mutable } from "../../common/mutable";
1414
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
15-
import { QueryLanguage } from "../../common/query-language";
15+
import type { QueryLanguage } from "../../common/query-language";
16+
import type { ModelsAsDataLanguagePredicates } from "../../model-editor/languages";
1617
import { getModelsAsDataLanguage } from "../../model-editor/languages";
1718
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
1819
import { InputDropdown } from "./InputDropdown";
@@ -25,6 +26,16 @@ type Props = {
2526
onChange: (modeledMethod: ModeledMethod) => void;
2627
};
2728

29+
const typeLabels: Record<keyof ModelsAsDataLanguagePredicates, string> = {
30+
source: "Source",
31+
sink: "Sink",
32+
summary: "Flow summary",
33+
neutral: "Neutral",
34+
type: "Type",
35+
};
36+
37+
type Option = { value: ModeledMethodType; label: string };
38+
2839
export const ModelTypeDropdown = ({
2940
language,
3041
method,
@@ -33,19 +44,31 @@ export const ModelTypeDropdown = ({
3344
onChange,
3445
}: Props): React.JSX.Element => {
3546
const options = useMemo(() => {
36-
const baseOptions: Array<{ value: ModeledMethodType; label: string }> = [
47+
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
48+
49+
const baseOptions: Option[] = [
3750
{ value: "none", label: "Unmodeled" },
38-
{ value: "source", label: "Source" },
39-
{ value: "sink", label: "Sink" },
40-
{ value: "summary", label: "Flow summary" },
41-
{ value: "neutral", label: "Neutral" },
51+
...Object.entries(modelsAsDataLanguage.predicates)
52+
.map(([predicateKey, predicate]): Option | null => {
53+
const type = predicateKey as keyof ModelsAsDataLanguagePredicates;
54+
55+
if (
56+
predicate.supportedEndpointTypes &&
57+
!predicate.supportedEndpointTypes.includes(method.endpointType)
58+
) {
59+
return null;
60+
}
61+
62+
return {
63+
value: type,
64+
label: typeLabels[type],
65+
};
66+
})
67+
.filter((option): option is Option => option !== null),
4268
];
43-
if (language === QueryLanguage.Ruby) {
44-
baseOptions.push({ value: "type", label: "Type" });
45-
}
4669

4770
return baseOptions;
48-
}, [language]);
71+
}, [language, method.endpointType]);
4972

5073
const handleChange = useCallback(
5174
(e: ChangeEvent<HTMLSelectElement>) => {

extensions/ql-vscode/test/unit-tests/model-editor/languages/ruby/generate.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ describe("parseGenerateModelResults", () => {
136136
typeName: "SQLite3::Database",
137137
},
138138
{
139-
endpointType: EndpointType.Method,
139+
endpointType: EndpointType.Constructor,
140140
input: "Argument[1]",
141141
kind: "value",
142142
methodName: "new",

extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/generate.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ describe("runGenerateQueries", () => {
196196
typeName: "SQLite3::Database",
197197
},
198198
{
199-
endpointType: EndpointType.Method,
199+
endpointType: EndpointType.Constructor,
200200
input: "Argument[1]",
201201
kind: "value",
202202
methodName: "new",

0 commit comments

Comments
 (0)