Skip to content

Commit 73221e8

Browse files
committed
Add supported endpoint types
1 parent 6fd628b commit 73221e8

6 files changed

Lines changed: 63 additions & 20 deletions

File tree

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

Lines changed: 6 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
};

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

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

67-
export function rubyEndpointType(methodName: string) {
67+
export function rubyEndpointType(typeName: string, methodName: string) {
68+
if (typeName.endsWith("!") && methodName === "new") {
69+
// This is a constructor
70+
return EndpointType.Constructor;
71+
}
72+
6873
return methodName === "" ? EndpointType.Class : EndpointType.Method;
6974
}

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

Lines changed: 9 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,
@@ -23,6 +23,7 @@ export const ruby: ModelsAsDataLanguage = {
2323
source: {
2424
extensiblePredicate: sharedExtensiblePredicates.source,
2525
supportedKinds: sharedKinds.source,
26+
supportedEndpointTypes: [EndpointType.Method, EndpointType.Class],
2627
// extensible predicate sourceModel(
2728
// string type, string path, string kind
2829
// );
@@ -42,7 +43,7 @@ export const ruby: ModelsAsDataLanguage = {
4243
kind: row[2] as string,
4344
provenance: "manual",
4445
signature: rubyMethodSignature(typeName, methodName),
45-
endpointType: rubyEndpointType(methodName),
46+
endpointType: rubyEndpointType(typeName, methodName),
4647
packageName: "",
4748
typeName,
4849
methodName,
@@ -53,6 +54,7 @@ export const ruby: ModelsAsDataLanguage = {
5354
sink: {
5455
extensiblePredicate: sharedExtensiblePredicates.sink,
5556
supportedKinds: sharedKinds.sink,
57+
supportedEndpointTypes: [EndpointType.Method, EndpointType.Constructor],
5658
// extensible predicate sinkModel(
5759
// string type, string path, string kind
5860
// );
@@ -74,7 +76,7 @@ export const ruby: ModelsAsDataLanguage = {
7476
kind: row[2] as string,
7577
provenance: "manual",
7678
signature: rubyMethodSignature(typeName, methodName),
77-
endpointType: rubyEndpointType(methodName),
79+
endpointType: rubyEndpointType(typeName, methodName),
7880
packageName: "",
7981
typeName,
8082
methodName,
@@ -85,6 +87,7 @@ export const ruby: ModelsAsDataLanguage = {
8587
summary: {
8688
extensiblePredicate: sharedExtensiblePredicates.summary,
8789
supportedKinds: sharedKinds.summary,
90+
supportedEndpointTypes: [EndpointType.Method, EndpointType.Constructor],
8891
// extensible predicate summaryModel(
8992
// string type, string path, string input, string output, string kind
9093
// );
@@ -105,7 +108,7 @@ export const ruby: ModelsAsDataLanguage = {
105108
kind: row[4] as string,
106109
provenance: "manual",
107110
signature: rubyMethodSignature(typeName, methodName),
108-
endpointType: rubyEndpointType(methodName),
111+
endpointType: rubyEndpointType(typeName, methodName),
109112
packageName: "",
110113
typeName,
111114
methodName,
@@ -132,7 +135,7 @@ export const ruby: ModelsAsDataLanguage = {
132135
kind: row[2] as string,
133136
provenance: "manual",
134137
signature: rubyMethodSignature(typeName, methodName),
135-
endpointType: rubyEndpointType(methodName),
138+
endpointType: rubyEndpointType(typeName, methodName),
136139
packageName: "",
137140
typeName,
138141
methodName,
@@ -157,7 +160,7 @@ export const ruby: ModelsAsDataLanguage = {
157160
relatedTypeName: row[0] as string,
158161
path,
159162
signature: rubyMethodSignature(typeName, methodName),
160-
endpointType: rubyEndpointType(methodName),
163+
endpointType: rubyEndpointType(typeName, methodName),
161164
packageName: "",
162165
typeName,
163166
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: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,16 @@ 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",
2227
Class = "class",
28+
Method = "method",
29+
Constructor = "constructor",
2330
}
2431

2532
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>) => {

0 commit comments

Comments
 (0)