Merge branch 'main' into robertbrignull/automodel-sort-order

This commit is contained in:
Robert
2024-02-21 10:33:27 +00:00
21 changed files with 667 additions and 155 deletions

View File

@@ -590,6 +590,14 @@ interface StopGeneratingMethodsFromLlmMessage {
packageName: string;
}
interface StartModelEvaluationMessage {
t: "startModelEvaluation";
}
interface StopModelEvaluationMessage {
t: "stopModelEvaluation";
}
interface ModelDependencyMessage {
t: "modelDependency";
}
@@ -648,7 +656,9 @@ export type FromModelEditorMessage =
| StopGeneratingMethodsFromLlmMessage
| ModelDependencyMessage
| HideModeledMethodsMessage
| SetMultipleModeledMethodsMessage;
| SetMultipleModeledMethodsMessage
| StartModelEvaluationMessage
| StopModelEvaluationMessage;
interface RevealInEditorMessage {
t: "revealInModelEditor";

View File

@@ -1,4 +1,29 @@
import type { Result } from "sarif";
import type { Location, Result } from "sarif";
function toCanonicalLocation(location: Location): 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;
}
function toCanonicalResult(result: Result): Result {
const canonicalResult = {
@@ -6,29 +31,45 @@ function toCanonicalResult(result: Result): Result {
};
if (canonicalResult.locations) {
canonicalResult.locations = canonicalResult.locations.map((location) => {
if (location.physicalLocation?.artifactLocation?.index !== undefined) {
const canonicalLocation = {
...location,
canonicalResult.locations =
canonicalResult.locations.map(toCanonicalLocation);
}
if (canonicalResult.relatedLocations) {
canonicalResult.relatedLocations =
canonicalResult.relatedLocations.map(toCanonicalLocation);
}
if (canonicalResult.codeFlows) {
canonicalResult.codeFlows = canonicalResult.codeFlows.map((codeFlow) => {
if (codeFlow.threadFlows) {
return {
...codeFlow,
threadFlows: codeFlow.threadFlows.map((threadFlow) => {
if (threadFlow.locations) {
return {
...threadFlow,
locations: threadFlow.locations.map((threadFlowLocation) => {
if (threadFlowLocation.location) {
return {
...threadFlowLocation,
location: toCanonicalLocation(
threadFlowLocation.location,
),
};
}
return threadFlowLocation;
}),
};
}
return threadFlow;
}),
};
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 codeFlow;
});
}

View File

@@ -715,6 +715,7 @@ const LLM_GENERATION_DEV_ENDPOINT = new Setting(
"llmGenerationDevEndpoint",
MODEL_SETTING,
);
const MODEL_EVALUATION = new Setting("evaluation", MODEL_SETTING);
const EXTENSIONS_DIRECTORY = new Setting("extensionsDirectory", MODEL_SETTING);
const ENABLE_PYTHON = new Setting("enablePython", MODEL_SETTING);
const ENABLE_ACCESS_PATH_SUGGESTIONS = new Setting(
@@ -759,6 +760,10 @@ export class ModelConfigListener extends ConfigListener implements ModelConfig {
return LLM_GENERATION_DEV_ENDPOINT.getValue<string | undefined>();
}
public get modelEvaluation(): boolean {
return !!MODEL_EVALUATION.getValue<boolean>();
}
public getExtensionsDirectory(languageId: string): string | undefined {
return EXTENSIONS_DIRECTORY.getValue<string>({
languageId,

View File

@@ -337,6 +337,12 @@ export class ModelEditorView extends AbstractWebview<
this.setModeledMethods(msg.methodSignature, msg.modeledMethods);
break;
}
case "startModelEvaluation":
this.startModelEvaluation();
break;
case "stopModelEvaluation":
this.stopModelEvaluation();
break;
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
@@ -402,6 +408,8 @@ export class ModelEditorView extends AbstractWebview<
const showLlmButton =
this.databaseItem.language === "java" && this.modelConfig.llmGeneration;
const showEvaluationUi = this.modelConfig.modelEvaluation;
const sourceArchiveAvailable =
this.databaseItem.hasSourceArchiveInExplorer();
@@ -416,6 +424,7 @@ export class ModelEditorView extends AbstractWebview<
language: this.language,
showGenerateButton,
showLlmButton,
showEvaluationUi,
mode: this.modelingStore.getMode(this.databaseItem),
showModeSwitchButton,
sourceArchiveAvailable,
@@ -910,4 +919,12 @@ export class ModelEditorView extends AbstractWebview<
);
this.modelingStore.addModifiedMethod(this.databaseItem, signature);
}
private startModelEvaluation() {
// Do nothing for now. This will be fleshed out in the near future.
}
private stopModelEvaluation() {
// Do nothing for now. This will be fleshed out in the near future.
}
}

View File

@@ -111,19 +111,27 @@ export function modeledMethodSupportsProvenance(
);
}
export function isModelAccepted(
export function isModelPending(
modeledMethod: ModeledMethod | undefined,
modelingStatus: ModelingStatus,
processedByAutoModel?: boolean,
): boolean {
if (!modeledMethod) {
if (
(!modeledMethod || modeledMethod.type === "none") &&
processedByAutoModel
) {
return true;
}
if (!modeledMethod) {
return false;
}
return (
modelingStatus !== "unsaved" ||
modeledMethod.type === "none" ||
!modeledMethodSupportsProvenance(modeledMethod) ||
modeledMethod.provenance !== "ai-generated"
modelingStatus === "unsaved" &&
modeledMethod.type !== "none" &&
modeledMethodSupportsProvenance(modeledMethod) &&
modeledMethod.provenance === "ai-generated"
);
}

View File

@@ -7,6 +7,7 @@ export interface ModelEditorViewState {
language: QueryLanguage;
showGenerateButton: boolean;
showLlmButton: boolean;
showEvaluationUi: boolean;
mode: Mode;
showModeSwitchButton: boolean;
sourceArchiveAvailable: boolean;

View File

@@ -66,5 +66,5 @@ export const ModelingNotAccepted = Template.bind({});
ModelingNotAccepted.args = {
method,
modeledMethod: generatedModeledMethod,
modelingStatus: "unsaved",
modelPending: true,
};

View File

@@ -7,7 +7,6 @@ import { ModelOutputDropdown } from "../model-editor/ModelOutputDropdown";
import { ModelKindDropdown } from "../model-editor/ModelKindDropdown";
import { InProgressDropdown } from "../model-editor/InProgressDropdown";
import type { QueryLanguage } from "../../common/query-language";
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
const Container = styled.div`
padding-top: 0.5rem;
@@ -27,7 +26,7 @@ export type MethodModelingInputsProps = {
language: QueryLanguage;
method: Method;
modeledMethod: ModeledMethod | undefined;
modelingStatus: ModelingStatus;
modelPending: boolean;
isModelingInProgress: boolean;
onChange: (modeledMethod: ModeledMethod) => void;
};
@@ -36,7 +35,7 @@ export const MethodModelingInputs = ({
language,
method,
modeledMethod,
modelingStatus,
modelPending,
isModelingInProgress,
onChange,
}: MethodModelingInputsProps): React.JSX.Element => {
@@ -44,7 +43,7 @@ export const MethodModelingInputs = ({
language,
method,
modeledMethod,
modelingStatus,
modelPending,
onChange,
};

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { Method } from "../../model-editor/method";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import { isModelPending } from "../../model-editor/modeled-method";
import {
canAddNewModeledMethod,
canRemoveModeledMethod,
@@ -156,7 +157,10 @@ export const MultipleModeledMethodsPanel = ({
language={language}
method={method}
modeledMethod={modeledMethods[selectedIndex]}
modelingStatus={modelingStatus}
modelPending={isModelPending(
modeledMethods[selectedIndex],
modelingStatus,
)}
isModelingInProgress={isModelingInProgress}
onChange={handleChange}
/>
@@ -165,7 +169,7 @@ export const MultipleModeledMethodsPanel = ({
language={language}
method={method}
modeledMethod={undefined}
modelingStatus={modelingStatus}
modelPending={false}
isModelingInProgress={isModelingInProgress}
onChange={handleChange}
/>

View File

@@ -17,7 +17,7 @@ describe(MethodModelingInputs.name, () => {
const language = QueryLanguage.Java;
const method = createMethod();
const modeledMethod = createSinkModeledMethod();
const modelingStatus = "unmodeled";
const modelPending = false;
const isModelingInProgress = false;
const onChange = jest.fn();
@@ -26,7 +26,7 @@ describe(MethodModelingInputs.name, () => {
language,
method,
modeledMethod,
modelingStatus,
modelPending,
isModelingInProgress,
onChange,
});
@@ -53,7 +53,7 @@ describe(MethodModelingInputs.name, () => {
language,
method,
modeledMethod,
modelingStatus,
modelPending,
isModelingInProgress,
onChange,
});
@@ -76,7 +76,7 @@ describe(MethodModelingInputs.name, () => {
language,
method,
modeledMethod,
modelingStatus,
modelPending,
isModelingInProgress,
onChange,
});
@@ -91,7 +91,7 @@ describe(MethodModelingInputs.name, () => {
language={language}
method={method}
modeledMethod={updatedModeledMethod}
modelingStatus={modelingStatus}
modelPending={modelPending}
isModelingInProgress={isModelingInProgress}
onChange={onChange}
/>,
@@ -121,7 +121,7 @@ describe(MethodModelingInputs.name, () => {
language,
method,
modeledMethod,
modelingStatus,
modelPending,
isModelingInProgress: true,
onChange,
});

View File

@@ -1,6 +1,6 @@
import { styled } from "styled-components";
import { Dropdown } from "../common/Dropdown";
export const InputDropdown = styled(Dropdown)<{ $accepted: boolean }>`
font-style: ${(props) => (props.$accepted ? "normal" : "italic")};
export const InputDropdown = styled(Dropdown)<{ $pending: boolean }>`
font-style: ${(props) => (props.$pending ? "italic" : "normal")};
`;

View File

@@ -16,6 +16,7 @@ import { vscode } from "../vscode-api";
import type { Method } from "../../model-editor/method";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import { isModelPending } from "../../model-editor/modeled-method";
import { ModelKindDropdown } from "./ModelKindDropdown";
import { Mode } from "../../model-editor/shared/mode";
import { MethodClassifications } from "./MethodClassifications";
@@ -112,6 +113,7 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
modeledMethods: modeledMethodsProp,
methodIsUnsaved,
methodIsSelected,
processedByAutoModel,
viewState,
revealedMethodSignature,
inputAccessPathSuggestions,
@@ -255,88 +257,96 @@ const ModelableMethodRow = forwardRef<HTMLElement | undefined, MethodRowProps>(
)}
{!props.modelingInProgress && (
<>
{modeledMethods.map((modeledMethod, index) => (
<DataGridRow key={index} focused={focusedIndex === index}>
<DataGridCell>
<ModelTypeDropdown
language={viewState.language}
method={method}
modeledMethod={modeledMethod}
modelingStatus={modelingStatus}
onChange={modeledMethodChangedHandlers[index]}
/>
</DataGridCell>
<DataGridCell>
{inputAccessPathSuggestions === undefined ? (
<ModelInputDropdown
{modeledMethods.map((modeledMethod, index) => {
const modelPending = isModelPending(
modeledMethod,
modelingStatus,
processedByAutoModel,
);
return (
<DataGridRow key={index} focused={focusedIndex === index}>
<DataGridCell>
<ModelTypeDropdown
language={viewState.language}
method={method}
modeledMethod={modeledMethod}
modelingStatus={modelingStatus}
modelPending={modelPending}
onChange={modeledMethodChangedHandlers[index]}
/>
) : (
<ModelInputSuggestBox
modeledMethod={modeledMethod}
suggestions={inputAccessPathSuggestions}
typePathSuggestions={outputAccessPathSuggestions ?? []}
onChange={modeledMethodChangedHandlers[index]}
/>
)}
</DataGridCell>
<DataGridCell>
{outputAccessPathSuggestions === undefined ? (
<ModelOutputDropdown
</DataGridCell>
<DataGridCell>
{inputAccessPathSuggestions === undefined ? (
<ModelInputDropdown
language={viewState.language}
method={method}
modeledMethod={modeledMethod}
modelPending={modelPending}
onChange={modeledMethodChangedHandlers[index]}
/>
) : (
<ModelInputSuggestBox
modeledMethod={modeledMethod}
suggestions={inputAccessPathSuggestions}
typePathSuggestions={outputAccessPathSuggestions ?? []}
onChange={modeledMethodChangedHandlers[index]}
/>
)}
</DataGridCell>
<DataGridCell>
{outputAccessPathSuggestions === undefined ? (
<ModelOutputDropdown
language={viewState.language}
method={method}
modeledMethod={modeledMethod}
modelPending={modelPending}
onChange={modeledMethodChangedHandlers[index]}
/>
) : (
<ModelOutputSuggestBox
modeledMethod={modeledMethod}
suggestions={outputAccessPathSuggestions}
onChange={modeledMethodChangedHandlers[index]}
/>
)}
</DataGridCell>
<DataGridCell>
<ModelKindDropdown
language={viewState.language}
method={method}
modeledMethod={modeledMethod}
modelingStatus={modelingStatus}
modelPending={modelPending}
onChange={modeledMethodChangedHandlers[index]}
/>
) : (
<ModelOutputSuggestBox
modeledMethod={modeledMethod}
suggestions={outputAccessPathSuggestions}
onChange={modeledMethodChangedHandlers[index]}
/>
)}
</DataGridCell>
<DataGridCell>
<ModelKindDropdown
language={viewState.language}
modeledMethod={modeledMethod}
modelingStatus={modelingStatus}
onChange={modeledMethodChangedHandlers[index]}
/>
</DataGridCell>
<DataGridCell>
{index === 0 ? (
<CodiconRow
appearance="icon"
aria-label="Add new model"
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
handleAddModelClick();
}}
disabled={addModelButtonDisabled}
>
<Codicon name="add" />
</CodiconRow>
) : (
<CodiconRow
appearance="icon"
aria-label="Remove model"
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
removeModelClickedHandlers[index]();
}}
>
<Codicon name="trash" />
</CodiconRow>
)}
</DataGridCell>
</DataGridRow>
))}
</DataGridCell>
<DataGridCell>
{index === 0 ? (
<CodiconRow
appearance="icon"
aria-label="Add new model"
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
handleAddModelClick();
}}
disabled={addModelButtonDisabled}
>
<Codicon name="add" />
</CodiconRow>
) : (
<CodiconRow
appearance="icon"
aria-label="Remove model"
onClick={(event: React.MouseEvent) => {
event.stopPropagation();
removeModelClickedHandlers[index]();
}}
>
<Codicon name="trash" />
</CodiconRow>
)}
</DataGridCell>
</DataGridRow>
);
})}
{validationErrors.map((error, index) => (
<DataGridCell gridColumn="span 5" key={index}>
<ModeledMethodAlert

View File

@@ -3,6 +3,7 @@ import type { ToModelEditorMessage } from "../../common/interface-types";
import {
VSCodeButton,
VSCodeCheckbox,
VSCodeProgressRing,
VSCodeTag,
} from "@vscode/webview-ui-toolkit/react";
import { styled } from "styled-components";
@@ -74,6 +75,57 @@ const ButtonsContainer = styled.div`
margin-top: 1rem;
`;
const ProgressRing = styled(VSCodeProgressRing)`
width: 16px;
height: 16px;
margin-right: 5px;
`;
const ModelEvaluation = ({
viewState,
modeledMethods,
modifiedSignatures,
onStartEvaluation,
onStopEvaluation,
evaluationInProgress,
}: {
viewState: ModelEditorViewState;
modeledMethods: Record<string, ModeledMethod[]>;
modifiedSignatures: Set<string>;
onStartEvaluation: () => void;
onStopEvaluation: () => void;
evaluationInProgress: boolean;
}) => {
if (!viewState.showEvaluationUi) {
return null;
}
if (!evaluationInProgress) {
const customModelsExist = Object.values(modeledMethods).some(
(methods) => methods.filter((m) => m.type !== "none").length > 0,
);
const unsavedChanges = modifiedSignatures.size > 0;
return (
<VSCodeButton
onClick={onStartEvaluation}
appearance="secondary"
disabled={!customModelsExist || unsavedChanges}
>
Evaluate
</VSCodeButton>
);
} else {
return (
<VSCodeButton onClick={onStopEvaluation} appearance="secondary">
<ProgressRing />
Stop evaluation
</VSCodeButton>
);
}
};
type Props = {
initialViewState?: ModelEditorViewState;
initialMethods?: Method[];
@@ -114,6 +166,8 @@ export function ModelEditor({
string | null
>(null);
const [evaluationInProgress, setEvaluationInProgress] = useState(false);
useEffect(() => {
vscode.postMessage({
t: "hideModeledMethods",
@@ -254,6 +308,20 @@ export function ModelEditor({
[selectedSignatures],
);
const onStartEvaluation = useCallback(() => {
setEvaluationInProgress(true);
vscode.postMessage({
t: "startModelEvaluation",
});
}, []);
const onStopEvaluation = useCallback(() => {
setEvaluationInProgress(false);
vscode.postMessage({
t: "stopModelEvaluation",
});
}, []);
const onGenerateFromSourceClick = useCallback(() => {
vscode.postMessage({
t: "generateMethod",
@@ -373,6 +441,14 @@ export function ModelEditor({
Generate
</VSCodeButton>
)}
<ModelEvaluation
viewState={viewState}
modeledMethods={modeledMethods}
modifiedSignatures={modifiedSignatures}
onStartEvaluation={onStartEvaluation}
onStopEvaluation={onStopEvaluation}
evaluationInProgress={evaluationInProgress}
/>
</ButtonsContainer>
</HeaderRow>
</HeaderColumn>

View File

@@ -3,13 +3,11 @@ import { useCallback, useMemo } from "react";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import {
calculateNewProvenance,
isModelAccepted,
modeledMethodSupportsInput,
} from "../../model-editor/modeled-method";
import type { Method } from "../../model-editor/method";
import type { QueryLanguage } from "../../common/query-language";
import { getModelsAsDataLanguage } from "../../model-editor/languages";
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
import { InputDropdown } from "./InputDropdown";
import { ModelTypeTextbox } from "./ModelTypeTextbox";
@@ -17,7 +15,7 @@ type Props = {
language: QueryLanguage;
method: Method;
modeledMethod: ModeledMethod | undefined;
modelingStatus: ModelingStatus;
modelPending: boolean;
onChange: (modeledMethod: ModeledMethod) => void;
};
@@ -25,7 +23,7 @@ export const ModelInputDropdown = ({
language,
method,
modeledMethod,
modelingStatus,
modelPending,
onChange,
}: Props): React.JSX.Element => {
const options = useMemo(() => {
@@ -77,14 +75,12 @@ export const ModelInputDropdown = ({
);
}
const modelAccepted = isModelAccepted(modeledMethod, modelingStatus);
return (
<InputDropdown
value={value}
options={options}
disabled={!enabled}
$accepted={modelAccepted}
$pending={modelPending}
onChange={handleChange}
aria-label="Input"
/>

View File

@@ -6,25 +6,23 @@ import type {
} from "../../model-editor/modeled-method";
import {
modeledMethodSupportsKind,
isModelAccepted,
calculateNewProvenance,
} from "../../model-editor/modeled-method";
import { getModelsAsDataLanguage } from "../../model-editor/languages";
import type { QueryLanguage } from "../../common/query-language";
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
import { InputDropdown } from "./InputDropdown";
type Props = {
language: QueryLanguage;
modeledMethod: ModeledMethod | undefined;
modelingStatus: ModelingStatus;
modelPending: boolean;
onChange: (modeledMethod: ModeledMethod) => void;
};
export const ModelKindDropdown = ({
language,
modeledMethod,
modelingStatus,
modelPending,
onChange,
}: Props) => {
const predicate = useMemo(() => {
@@ -89,14 +87,12 @@ export const ModelKindDropdown = ({
}
}, [modeledMethod, value, kinds, onChangeKind]);
const modelAccepted = isModelAccepted(modeledMethod, modelingStatus);
return (
<InputDropdown
value={value}
options={options}
disabled={disabled}
$accepted={modelAccepted}
$pending={modelPending}
onChange={handleChange}
aria-label="Kind"
/>

View File

@@ -3,13 +3,11 @@ import { useCallback, useMemo } from "react";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import {
calculateNewProvenance,
isModelAccepted,
modeledMethodSupportsOutput,
} from "../../model-editor/modeled-method";
import type { Method } from "../../model-editor/method";
import { getModelsAsDataLanguage } from "../../model-editor/languages";
import type { QueryLanguage } from "../../common/query-language";
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
import { InputDropdown } from "./InputDropdown";
import { ModelTypeTextbox } from "./ModelTypeTextbox";
@@ -17,7 +15,7 @@ type Props = {
language: QueryLanguage;
method: Method;
modeledMethod: ModeledMethod | undefined;
modelingStatus: ModelingStatus;
modelPending: boolean;
onChange: (modeledMethod: ModeledMethod) => void;
};
@@ -25,7 +23,7 @@ export const ModelOutputDropdown = ({
language,
method,
modeledMethod,
modelingStatus,
modelPending,
onChange,
}: Props): React.JSX.Element => {
const options = useMemo(() => {
@@ -78,14 +76,12 @@ export const ModelOutputDropdown = ({
);
}
const modelAccepted = isModelAccepted(modeledMethod, modelingStatus);
return (
<InputDropdown
value={value}
options={options}
disabled={!enabled}
$accepted={modelAccepted}
$pending={modelPending}
onChange={handleChange}
aria-label="Output"
/>

View File

@@ -4,10 +4,7 @@ import type {
ModeledMethod,
ModeledMethodType,
} from "../../model-editor/modeled-method";
import {
calculateNewProvenance,
isModelAccepted,
} from "../../model-editor/modeled-method";
import { calculateNewProvenance } from "../../model-editor/modeled-method";
import type { Method } from "../../model-editor/method";
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
import type { Mutable } from "../../common/mutable";
@@ -15,14 +12,13 @@ import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
import type { QueryLanguage } from "../../common/query-language";
import type { ModelsAsDataLanguagePredicates } from "../../model-editor/languages";
import { getModelsAsDataLanguage } from "../../model-editor/languages";
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
import { InputDropdown } from "./InputDropdown";
type Props = {
language: QueryLanguage;
method: Method;
modeledMethod: ModeledMethod | undefined;
modelingStatus: ModelingStatus;
modelPending: boolean;
onChange: (modeledMethod: ModeledMethod) => void;
};
@@ -40,7 +36,7 @@ export const ModelTypeDropdown = ({
language,
method,
modeledMethod,
modelingStatus,
modelPending,
onChange,
}: Props): React.JSX.Element => {
const options = useMemo(() => {
@@ -114,13 +110,11 @@ export const ModelTypeDropdown = ({
);
}
const modelAccepted = isModelAccepted(modeledMethod, modelingStatus);
return (
<InputDropdown
value={modeledMethod?.type ?? "none"}
options={options}
$accepted={modelAccepted}
$pending={modelPending}
onChange={handleChange}
aria-label="Model type"
/>

View File

@@ -24,7 +24,7 @@ describe(ModelKindDropdown.name, () => {
<ModelKindDropdown
language={QueryLanguage.Java}
modeledMethod={modeledMethod}
modelingStatus="unsaved"
modelPending={false}
onChange={onChange}
/>,
);
@@ -47,7 +47,7 @@ describe(ModelKindDropdown.name, () => {
<ModelKindDropdown
language={QueryLanguage.Java}
modeledMethod={modeledMethod}
modelingStatus="unsaved"
modelPending={false}
onChange={onChange}
/>,
);
@@ -64,7 +64,7 @@ describe(ModelKindDropdown.name, () => {
<ModelKindDropdown
language={QueryLanguage.Java}
modeledMethod={updatedModeledMethod}
modelingStatus="unsaved"
modelPending={false}
onChange={onChange}
/>,
);
@@ -82,7 +82,7 @@ describe(ModelKindDropdown.name, () => {
<ModelKindDropdown
language={QueryLanguage.Java}
modeledMethod={modeledMethod}
modelingStatus="unsaved"
modelPending={false}
onChange={onChange}
/>,
);
@@ -102,7 +102,7 @@ describe(ModelKindDropdown.name, () => {
<ModelKindDropdown
language={QueryLanguage.Java}
modeledMethod={modeledMethod}
modelingStatus="unsaved"
modelPending={false}
onChange={onChange}
/>,
);

View File

@@ -20,7 +20,7 @@ describe(ModelTypeDropdown.name, () => {
<ModelTypeDropdown
language={QueryLanguage.Java}
modeledMethod={modeledMethod}
modelingStatus="unsaved"
modelPending={false}
onChange={onChange}
method={method}
/>,
@@ -42,7 +42,7 @@ describe(ModelTypeDropdown.name, () => {
<ModelTypeDropdown
language={QueryLanguage.Ruby}
modeledMethod={modeledMethod}
modelingStatus="unsaved"
modelPending={false}
onChange={onChange}
method={method}
/>,
@@ -64,7 +64,7 @@ describe(ModelTypeDropdown.name, () => {
<ModelTypeDropdown
language={QueryLanguage.Java}
modeledMethod={modeledMethod}
modelingStatus="unsaved"
modelPending={false}
onChange={onChange}
method={method}
/>,

View File

@@ -11,6 +11,7 @@ export function createMockModelEditorViewState(
mode: Mode.Application,
showGenerateButton: false,
showLlmButton: false,
showEvaluationUi: false,
showModeSwitchButton: true,
extensionPack: createMockExtensionPack(),
sourceArchiveAvailable: true,

View File

@@ -138,6 +138,364 @@ describe("sarifDiff", () => {
});
});
it("does not take into account the location index when in thread flows or related locations", () => {
const result1: Result = {
ruleId: "java/static-initialization-vector",
ruleIndex: 0,
rule: {
id: "java/static-initialization-vector",
index: 0,
},
message: {
text: "A [static initialization vector](1) should not be used for encryption.",
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 126,
},
region: {
startLine: 1272,
startColumn: 55,
endColumn: 61,
},
},
},
],
partialFingerprints: {
primaryLocationLineHash: "9a2a0c085da38206:3",
primaryLocationStartColumnFingerprint: "38",
},
codeFlows: [
{
threadFlows: [
{
locations: [
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 126,
},
region: {
startLine: 1270,
startColumn: 50,
endColumn: 76,
},
},
message: {
text: "new byte[] : byte[]",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/javax/crypto/spec/IvParameterSpec.java",
uriBaseId: "%SRCROOT%",
index: 12,
},
region: {
startLine: 52,
startColumn: 28,
endColumn: 37,
},
},
message: {
text: "iv : byte[]",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/javax/crypto/spec/IvParameterSpec.java",
uriBaseId: "%SRCROOT%",
index: 12,
},
region: {
startLine: 53,
startColumn: 14,
endColumn: 16,
},
},
message: {
text: "iv : byte[]",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/javax/crypto/spec/IvParameterSpec.java",
uriBaseId: "%SRCROOT%",
index: 12,
},
region: {
startLine: 53,
startColumn: 9,
endColumn: 32,
},
},
message: {
text: "this <constr(this)> [post update] : IvParameterSpec",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 126,
},
region: {
startLine: 1270,
startColumn: 30,
endColumn: 77,
},
},
message: {
text: "new IvParameterSpec(...) : IvParameterSpec",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 126,
},
region: {
startLine: 1272,
startColumn: 55,
endColumn: 61,
},
},
message: {
text: "params",
},
},
},
],
},
],
},
],
relatedLocations: [
{
id: 1,
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 126,
},
region: {
startLine: 1270,
startColumn: 50,
endColumn: 76,
},
},
message: {
text: "static initialization vector",
},
},
],
};
const result2: Result = {
ruleId: "java/static-initialization-vector",
ruleIndex: 0,
rule: {
id: "java/static-initialization-vector",
index: 0,
},
message: {
text: "A [static initialization vector](1) should not be used for encryption.",
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 141,
},
region: {
startLine: 1272,
startColumn: 55,
endColumn: 61,
},
},
},
],
partialFingerprints: {
primaryLocationLineHash: "9a2a0c085da38206:3",
primaryLocationStartColumnFingerprint: "38",
},
codeFlows: [
{
threadFlows: [
{
locations: [
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 141,
},
region: {
startLine: 1270,
startColumn: 50,
endColumn: 76,
},
},
message: {
text: "new byte[] : byte[]",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/javax/crypto/spec/IvParameterSpec.java",
uriBaseId: "%SRCROOT%",
index: 12,
},
region: {
startLine: 52,
startColumn: 28,
endColumn: 37,
},
},
message: {
text: "iv : byte[]",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/javax/crypto/spec/IvParameterSpec.java",
uriBaseId: "%SRCROOT%",
index: 12,
},
region: {
startLine: 53,
startColumn: 14,
endColumn: 16,
},
},
message: {
text: "iv : byte[]",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/javax/crypto/spec/IvParameterSpec.java",
uriBaseId: "%SRCROOT%",
index: 12,
},
region: {
startLine: 53,
startColumn: 9,
endColumn: 32,
},
},
message: {
text: "this <constr(this)> [post update] : IvParameterSpec",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 141,
},
region: {
startLine: 1270,
startColumn: 30,
endColumn: 77,
},
},
message: {
text: "new IvParameterSpec(...) : IvParameterSpec",
},
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 141,
},
region: {
startLine: 1272,
startColumn: 55,
endColumn: 61,
},
},
message: {
text: "params",
},
},
},
],
},
],
},
],
relatedLocations: [
{
id: 1,
physicalLocation: {
artifactLocation: {
uri: "src/java.base/share/classes/sun/security/ssl/SSLCipher.java",
uriBaseId: "%SRCROOT%",
index: 141,
},
region: {
startLine: 1270,
startColumn: 50,
endColumn: 76,
},
},
message: {
text: "static initialization vector",
},
},
],
};
expect(sarifDiff([result1], [result2])).toEqual({
from: [],
to: [],
});
});
it("does not modify the input", () => {
const result1: Result = {
message: {