Skip to content

Commit 6b965af

Browse files
committed
Add validation function for modeled methods
1 parent 623df4c commit 6b965af

3 files changed

Lines changed: 470 additions & 1 deletion

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { ModeledMethod } from "../modeled-method";
2+
import { MethodSignature } from "../method";
3+
import { assertNever } from "../../common/helpers-pure";
4+
5+
export type ModeledMethodValidationError = {
6+
title: string;
7+
message: string;
8+
actionText: string;
9+
index: number;
10+
};
11+
12+
/**
13+
* This method will reset any properties which are not used for the specific type of modeled method.
14+
*
15+
* It will also set the `provenance` to `manual` since multiple modelings of the same method with a
16+
* different provenance are not actually different.
17+
*
18+
* The returned canonical modeled method should only be used for comparisons. It should not be used
19+
* for display purposes, saving the model, or any other purpose which requires the original modeled
20+
* method to be preserved.
21+
*
22+
* @param modeledMethod The modeled method to canonicalize
23+
*/
24+
function canonicalizeModeledMethod(
25+
modeledMethod: ModeledMethod,
26+
): ModeledMethod {
27+
const methodSignature: MethodSignature = {
28+
signature: modeledMethod.signature,
29+
packageName: modeledMethod.packageName,
30+
typeName: modeledMethod.typeName,
31+
methodName: modeledMethod.methodName,
32+
methodParameters: modeledMethod.methodParameters,
33+
};
34+
35+
switch (modeledMethod.type) {
36+
case "none":
37+
return {
38+
...methodSignature,
39+
type: "none",
40+
input: "",
41+
output: "",
42+
kind: "",
43+
provenance: "manual",
44+
};
45+
case "source":
46+
return {
47+
...methodSignature,
48+
type: "source",
49+
input: "",
50+
output: modeledMethod.output,
51+
kind: modeledMethod.kind,
52+
provenance: "manual",
53+
};
54+
case "sink":
55+
return {
56+
...methodSignature,
57+
type: "sink",
58+
input: modeledMethod.input,
59+
output: "",
60+
kind: modeledMethod.kind,
61+
provenance: "manual",
62+
};
63+
case "summary":
64+
return {
65+
...methodSignature,
66+
type: "summary",
67+
input: modeledMethod.input,
68+
output: modeledMethod.output,
69+
kind: modeledMethod.kind,
70+
provenance: "manual",
71+
};
72+
case "neutral":
73+
return {
74+
...methodSignature,
75+
type: "neutral",
76+
input: "",
77+
output: "",
78+
kind: "",
79+
provenance: "manual",
80+
};
81+
default:
82+
assertNever(modeledMethod.type);
83+
}
84+
}
85+
86+
export function validateModeledMethods(
87+
modeledMethods: ModeledMethod[],
88+
): ModeledMethodValidationError[] {
89+
// Anything that is not modeled will not be saved, so we don't need to validate it
90+
const consideredModeledMethods = modeledMethods.filter(
91+
(modeledMethod) => modeledMethod.type !== "none",
92+
);
93+
94+
const result: ModeledMethodValidationError[] = [];
95+
96+
// If the same model is present multiple times, only the first one makes sense, so we should give
97+
// an error for any duplicates.
98+
const seenModeledMethods = new Set<string>();
99+
for (const modeledMethod of consideredModeledMethods) {
100+
const canonicalModeledMethod = canonicalizeModeledMethod(modeledMethod);
101+
const key = JSON.stringify(canonicalModeledMethod);
102+
103+
if (seenModeledMethods.has(key)) {
104+
result.push({
105+
title: "Duplicated classification",
106+
message:
107+
"This method has two identical or conflicting classifications.",
108+
actionText: "Modify or remove the duplicated classification.",
109+
index: modeledMethods.indexOf(modeledMethod),
110+
});
111+
} else {
112+
seenModeledMethods.add(key);
113+
}
114+
}
115+
116+
const neutralModeledMethod = consideredModeledMethods.find(
117+
(modeledMethod) => modeledMethod.type === "neutral",
118+
);
119+
const hasNonNeutralModeledMethod = consideredModeledMethods.some(
120+
(modeledMethod) => modeledMethod.type !== "neutral",
121+
);
122+
123+
// If there is a neutral model and any other model, that is an error
124+
if (neutralModeledMethod && hasNonNeutralModeledMethod) {
125+
// Another validation will validate that only one neutral method is present, so we only need
126+
// to return an error for the first one
127+
128+
result.push({
129+
title: "Conflicting classification",
130+
message:
131+
"This method has a neutral classification, which conflicts with other classifications.",
132+
actionText: "Modify or remove the neutral classification.",
133+
index: modeledMethods.indexOf(neutralModeledMethod),
134+
});
135+
}
136+
137+
// Sort by index so that the errors are always in the same order
138+
result.sort((a, b) => a.index - b.index);
139+
140+
return result;
141+
}

extensions/ql-vscode/test/unit-tests/jest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ const config: Config = {
146146
// testLocationInResults: false,
147147

148148
// The glob patterns Jest uses to detect test files
149-
testMatch: ["**/*.test.[jt]s"],
149+
testMatch: ["**/*.{test,spec}.[jt]s"],
150150

151151
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
152152
// testPathIgnorePatterns: [

0 commit comments

Comments
 (0)