Skip to content

Commit 64a0733

Browse files
authored
Merge pull request #3048 from github/koesie10/type-model
Add `type` as modeled method type
2 parents f9e0654 + ddaabfa commit 64a0733

File tree

12 files changed

+234
-30
lines changed

12 files changed

+234
-30
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
SinkModeledMethod,
66
SourceModeledMethod,
77
SummaryModeledMethod,
8+
TypeModeledMethod,
89
} from "../modeled-method";
910
import { DataTuple } from "../model-extension-file";
1011
import { Mode } from "../shared/mode";
@@ -17,7 +18,7 @@ type ReadModeledMethod = (row: DataTuple[]) => ModeledMethod;
1718

1819
export type ModelsAsDataLanguagePredicate<T> = {
1920
extensiblePredicate: string;
20-
supportedKinds: string[];
21+
supportedKinds?: string[];
2122
generateMethodDefinition: GenerateMethodDefinition<T>;
2223
readModeledMethod: ReadModeledMethod;
2324
};
@@ -43,6 +44,7 @@ export type ModelsAsDataLanguagePredicates = {
4344
sink?: ModelsAsDataLanguagePredicate<SinkModeledMethod>;
4445
summary?: ModelsAsDataLanguagePredicate<SummaryModeledMethod>;
4546
neutral?: ModelsAsDataLanguagePredicate<NeutralModeledMethod>;
47+
type?: ModelsAsDataLanguagePredicate<TypeModeledMethod>;
4648
};
4749

4850
export type MethodArgumentOptions = {

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,30 @@ export const ruby: ModelsAsDataLanguage = {
151151
};
152152
},
153153
},
154+
type: {
155+
extensiblePredicate: "typeModel",
156+
// extensible predicate typeModel(string type1, string type2, string path);
157+
generateMethodDefinition: (method) => [
158+
method.relatedTypeName,
159+
method.typeName,
160+
`Method[${method.methodName}].${method.path}`,
161+
],
162+
readModeledMethod: (row) => {
163+
const typeName = row[1] as string;
164+
const { methodName, path } = parseRubyAccessPath(row[2] as string);
165+
166+
return {
167+
type: "type",
168+
relatedTypeName: row[0] as string,
169+
path,
170+
signature: rubyMethodSignature(typeName, methodName),
171+
packageName: "",
172+
typeName,
173+
methodName,
174+
methodParameters: "",
175+
};
176+
},
177+
},
154178
},
155179
modelGeneration: {
156180
queryConstraints: {

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export function createEmptyModeledMethod(
2525
return createEmptySummaryModeledMethod(canonicalMethodSignature);
2626
case "neutral":
2727
return createEmptyNeutralModeledMethod(canonicalMethodSignature);
28+
case "type":
29+
return createEmptyTypeModeledMethod(canonicalMethodSignature);
2830
default:
2931
assertNever(type);
3032
}
@@ -86,3 +88,14 @@ function createEmptyNeutralModeledMethod(
8688
provenance: "manual",
8789
};
8890
}
91+
92+
function createEmptyTypeModeledMethod(
93+
methodSignature: MethodSignature,
94+
): ModeledMethod {
95+
return {
96+
...methodSignature,
97+
type: "type",
98+
relatedTypeName: "",
99+
path: "",
100+
};
101+
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type ModeledMethodType =
55
| "source"
66
| "sink"
77
| "summary"
8+
| "type"
89
| "neutral";
910

1011
export type Provenance =
@@ -51,12 +52,19 @@ export interface NeutralModeledMethod extends MethodSignature {
5152
readonly provenance: Provenance;
5253
}
5354

55+
export interface TypeModeledMethod extends MethodSignature {
56+
readonly type: "type";
57+
readonly relatedTypeName: string;
58+
readonly path: string;
59+
}
60+
5461
export type ModeledMethod =
5562
| NoneModeledMethod
5663
| SourceModeledMethod
5764
| SinkModeledMethod
5865
| SummaryModeledMethod
59-
| NeutralModeledMethod;
66+
| NeutralModeledMethod
67+
| TypeModeledMethod;
6068

6169
export type ModeledMethodKind = string;
6270

extensions/ql-vscode/src/model-editor/shared/validation.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ function canonicalizeModeledMethod(
7070
kind: modeledMethod.kind,
7171
provenance: "manual",
7272
};
73+
case "type":
74+
return {
75+
...methodSignature,
76+
type: "type",
77+
relatedTypeName: modeledMethod.relatedTypeName,
78+
path: modeledMethod.path,
79+
};
7380
default:
7481
assertNever(modeledMethod);
7582
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
SinkModeledMethod,
88
SourceModeledMethod,
99
SummaryModeledMethod,
10+
TypeModeledMethod,
1011
} from "./modeled-method";
1112
import {
1213
getModelsAsDataLanguage,
@@ -68,6 +69,7 @@ export function createDataExtensionYaml(
6869
sink: [] as SinkModeledMethod[],
6970
summary: [] as SummaryModeledMethod[],
7071
neutral: [] as NeutralModeledMethod[],
72+
type: [] as TypeModeledMethod[],
7173
} satisfies Record<keyof ModelsAsDataLanguagePredicates, ModeledMethod[]>;
7274

7375
for (const modeledMethod of modeledMethods) {
@@ -88,6 +90,9 @@ export function createDataExtensionYaml(
8890
case "neutral":
8991
methodsByType.neutral.push(modeledMethod);
9092
break;
93+
case "type":
94+
methodsByType.type.push(modeledMethod);
95+
break;
9196
default:
9297
assertNever(modeledMethod);
9398
}
@@ -122,6 +127,12 @@ export function createDataExtensionYaml(
122127
methodsByType.neutral,
123128
modelsAsDataLanguage.predicates.neutral,
124129
);
130+
case "type":
131+
return createExtensions(
132+
language,
133+
methodsByType.type,
134+
modelsAsDataLanguage.predicates.type,
135+
);
125136
default:
126137
assertNever(type);
127138
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as React from "react";
2+
import { useMemo } from "react";
3+
import { Dropdown } from "./Dropdown";
4+
5+
type Props = {
6+
value: string;
7+
className?: string;
8+
9+
"aria-label"?: string;
10+
};
11+
12+
export function ReadonlyDropdown({ value, ...props }: Props) {
13+
const options = useMemo(() => {
14+
return [
15+
{
16+
value,
17+
label: value,
18+
},
19+
];
20+
}, [value]);
21+
22+
return (
23+
<Dropdown
24+
value={value}
25+
disabledPlaceholder={value}
26+
disabled={true}
27+
options={options}
28+
{...props}
29+
/>
30+
);
31+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
modeledMethodSupportsInput,
77
} from "../../model-editor/modeled-method";
88
import { Method } from "../../model-editor/method";
9+
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
910
import { QueryLanguage } from "../../common/query-language";
1011
import { getModelsAsDataLanguage } from "../../model-editor/languages";
1112

@@ -59,6 +60,10 @@ export const ModelInputDropdown = ({
5960
? modeledMethod.input
6061
: undefined;
6162

63+
if (modeledMethod?.type === "type") {
64+
return <ReadonlyDropdown value={modeledMethod.path} aria-label="Path" />;
65+
}
66+
6267
return (
6368
<Dropdown
6469
value={value}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
modeledMethodSupportsOutput,
77
} from "../../model-editor/modeled-method";
88
import { Method } from "../../model-editor/method";
9+
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
910
import { getModelsAsDataLanguage } from "../../model-editor/languages";
1011
import { QueryLanguage } from "../../common/query-language";
1112

@@ -60,6 +61,15 @@ export const ModelOutputDropdown = ({
6061
? modeledMethod.output
6162
: undefined;
6263

64+
if (modeledMethod?.type === "type") {
65+
return (
66+
<ReadonlyDropdown
67+
value={modeledMethod.relatedTypeName}
68+
aria-label="Related type name"
69+
/>
70+
);
71+
}
72+
6373
return (
6474
<Dropdown
6575
value={value}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { Method } from "../../model-editor/method";
1111
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
1212
import { Mutable } from "../../common/mutable";
13+
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
1314
import { QueryLanguage } from "../../common/query-language";
1415
import { getModelsAsDataLanguage } from "../../model-editor/languages";
1516

@@ -73,6 +74,20 @@ export const ModelTypeDropdown = ({
7374
[onChange, method, modeledMethod, language],
7475
);
7576

77+
const value = modeledMethod?.type ?? "none";
78+
79+
const isShownOption = options.some((option) => option.value === value);
80+
81+
if (!isShownOption) {
82+
return (
83+
<ReadonlyDropdown
84+
// Try to show this like a normal type with uppercased first letter
85+
value={value.charAt(0).toUpperCase() + value.slice(1)}
86+
aria-label="Model type"
87+
/>
88+
);
89+
}
90+
7691
return (
7792
<Dropdown
7893
value={modeledMethod?.type ?? "none"}

0 commit comments

Comments
 (0)