Skip to content

Commit c6e7bcd

Browse files
authored
Merge branch 'main' into koesie10/access-paths-query-bqrs
2 parents 11e0f49 + a03863c commit c6e7bcd

File tree

11 files changed

+346
-19
lines changed

11 files changed

+346
-19
lines changed

extensions/ql-vscode/src/compare/sarif-diff.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
import type { Result } from "sarif";
22

3+
function toCanonicalResult(result: Result): Result {
4+
const canonicalResult = {
5+
...result,
6+
};
7+
8+
if (canonicalResult.locations) {
9+
canonicalResult.locations = canonicalResult.locations.map((location) => {
10+
if (location.physicalLocation?.artifactLocation?.index !== undefined) {
11+
const canonicalLocation = {
12+
...location,
13+
};
14+
15+
canonicalLocation.physicalLocation = {
16+
...canonicalLocation.physicalLocation,
17+
};
18+
19+
canonicalLocation.physicalLocation.artifactLocation = {
20+
...canonicalLocation.physicalLocation.artifactLocation,
21+
};
22+
23+
// The index is dependent on the result of the SARIF file and usually doesn't really tell
24+
// us anything useful, so we remove it from the comparison.
25+
delete canonicalLocation.physicalLocation.artifactLocation.index;
26+
27+
return canonicalLocation;
28+
}
29+
30+
// Don't create a new object if we don't need to
31+
return location;
32+
});
33+
}
34+
35+
return canonicalResult;
36+
}
37+
338
/**
439
* Compare the alerts of two queries. Use deep equality to determine if
540
* results have been added or removed across two invocations of a query.
@@ -25,9 +60,12 @@ export function sarifDiff(fromResults: Result[], toResults: Result[]) {
2560
throw new Error("CodeQL Compare: Target query has no results.");
2661
}
2762

63+
const canonicalFromResults = fromResults.map(toCanonicalResult);
64+
const canonicalToResults = toResults.map(toCanonicalResult);
65+
2866
const results = {
29-
from: arrayDiff(fromResults, toResults),
30-
to: arrayDiff(toResults, fromResults),
67+
from: arrayDiff(canonicalFromResults, canonicalToResults),
68+
to: arrayDiff(canonicalToResults, canonicalFromResults),
3169
};
3270

3371
if (

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type ModelsAsDataLanguageModelGeneration = {
4444
};
4545

4646
type ModelsAsDataLanguageAccessPathSuggestions = {
47+
queryConstraints: (mode: Mode) => QueryConstraints;
4748
parseResults: (
4849
// The results of a single predicate of the query.
4950
bqrs: DecodedBqrsChunk,

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
rubyPath,
1313
} from "./access-paths";
1414
import { parseAccessPathSuggestionsResults } from "./suggestions";
15+
import { modeTag } from "../../mode-tag";
1516

1617
export const ruby: ModelsAsDataLanguage = {
1718
availableModes: [Mode.Framework],
@@ -170,6 +171,10 @@ export const ruby: ModelsAsDataLanguage = {
170171
parseResults: parseGenerateModelResults,
171172
},
172173
accessPathSuggestions: {
174+
queryConstraints: (mode) => ({
175+
kind: "table",
176+
"tags contain all": ["modeleditor", "access-paths", modeTag(mode)],
177+
}),
173178
parseResults: parseAccessPathSuggestionsResults,
174179
},
175180
getArgumentOptions: (method) => {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ export class ModelEditorView extends AbstractWebview<
529529
modelsAsDataLanguage,
530530
this.app.logger,
531531
),
532+
queryConstraints: accessPathSuggestions.queryConstraints(mode),
532533
cliServer: this.cliServer,
533534
queryRunner: this.queryRunner,
534535
queryStorageDir: this.queryStorageDir,

extensions/ql-vscode/src/model-editor/suggestion-queries.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { CodeQLCliServer } from "../codeql-cli/cli";
22
import type { Mode } from "./shared/mode";
3+
import type { QueryConstraints } from "../local-queries";
34
import { resolveQueriesFromPacks } from "../local-queries";
4-
import { modeTag } from "./mode-tag";
55
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
66
import type { NotificationLogger } from "../common/logging";
77
import { showAndLogExceptionWithTelemetry } from "../common/logging";
@@ -22,6 +22,7 @@ type RunQueryOptions = {
2222
parseResults: (
2323
results: DecodedBqrsChunk,
2424
) => AccessPathSuggestionRow[] | Promise<AccessPathSuggestionRow[]>;
25+
queryConstraints: QueryConstraints;
2526

2627
cliServer: CodeQLCliServer;
2728
queryRunner: QueryRunner;
@@ -39,6 +40,7 @@ export async function runSuggestionsQuery(
3940
mode: Mode,
4041
{
4142
parseResults,
43+
queryConstraints,
4244
cliServer,
4345
queryRunner,
4446
logger,
@@ -68,6 +70,7 @@ export async function runSuggestionsQuery(
6870
cliServer,
6971
databaseItem.language,
7072
mode,
73+
queryConstraints,
7174
);
7275
if (!queryPath) {
7376
void showAndLogExceptionWithTelemetry(
@@ -141,13 +144,15 @@ export async function runSuggestionsQuery(
141144
* @param cliServer The CodeQL CLI server to use.
142145
* @param language The language of the query pack to use.
143146
* @param mode The mode to resolve the query for.
147+
* @param queryConstraints Constraints to apply to the query.
144148
* @param additionalPackNames Additional pack names to search.
145149
* @param additionalPackPaths Additional pack paths to search.
146150
*/
147151
async function resolveSuggestionsQuery(
148152
cliServer: CodeQLCliServer,
149153
language: string,
150154
mode: Mode,
155+
queryConstraints: QueryConstraints,
151156
additionalPackNames: string[] = [],
152157
additionalPackPaths: string[] = [],
153158
): Promise<string | undefined> {
@@ -156,14 +161,7 @@ async function resolveSuggestionsQuery(
156161
const queries = await resolveQueriesFromPacks(
157162
cliServer,
158163
packsToSearch,
159-
{
160-
kind: "table",
161-
"tags contain all": [
162-
"modeleditor",
163-
"access-path-suggestions",
164-
modeTag(mode),
165-
],
166-
},
164+
queryConstraints,
167165
additionalPackPaths,
168166
);
169167
if (queries.length > 1) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
278278
<ModelInputSuggestBox
279279
modeledMethod={modeledMethod}
280280
suggestions={inputAccessPathSuggestions}
281+
typePathSuggestions={outputAccessPathSuggestions ?? []}
281282
onChange={modeledMethodChangedHandlers[index]}
282283
/>
283284
)}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
calculateNewProvenance,
55
modeledMethodSupportsInput,
66
} from "../../model-editor/modeled-method";
7-
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
87
import type { AccessPathOption } from "../../model-editor/suggestions";
98
import { SuggestBox } from "../common/SuggestBox";
109
import { useDebounceCallback } from "../common/useDebounceCallback";
@@ -14,10 +13,12 @@ import {
1413
validateAccessPath,
1514
} from "../../model-editor/shared/access-paths";
1615
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
16+
import { ModelTypePathSuggestBox } from "./ModelTypePathSuggestBox";
1717

1818
type Props = {
1919
modeledMethod: ModeledMethod | undefined;
2020
suggestions: AccessPathOption[];
21+
typePathSuggestions: AccessPathOption[];
2122
onChange: (modeledMethod: ModeledMethod) => void;
2223
};
2324

@@ -33,6 +34,7 @@ const getDetails = (option: AccessPathOption) => option.details;
3334
export const ModelInputSuggestBox = ({
3435
modeledMethod,
3536
suggestions,
37+
typePathSuggestions,
3638
onChange,
3739
}: Props) => {
3840
const [value, setValue] = useState<string | undefined>(
@@ -75,7 +77,13 @@ export const ModelInputSuggestBox = ({
7577
);
7678

7779
if (modeledMethod?.type === "type") {
78-
return <ReadonlyDropdown value={modeledMethod.path} aria-label="Path" />;
80+
return (
81+
<ModelTypePathSuggestBox
82+
modeledMethod={modeledMethod}
83+
suggestions={typePathSuggestions}
84+
onChange={onChange}
85+
/>
86+
);
7987
}
8088

8189
return (

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
calculateNewProvenance,
55
modeledMethodSupportsOutput,
66
} from "../../model-editor/modeled-method";
7-
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
87
import type { AccessPathOption } from "../../model-editor/suggestions";
98
import { SuggestBox } from "../common/SuggestBox";
109
import { useDebounceCallback } from "../common/useDebounceCallback";
@@ -14,6 +13,7 @@ import {
1413
validateAccessPath,
1514
} from "../../model-editor/shared/access-paths";
1615
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
16+
import { ModelTypeTextbox } from "./ModelTypeTextbox";
1717

1818
type Props = {
1919
modeledMethod: ModeledMethod | undefined;
@@ -76,8 +76,10 @@ export const ModelOutputSuggestBox = ({
7676

7777
if (modeledMethod?.type === "type") {
7878
return (
79-
<ReadonlyDropdown
80-
value={modeledMethod.relatedTypeName}
79+
<ModelTypeTextbox
80+
modeledMethod={modeledMethod}
81+
typeInfo="relatedTypeName"
82+
onChange={onChange}
8183
aria-label="Related type name"
8284
/>
8385
);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useEffect, useState } from "react";
2+
import type { TypeModeledMethod } from "../../model-editor/modeled-method";
3+
import type { AccessPathOption } from "../../model-editor/suggestions";
4+
import { SuggestBox } from "../common/SuggestBox";
5+
import { useDebounceCallback } from "../common/useDebounceCallback";
6+
import type { AccessPathDiagnostic } from "../../model-editor/shared/access-paths";
7+
import {
8+
parseAccessPathTokens,
9+
validateAccessPath,
10+
} from "../../model-editor/shared/access-paths";
11+
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
12+
13+
type Props = {
14+
modeledMethod: TypeModeledMethod;
15+
suggestions: AccessPathOption[];
16+
onChange: (modeledMethod: TypeModeledMethod) => void;
17+
};
18+
19+
const parseValueToTokens = (value: string) =>
20+
parseAccessPathTokens(value).map((t) => t.text);
21+
22+
const getIcon = (option: AccessPathOption) => (
23+
<ModelSuggestionIcon name={option.icon} />
24+
);
25+
26+
const getDetails = (option: AccessPathOption) => option.details;
27+
28+
export const ModelTypePathSuggestBox = ({
29+
modeledMethod,
30+
suggestions,
31+
onChange,
32+
}: Props) => {
33+
const [value, setValue] = useState<string | undefined>(modeledMethod.path);
34+
35+
useEffect(() => {
36+
setValue(modeledMethod.path);
37+
}, [modeledMethod]);
38+
39+
// Debounce the callback to avoid updating the model too often.
40+
// Not doing this results in a lot of lag when typing.
41+
useDebounceCallback(
42+
value,
43+
(path: string | undefined) => {
44+
if (path === undefined) {
45+
return;
46+
}
47+
48+
onChange({
49+
...modeledMethod,
50+
path,
51+
});
52+
},
53+
500,
54+
);
55+
56+
return (
57+
<SuggestBox<AccessPathOption, AccessPathDiagnostic>
58+
value={value}
59+
options={suggestions}
60+
onChange={setValue}
61+
parseValueToTokens={parseValueToTokens}
62+
validateValue={validateAccessPath}
63+
getIcon={getIcon}
64+
getDetails={getDetails}
65+
aria-label="Path"
66+
/>
67+
);
68+
};

0 commit comments

Comments
 (0)