Skip to content

Commit 261f8b3

Browse files
committed
Add type as modeled method type
This adds support for modeling types. A MaD language can now optionally define a `type` predicate. This allows internally propagating these models. The UI will now simply show a label "type" for type models without any way to edit these.
1 parent 4673bf5 commit 261f8b3

File tree

9 files changed

+181
-30
lines changed

9 files changed

+181
-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 ModelsAsDataLanguage = {

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

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

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ export const ModelTypeDropdown = ({
7373
[onChange, method, modeledMethod, argumentsList],
7474
);
7575

76+
const value = modeledMethod?.type ?? "none";
77+
78+
const isShownOption = options.some((option) => option.value === value);
79+
80+
if (!isShownOption) {
81+
return <>{value}</>;
82+
}
83+
7684
return (
7785
<Dropdown
7886
value={modeledMethod?.type ?? "none"}

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

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -75,33 +75,71 @@ describe("parseGenerateModelResults", () => {
7575
ruby,
7676
createMockLogger(),
7777
);
78-
expect(result.sort()).toEqual(
79-
[
80-
{
81-
input: "Argument[self]",
82-
kind: "value",
83-
methodName: "create_function",
84-
methodParameters: "",
85-
output: "ReturnValue",
86-
packageName: "",
87-
provenance: "manual",
88-
signature: "SQLite3::Database#create_function",
89-
type: "summary",
90-
typeName: "SQLite3::Database",
91-
},
92-
{
93-
input: "Argument[1]",
94-
kind: "value",
95-
methodName: "new",
96-
methodParameters: "",
97-
output: "ReturnValue",
98-
packageName: "",
99-
provenance: "manual",
100-
signature: "SQLite3::Value!#new",
101-
type: "summary",
102-
typeName: "SQLite3::Value!",
103-
},
104-
].sort(),
105-
);
78+
expect(result).toEqual([
79+
{
80+
methodName: "types",
81+
methodParameters: "",
82+
packageName: "",
83+
path: "ReturnValue",
84+
relatedTypeName: "Array",
85+
signature: "SQLite3::ResultSet#types",
86+
type: "type",
87+
typeName: "SQLite3::ResultSet",
88+
},
89+
{
90+
methodName: "columns",
91+
methodParameters: "",
92+
packageName: "",
93+
path: "ReturnValue",
94+
relatedTypeName: "Array",
95+
signature: "SQLite3::ResultSet#columns",
96+
type: "type",
97+
typeName: "SQLite3::ResultSet",
98+
},
99+
{
100+
methodName: "types",
101+
methodParameters: "",
102+
packageName: "",
103+
path: "ReturnValue",
104+
relatedTypeName: "Array",
105+
signature: "SQLite3::Statement#types",
106+
type: "type",
107+
typeName: "SQLite3::Statement",
108+
},
109+
{
110+
methodName: "columns",
111+
methodParameters: "",
112+
packageName: "",
113+
path: "ReturnValue",
114+
relatedTypeName: "Array",
115+
signature: "SQLite3::Statement#columns",
116+
type: "type",
117+
typeName: "SQLite3::Statement",
118+
},
119+
{
120+
input: "Argument[self]",
121+
kind: "value",
122+
methodName: "create_function",
123+
methodParameters: "",
124+
output: "ReturnValue",
125+
packageName: "",
126+
provenance: "manual",
127+
signature: "SQLite3::Database#create_function",
128+
type: "summary",
129+
typeName: "SQLite3::Database",
130+
},
131+
{
132+
input: "Argument[1]",
133+
kind: "value",
134+
methodName: "new",
135+
methodParameters: "",
136+
output: "ReturnValue",
137+
packageName: "",
138+
provenance: "manual",
139+
signature: "SQLite3::Value!#new",
140+
type: "summary",
141+
typeName: "SQLite3::Value!",
142+
},
143+
]);
106144
});
107145
});

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,46 @@ describe("runGenerateQueries", () => {
138138
...options,
139139
});
140140
expect(onResults).toHaveBeenCalledWith([
141+
{
142+
methodName: "types",
143+
methodParameters: "",
144+
packageName: "",
145+
path: "ReturnValue",
146+
relatedTypeName: "Array",
147+
signature: "SQLite3::ResultSet#types",
148+
type: "type",
149+
typeName: "SQLite3::ResultSet",
150+
},
151+
{
152+
methodName: "columns",
153+
methodParameters: "",
154+
packageName: "",
155+
path: "ReturnValue",
156+
relatedTypeName: "Array",
157+
signature: "SQLite3::ResultSet#columns",
158+
type: "type",
159+
typeName: "SQLite3::ResultSet",
160+
},
161+
{
162+
methodName: "types",
163+
methodParameters: "",
164+
packageName: "",
165+
path: "ReturnValue",
166+
relatedTypeName: "Array",
167+
signature: "SQLite3::Statement#types",
168+
type: "type",
169+
typeName: "SQLite3::Statement",
170+
},
171+
{
172+
methodName: "columns",
173+
methodParameters: "",
174+
packageName: "",
175+
path: "ReturnValue",
176+
relatedTypeName: "Array",
177+
signature: "SQLite3::Statement#columns",
178+
type: "type",
179+
typeName: "SQLite3::Statement",
180+
},
141181
{
142182
input: "Argument[self]",
143183
kind: "value",

0 commit comments

Comments
 (0)