Skip to content

Commit 6cfa0a9

Browse files
authored
Merge pull request #3141 from github/koesie10/model-editor-consistency-check
Add a consistency check for the model editor
2 parents 60d777a + 1891763 commit 6cfa0a9

File tree

5 files changed

+170
-0
lines changed

5 files changed

+170
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Method } from "./method";
2+
import { ModeledMethod } from "./modeled-method";
3+
import { BaseLogger } from "../common/logging";
4+
5+
interface Notifier {
6+
missingMethod(signature: string): void;
7+
inconsistentSupported(signature: string, expectedSupported: boolean): void;
8+
}
9+
10+
export function checkConsistency(
11+
methods: readonly Method[],
12+
modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
13+
notifier: Notifier,
14+
) {
15+
const methodsBySignature = methods.reduce(
16+
(acc, method) => {
17+
acc[method.signature] = method;
18+
return acc;
19+
},
20+
{} as Record<string, Method>,
21+
);
22+
23+
for (const signature in modeledMethods) {
24+
const method = methodsBySignature[signature];
25+
if (!method) {
26+
notifier.missingMethod(signature);
27+
continue;
28+
}
29+
30+
const modeledMethodsForSignature = modeledMethods[signature];
31+
32+
checkMethodConsistency(method, modeledMethodsForSignature, notifier);
33+
}
34+
}
35+
36+
function checkMethodConsistency(
37+
method: Method,
38+
modeledMethods: readonly ModeledMethod[],
39+
notifier: Notifier,
40+
) {
41+
// Type models are currently not shown as `supported` since they do not give any model information.
42+
const expectSupported = modeledMethods.some(
43+
(m) => m.type !== "none" && m.type !== "type",
44+
);
45+
46+
if (method.supported !== expectSupported) {
47+
notifier.inconsistentSupported(method.signature, expectSupported);
48+
}
49+
}
50+
51+
export class DefaultNotifier implements Notifier {
52+
constructor(private readonly logger: BaseLogger) {}
53+
54+
missingMethod(signature: string) {
55+
void this.logger.log(
56+
`Model editor query consistency check: Missing method ${signature} for method that is modeled.`,
57+
);
58+
}
59+
60+
inconsistentSupported(signature: string, expectedSupported: boolean) {
61+
const expectedMessage = expectedSupported
62+
? `Expected method to be supported, but it is not.`
63+
: `Expected method to not be supported, but it is.`;
64+
65+
void this.logger.log(
66+
`Model editor query consistency check: Inconsistent supported flag for method ${signature}. ${expectedMessage}`,
67+
);
68+
}
69+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { ModelingEvents } from "./modeling-events";
2424
import { getModelsAsDataLanguage } from "./languages";
2525
import { INITIAL_MODE } from "./shared/mode";
2626
import { isSupportedLanguage } from "./supported-languages";
27+
import { DefaultNotifier, checkConsistency } from "./consistency-check";
2728

2829
export class ModelEditorModule extends DisposableObject {
2930
private readonly queryStorageDir: string;
@@ -99,6 +100,20 @@ export class ModelEditorModule extends DisposableObject {
99100
await this.showMethod(event.databaseItem, event.method, event.usage);
100101
}),
101102
);
103+
104+
this.push(
105+
this.modelingEvents.onMethodsChanged((event) => {
106+
const modeledMethods = this.modelingStore.getModeledMethods(
107+
event.databaseItem,
108+
);
109+
110+
checkConsistency(
111+
event.methods,
112+
modeledMethods,
113+
new DefaultNotifier(this.app.logger),
114+
);
115+
}),
116+
);
102117
}
103118

104119
private async showMethod(

extensions/ql-vscode/src/model-editor/modeling-events.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Mode } from "./shared/mode";
99
interface MethodsChangedEvent {
1010
readonly methods: readonly Method[];
1111
readonly dbUri: string;
12+
readonly databaseItem: DatabaseItem;
1213
readonly isActiveDb: boolean;
1314
}
1415

@@ -166,10 +167,12 @@ export class ModelingEvents extends DisposableObject {
166167
public fireMethodsChangedEvent(
167168
methods: Method[],
168169
dbUri: string,
170+
databaseItem: DatabaseItem,
169171
isActiveDb: boolean,
170172
) {
171173
this.onMethodsChangedEventEmitter.fire({
172174
methods,
175+
databaseItem,
173176
dbUri,
174177
isActiveDb,
175178
});

extensions/ql-vscode/src/model-editor/modeling-store.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export class ModelingStore extends DisposableObject {
155155
this.modelingEvents.fireMethodsChangedEvent(
156156
methods,
157157
dbUri,
158+
dbItem,
158159
dbUri === this.activeDb,
159160
);
160161
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { checkConsistency } from "../../../src/model-editor/consistency-check";
2+
import { createSourceModeledMethod } from "../../factories/model-editor/modeled-method-factories";
3+
import { createMethod } from "../../factories/model-editor/method-factories";
4+
5+
describe("checkConsistency", () => {
6+
const notifier = {
7+
missingMethod: jest.fn(),
8+
inconsistentSupported: jest.fn(),
9+
};
10+
11+
beforeEach(() => {
12+
notifier.missingMethod.mockReset();
13+
notifier.inconsistentSupported.mockReset();
14+
});
15+
16+
it("should call missingMethod when method is missing", () => {
17+
checkConsistency(
18+
[],
19+
{
20+
"Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SeparatedList`1(System.Collections.Generic.IEnumerable<TNode>)":
21+
[createSourceModeledMethod()],
22+
},
23+
notifier,
24+
);
25+
26+
expect(notifier.missingMethod).toHaveBeenCalledWith(
27+
"Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SeparatedList`1(System.Collections.Generic.IEnumerable<TNode>)",
28+
);
29+
expect(notifier.inconsistentSupported).not.toHaveBeenCalled();
30+
});
31+
32+
it("should call inconsistentSupported when support is inconsistent", () => {
33+
checkConsistency(
34+
[
35+
createMethod({
36+
signature:
37+
"Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SeparatedList`1(System.Collections.Generic.IEnumerable<TNode>)",
38+
packageName: "Microsoft.CodeAnalysis.CSharp",
39+
typeName: "SyntaxFactory",
40+
methodName: "SeparatedList`1",
41+
methodParameters: "(System.Collections.Generic.IEnumerable<TNode>)",
42+
supported: false,
43+
}),
44+
],
45+
{
46+
"Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SeparatedList`1(System.Collections.Generic.IEnumerable<TNode>)":
47+
[createSourceModeledMethod({})],
48+
},
49+
notifier,
50+
);
51+
52+
expect(notifier.inconsistentSupported).toHaveBeenCalledWith(
53+
"Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SeparatedList`1(System.Collections.Generic.IEnumerable<TNode>)",
54+
true,
55+
);
56+
expect(notifier.missingMethod).not.toHaveBeenCalled();
57+
});
58+
59+
it("should call no methods when consistent", () => {
60+
checkConsistency(
61+
[
62+
createMethod({
63+
signature:
64+
"Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SeparatedList<TNode>(System.Collections.Generic.IEnumerable<TNode>)",
65+
packageName: "Microsoft.CodeAnalysis.CSharp",
66+
typeName: "SyntaxFactory",
67+
methodName: "SeparatedList<TNode>",
68+
methodParameters: "(System.Collections.Generic.IEnumerable<TNode>)",
69+
supported: true,
70+
}),
71+
],
72+
{
73+
"Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SeparatedList<TNode>(System.Collections.Generic.IEnumerable<TNode>)":
74+
[createSourceModeledMethod({})],
75+
},
76+
notifier,
77+
);
78+
79+
expect(notifier.missingMethod).not.toHaveBeenCalled();
80+
expect(notifier.inconsistentSupported).not.toHaveBeenCalled();
81+
});
82+
});

0 commit comments

Comments
 (0)