Merge branch 'main' into koesie10/access-paths-query-bqrs
This commit is contained in:
@@ -1,5 +1,40 @@
|
|||||||
import type { Result } from "sarif";
|
import type { Result } from "sarif";
|
||||||
|
|
||||||
|
function toCanonicalResult(result: Result): Result {
|
||||||
|
const canonicalResult = {
|
||||||
|
...result,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (canonicalResult.locations) {
|
||||||
|
canonicalResult.locations = canonicalResult.locations.map((location) => {
|
||||||
|
if (location.physicalLocation?.artifactLocation?.index !== undefined) {
|
||||||
|
const canonicalLocation = {
|
||||||
|
...location,
|
||||||
|
};
|
||||||
|
|
||||||
|
canonicalLocation.physicalLocation = {
|
||||||
|
...canonicalLocation.physicalLocation,
|
||||||
|
};
|
||||||
|
|
||||||
|
canonicalLocation.physicalLocation.artifactLocation = {
|
||||||
|
...canonicalLocation.physicalLocation.artifactLocation,
|
||||||
|
};
|
||||||
|
|
||||||
|
// The index is dependent on the result of the SARIF file and usually doesn't really tell
|
||||||
|
// us anything useful, so we remove it from the comparison.
|
||||||
|
delete canonicalLocation.physicalLocation.artifactLocation.index;
|
||||||
|
|
||||||
|
return canonicalLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't create a new object if we don't need to
|
||||||
|
return location;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return canonicalResult;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare the alerts of two queries. Use deep equality to determine if
|
* Compare the alerts of two queries. Use deep equality to determine if
|
||||||
* results have been added or removed across two invocations of a query.
|
* results have been added or removed across two invocations of a query.
|
||||||
@@ -25,9 +60,12 @@ export function sarifDiff(fromResults: Result[], toResults: Result[]) {
|
|||||||
throw new Error("CodeQL Compare: Target query has no results.");
|
throw new Error("CodeQL Compare: Target query has no results.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canonicalFromResults = fromResults.map(toCanonicalResult);
|
||||||
|
const canonicalToResults = toResults.map(toCanonicalResult);
|
||||||
|
|
||||||
const results = {
|
const results = {
|
||||||
from: arrayDiff(fromResults, toResults),
|
from: arrayDiff(canonicalFromResults, canonicalToResults),
|
||||||
to: arrayDiff(toResults, fromResults),
|
to: arrayDiff(canonicalToResults, canonicalFromResults),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ type ModelsAsDataLanguageModelGeneration = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ModelsAsDataLanguageAccessPathSuggestions = {
|
type ModelsAsDataLanguageAccessPathSuggestions = {
|
||||||
|
queryConstraints: (mode: Mode) => QueryConstraints;
|
||||||
parseResults: (
|
parseResults: (
|
||||||
// The results of a single predicate of the query.
|
// The results of a single predicate of the query.
|
||||||
bqrs: DecodedBqrsChunk,
|
bqrs: DecodedBqrsChunk,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
rubyPath,
|
rubyPath,
|
||||||
} from "./access-paths";
|
} from "./access-paths";
|
||||||
import { parseAccessPathSuggestionsResults } from "./suggestions";
|
import { parseAccessPathSuggestionsResults } from "./suggestions";
|
||||||
|
import { modeTag } from "../../mode-tag";
|
||||||
|
|
||||||
export const ruby: ModelsAsDataLanguage = {
|
export const ruby: ModelsAsDataLanguage = {
|
||||||
availableModes: [Mode.Framework],
|
availableModes: [Mode.Framework],
|
||||||
@@ -170,6 +171,10 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
parseResults: parseGenerateModelResults,
|
parseResults: parseGenerateModelResults,
|
||||||
},
|
},
|
||||||
accessPathSuggestions: {
|
accessPathSuggestions: {
|
||||||
|
queryConstraints: (mode) => ({
|
||||||
|
kind: "table",
|
||||||
|
"tags contain all": ["modeleditor", "access-paths", modeTag(mode)],
|
||||||
|
}),
|
||||||
parseResults: parseAccessPathSuggestionsResults,
|
parseResults: parseAccessPathSuggestionsResults,
|
||||||
},
|
},
|
||||||
getArgumentOptions: (method) => {
|
getArgumentOptions: (method) => {
|
||||||
|
|||||||
@@ -529,6 +529,7 @@ export class ModelEditorView extends AbstractWebview<
|
|||||||
modelsAsDataLanguage,
|
modelsAsDataLanguage,
|
||||||
this.app.logger,
|
this.app.logger,
|
||||||
),
|
),
|
||||||
|
queryConstraints: accessPathSuggestions.queryConstraints(mode),
|
||||||
cliServer: this.cliServer,
|
cliServer: this.cliServer,
|
||||||
queryRunner: this.queryRunner,
|
queryRunner: this.queryRunner,
|
||||||
queryStorageDir: this.queryStorageDir,
|
queryStorageDir: this.queryStorageDir,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
||||||
import type { Mode } from "./shared/mode";
|
import type { Mode } from "./shared/mode";
|
||||||
|
import type { QueryConstraints } from "../local-queries";
|
||||||
import { resolveQueriesFromPacks } from "../local-queries";
|
import { resolveQueriesFromPacks } from "../local-queries";
|
||||||
import { modeTag } from "./mode-tag";
|
|
||||||
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
|
||||||
import type { NotificationLogger } from "../common/logging";
|
import type { NotificationLogger } from "../common/logging";
|
||||||
import { showAndLogExceptionWithTelemetry } from "../common/logging";
|
import { showAndLogExceptionWithTelemetry } from "../common/logging";
|
||||||
@@ -22,6 +22,7 @@ type RunQueryOptions = {
|
|||||||
parseResults: (
|
parseResults: (
|
||||||
results: DecodedBqrsChunk,
|
results: DecodedBqrsChunk,
|
||||||
) => AccessPathSuggestionRow[] | Promise<AccessPathSuggestionRow[]>;
|
) => AccessPathSuggestionRow[] | Promise<AccessPathSuggestionRow[]>;
|
||||||
|
queryConstraints: QueryConstraints;
|
||||||
|
|
||||||
cliServer: CodeQLCliServer;
|
cliServer: CodeQLCliServer;
|
||||||
queryRunner: QueryRunner;
|
queryRunner: QueryRunner;
|
||||||
@@ -39,6 +40,7 @@ export async function runSuggestionsQuery(
|
|||||||
mode: Mode,
|
mode: Mode,
|
||||||
{
|
{
|
||||||
parseResults,
|
parseResults,
|
||||||
|
queryConstraints,
|
||||||
cliServer,
|
cliServer,
|
||||||
queryRunner,
|
queryRunner,
|
||||||
logger,
|
logger,
|
||||||
@@ -68,6 +70,7 @@ export async function runSuggestionsQuery(
|
|||||||
cliServer,
|
cliServer,
|
||||||
databaseItem.language,
|
databaseItem.language,
|
||||||
mode,
|
mode,
|
||||||
|
queryConstraints,
|
||||||
);
|
);
|
||||||
if (!queryPath) {
|
if (!queryPath) {
|
||||||
void showAndLogExceptionWithTelemetry(
|
void showAndLogExceptionWithTelemetry(
|
||||||
@@ -141,6 +144,7 @@ export async function runSuggestionsQuery(
|
|||||||
* @param cliServer The CodeQL CLI server to use.
|
* @param cliServer The CodeQL CLI server to use.
|
||||||
* @param language The language of the query pack to use.
|
* @param language The language of the query pack to use.
|
||||||
* @param mode The mode to resolve the query for.
|
* @param mode The mode to resolve the query for.
|
||||||
|
* @param queryConstraints Constraints to apply to the query.
|
||||||
* @param additionalPackNames Additional pack names to search.
|
* @param additionalPackNames Additional pack names to search.
|
||||||
* @param additionalPackPaths Additional pack paths to search.
|
* @param additionalPackPaths Additional pack paths to search.
|
||||||
*/
|
*/
|
||||||
@@ -148,6 +152,7 @@ async function resolveSuggestionsQuery(
|
|||||||
cliServer: CodeQLCliServer,
|
cliServer: CodeQLCliServer,
|
||||||
language: string,
|
language: string,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
|
queryConstraints: QueryConstraints,
|
||||||
additionalPackNames: string[] = [],
|
additionalPackNames: string[] = [],
|
||||||
additionalPackPaths: string[] = [],
|
additionalPackPaths: string[] = [],
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
@@ -156,14 +161,7 @@ async function resolveSuggestionsQuery(
|
|||||||
const queries = await resolveQueriesFromPacks(
|
const queries = await resolveQueriesFromPacks(
|
||||||
cliServer,
|
cliServer,
|
||||||
packsToSearch,
|
packsToSearch,
|
||||||
{
|
queryConstraints,
|
||||||
kind: "table",
|
|
||||||
"tags contain all": [
|
|
||||||
"modeleditor",
|
|
||||||
"access-path-suggestions",
|
|
||||||
modeTag(mode),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
additionalPackPaths,
|
additionalPackPaths,
|
||||||
);
|
);
|
||||||
if (queries.length > 1) {
|
if (queries.length > 1) {
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
|
|||||||
<ModelInputSuggestBox
|
<ModelInputSuggestBox
|
||||||
modeledMethod={modeledMethod}
|
modeledMethod={modeledMethod}
|
||||||
suggestions={inputAccessPathSuggestions}
|
suggestions={inputAccessPathSuggestions}
|
||||||
|
typePathSuggestions={outputAccessPathSuggestions ?? []}
|
||||||
onChange={modeledMethodChangedHandlers[index]}
|
onChange={modeledMethodChangedHandlers[index]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
calculateNewProvenance,
|
calculateNewProvenance,
|
||||||
modeledMethodSupportsInput,
|
modeledMethodSupportsInput,
|
||||||
} from "../../model-editor/modeled-method";
|
} from "../../model-editor/modeled-method";
|
||||||
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
|
|
||||||
import type { AccessPathOption } from "../../model-editor/suggestions";
|
import type { AccessPathOption } from "../../model-editor/suggestions";
|
||||||
import { SuggestBox } from "../common/SuggestBox";
|
import { SuggestBox } from "../common/SuggestBox";
|
||||||
import { useDebounceCallback } from "../common/useDebounceCallback";
|
import { useDebounceCallback } from "../common/useDebounceCallback";
|
||||||
@@ -14,10 +13,12 @@ import {
|
|||||||
validateAccessPath,
|
validateAccessPath,
|
||||||
} from "../../model-editor/shared/access-paths";
|
} from "../../model-editor/shared/access-paths";
|
||||||
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
|
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
|
||||||
|
import { ModelTypePathSuggestBox } from "./ModelTypePathSuggestBox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modeledMethod: ModeledMethod | undefined;
|
modeledMethod: ModeledMethod | undefined;
|
||||||
suggestions: AccessPathOption[];
|
suggestions: AccessPathOption[];
|
||||||
|
typePathSuggestions: AccessPathOption[];
|
||||||
onChange: (modeledMethod: ModeledMethod) => void;
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ const getDetails = (option: AccessPathOption) => option.details;
|
|||||||
export const ModelInputSuggestBox = ({
|
export const ModelInputSuggestBox = ({
|
||||||
modeledMethod,
|
modeledMethod,
|
||||||
suggestions,
|
suggestions,
|
||||||
|
typePathSuggestions,
|
||||||
onChange,
|
onChange,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [value, setValue] = useState<string | undefined>(
|
const [value, setValue] = useState<string | undefined>(
|
||||||
@@ -75,7 +77,13 @@ export const ModelInputSuggestBox = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (modeledMethod?.type === "type") {
|
if (modeledMethod?.type === "type") {
|
||||||
return <ReadonlyDropdown value={modeledMethod.path} aria-label="Path" />;
|
return (
|
||||||
|
<ModelTypePathSuggestBox
|
||||||
|
modeledMethod={modeledMethod}
|
||||||
|
suggestions={typePathSuggestions}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
calculateNewProvenance,
|
calculateNewProvenance,
|
||||||
modeledMethodSupportsOutput,
|
modeledMethodSupportsOutput,
|
||||||
} from "../../model-editor/modeled-method";
|
} from "../../model-editor/modeled-method";
|
||||||
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
|
|
||||||
import type { AccessPathOption } from "../../model-editor/suggestions";
|
import type { AccessPathOption } from "../../model-editor/suggestions";
|
||||||
import { SuggestBox } from "../common/SuggestBox";
|
import { SuggestBox } from "../common/SuggestBox";
|
||||||
import { useDebounceCallback } from "../common/useDebounceCallback";
|
import { useDebounceCallback } from "../common/useDebounceCallback";
|
||||||
@@ -14,6 +13,7 @@ import {
|
|||||||
validateAccessPath,
|
validateAccessPath,
|
||||||
} from "../../model-editor/shared/access-paths";
|
} from "../../model-editor/shared/access-paths";
|
||||||
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
|
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
|
||||||
|
import { ModelTypeTextbox } from "./ModelTypeTextbox";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modeledMethod: ModeledMethod | undefined;
|
modeledMethod: ModeledMethod | undefined;
|
||||||
@@ -76,8 +76,10 @@ export const ModelOutputSuggestBox = ({
|
|||||||
|
|
||||||
if (modeledMethod?.type === "type") {
|
if (modeledMethod?.type === "type") {
|
||||||
return (
|
return (
|
||||||
<ReadonlyDropdown
|
<ModelTypeTextbox
|
||||||
value={modeledMethod.relatedTypeName}
|
modeledMethod={modeledMethod}
|
||||||
|
typeInfo="relatedTypeName"
|
||||||
|
onChange={onChange}
|
||||||
aria-label="Related type name"
|
aria-label="Related type name"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import type { TypeModeledMethod } from "../../model-editor/modeled-method";
|
||||||
|
import type { AccessPathOption } from "../../model-editor/suggestions";
|
||||||
|
import { SuggestBox } from "../common/SuggestBox";
|
||||||
|
import { useDebounceCallback } from "../common/useDebounceCallback";
|
||||||
|
import type { AccessPathDiagnostic } from "../../model-editor/shared/access-paths";
|
||||||
|
import {
|
||||||
|
parseAccessPathTokens,
|
||||||
|
validateAccessPath,
|
||||||
|
} from "../../model-editor/shared/access-paths";
|
||||||
|
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
modeledMethod: TypeModeledMethod;
|
||||||
|
suggestions: AccessPathOption[];
|
||||||
|
onChange: (modeledMethod: TypeModeledMethod) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseValueToTokens = (value: string) =>
|
||||||
|
parseAccessPathTokens(value).map((t) => t.text);
|
||||||
|
|
||||||
|
const getIcon = (option: AccessPathOption) => (
|
||||||
|
<ModelSuggestionIcon name={option.icon} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const getDetails = (option: AccessPathOption) => option.details;
|
||||||
|
|
||||||
|
export const ModelTypePathSuggestBox = ({
|
||||||
|
modeledMethod,
|
||||||
|
suggestions,
|
||||||
|
onChange,
|
||||||
|
}: Props) => {
|
||||||
|
const [value, setValue] = useState<string | undefined>(modeledMethod.path);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(modeledMethod.path);
|
||||||
|
}, [modeledMethod]);
|
||||||
|
|
||||||
|
// Debounce the callback to avoid updating the model too often.
|
||||||
|
// Not doing this results in a lot of lag when typing.
|
||||||
|
useDebounceCallback(
|
||||||
|
value,
|
||||||
|
(path: string | undefined) => {
|
||||||
|
if (path === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
...modeledMethod,
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SuggestBox<AccessPathOption, AccessPathDiagnostic>
|
||||||
|
value={value}
|
||||||
|
options={suggestions}
|
||||||
|
onChange={setValue}
|
||||||
|
parseValueToTokens={parseValueToTokens}
|
||||||
|
validateValue={validateAccessPath}
|
||||||
|
getIcon={getIcon}
|
||||||
|
getDetails={getDetails}
|
||||||
|
aria-label="Path"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
183
extensions/ql-vscode/test/unit-tests/compare/sarif-diff.test.ts
Normal file
183
extensions/ql-vscode/test/unit-tests/compare/sarif-diff.test.ts
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import type { Result } from "sarif";
|
||||||
|
import { sarifDiff } from "../../../src/compare/sarif-diff";
|
||||||
|
|
||||||
|
describe("sarifDiff", () => {
|
||||||
|
const result1: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result2: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result2",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result3: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result3",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it("throws an error when the source query has no results", () => {
|
||||||
|
expect(() => sarifDiff([], [result1, result2])).toThrow(
|
||||||
|
"CodeQL Compare: Source query has no results.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error when the target query has no results", () => {
|
||||||
|
expect(() => sarifDiff([result1, result2], [])).toThrow(
|
||||||
|
"CodeQL Compare: Target query has no results.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws an error when there is no overlap between the source and target queries", () => {
|
||||||
|
expect(() => sarifDiff([result1], [result2])).toThrow(
|
||||||
|
"CodeQL Compare: No overlap between the selected queries.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("return an empty comparison when the results are the same", () => {
|
||||||
|
expect(sarifDiff([result1, result2], [result1, result2])).toEqual({
|
||||||
|
from: [],
|
||||||
|
to: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the added and removed results", () => {
|
||||||
|
expect(sarifDiff([result1, result3], [result1, result2])).toEqual({
|
||||||
|
from: [result3],
|
||||||
|
to: [result2],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not use reference equality to compare results", () => {
|
||||||
|
const result = {
|
||||||
|
message: {
|
||||||
|
text: "result1",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(sarifDiff([result], [result1])).toEqual({
|
||||||
|
from: [],
|
||||||
|
to: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not take into account the index of the artifact location", () => {
|
||||||
|
const result1: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result1",
|
||||||
|
},
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
physicalLocation: {
|
||||||
|
artifactLocation: {
|
||||||
|
uri: "file:///path/to/file1",
|
||||||
|
uriBaseId: "%SRCROOT%",
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result2: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result1",
|
||||||
|
},
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
physicalLocation: {
|
||||||
|
artifactLocation: {
|
||||||
|
uri: "file:///path/to/file1",
|
||||||
|
uriBaseId: "%SRCROOT%",
|
||||||
|
index: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(sarifDiff([result1], [result2])).toEqual({
|
||||||
|
from: [],
|
||||||
|
to: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("takes into account the other properties of the artifact location", () => {
|
||||||
|
const result1: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result1",
|
||||||
|
},
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
physicalLocation: {
|
||||||
|
artifactLocation: {
|
||||||
|
uri: "file:///path/to/file1",
|
||||||
|
uriBaseId: "%SRCROOT%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result2: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result1",
|
||||||
|
},
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
physicalLocation: {
|
||||||
|
artifactLocation: {
|
||||||
|
uri: "file:///path/to/file2",
|
||||||
|
uriBaseId: "%SRCROOT%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(sarifDiff([result1], [result1, result2])).toEqual({
|
||||||
|
from: [],
|
||||||
|
to: [result2],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not modify the input", () => {
|
||||||
|
const result1: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result1",
|
||||||
|
},
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
physicalLocation: {
|
||||||
|
artifactLocation: {
|
||||||
|
uri: "file:///path/to/file1",
|
||||||
|
uriBaseId: "%SRCROOT%",
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result1Copy = JSON.parse(JSON.stringify(result1));
|
||||||
|
const result2: Result = {
|
||||||
|
message: {
|
||||||
|
text: "result1",
|
||||||
|
},
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
physicalLocation: {
|
||||||
|
artifactLocation: {
|
||||||
|
uri: "file:///path/to/file1",
|
||||||
|
uriBaseId: "%SRCROOT%",
|
||||||
|
index: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result2Copy = JSON.parse(JSON.stringify(result2));
|
||||||
|
expect(sarifDiff([result1], [result2])).toEqual({
|
||||||
|
from: [],
|
||||||
|
to: [],
|
||||||
|
});
|
||||||
|
expect(result1).toEqual(result1Copy);
|
||||||
|
expect(result2).toEqual(result2Copy);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { load } from "js-yaml";
|
||||||
|
import { readFile } from "fs-extra";
|
||||||
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
import { createMockLogger } from "../../../__mocks__/loggerMock";
|
||||||
import type { DatabaseItem } from "../../../../src/databases/local-databases";
|
import type { DatabaseItem } from "../../../../src/databases/local-databases";
|
||||||
import { DatabaseKind } from "../../../../src/databases/local-databases";
|
import { DatabaseKind } from "../../../../src/databases/local-databases";
|
||||||
@@ -144,15 +146,21 @@ describe("runSuggestionsQuery", () => {
|
|||||||
.mockResolvedValueOnce(mockInputSuggestions)
|
.mockResolvedValueOnce(mockInputSuggestions)
|
||||||
.mockResolvedValueOnce(mockOutputSuggestions);
|
.mockResolvedValueOnce(mockOutputSuggestions);
|
||||||
|
|
||||||
|
const resolveQueriesInSuite = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(["/a/b/c/FrameworkModeAccessPathSuggestions.ql"]);
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
parseResults,
|
parseResults,
|
||||||
|
queryConstraints: {
|
||||||
|
kind: "table",
|
||||||
|
"tags all": ["modeleditor", "access-paths", "ruby", "foo"],
|
||||||
|
},
|
||||||
cliServer: mockedObject<CodeQLCliServer>({
|
cliServer: mockedObject<CodeQLCliServer>({
|
||||||
resolveQlpacks: jest.fn().mockResolvedValue({
|
resolveQlpacks: jest.fn().mockResolvedValue({
|
||||||
"my/extensions": "/a/b/c/",
|
"my/extensions": "/a/b/c/",
|
||||||
}),
|
}),
|
||||||
resolveQueriesInSuite: jest
|
resolveQueriesInSuite,
|
||||||
.fn()
|
|
||||||
.mockResolvedValue(["/a/b/c/FrameworkModeAccessPathSuggestions.ql"]),
|
|
||||||
packPacklist: jest
|
packPacklist: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValue([
|
.mockResolvedValue([
|
||||||
@@ -211,6 +219,20 @@ describe("runSuggestionsQuery", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
expect(options.cliServer.resolveQueriesInSuite).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
load(await readFile(resolveQueriesInSuite.mock.calls[0][0], "utf-8")),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
from: "codeql/ruby-queries",
|
||||||
|
include: {
|
||||||
|
kind: "table",
|
||||||
|
"tags all": ["modeleditor", "access-paths", "ruby", "foo"],
|
||||||
|
},
|
||||||
|
queries: ".",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
expect(options.parseResults).toHaveBeenCalledTimes(2);
|
expect(options.parseResults).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user