diff --git a/extensions/ql-vscode/src/common/interface-types.ts b/extensions/ql-vscode/src/common/interface-types.ts index 083c8a52838..7275f29763e 100644 --- a/extensions/ql-vscode/src/common/interface-types.ts +++ b/extensions/ql-vscode/src/common/interface-types.ts @@ -507,7 +507,7 @@ interface SetMethodsMessage { interface SetModeledMethodsMessage { t: "setModeledMethods"; - methods: Record; + methods: Record; } interface SetModifiedMethodsMessage { diff --git a/extensions/ql-vscode/src/model-editor/method.ts b/extensions/ql-vscode/src/model-editor/method.ts index bc60afd928d..6f2d377be9c 100644 --- a/extensions/ql-vscode/src/model-editor/method.ts +++ b/extensions/ql-vscode/src/model-editor/method.ts @@ -68,12 +68,12 @@ export function getArgumentsList(methodParameters: string): string[] { export function canMethodBeModeled( method: Method, - modeledMethod: ModeledMethod | undefined, + modeledMethods: ModeledMethod[], methodIsUnsaved: boolean, ): boolean { return ( !method.supported || - (modeledMethod && modeledMethod?.type !== "none") || + modeledMethods.some((modeledMethod) => modeledMethod.type !== "none") || methodIsUnsaved ); } diff --git a/extensions/ql-vscode/src/model-editor/model-editor-view.ts b/extensions/ql-vscode/src/model-editor/model-editor-view.ts index 4e0fc8f626e..a39f0498056 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-view.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-view.ts @@ -43,10 +43,7 @@ import { AutoModeler } from "./auto-modeler"; import { telemetryListener } from "../common/vscode/telemetry"; import { ModelingStore } from "./modeling-store"; import { ModelEditorViewTracker } from "./model-editor-view-tracker"; -import { - convertFromLegacyModeledMethod, - convertToLegacyModeledMethods, -} from "./shared/modeled-methods-legacy"; +import { convertFromLegacyModeledMethod } from "./shared/modeled-methods-legacy"; export class ModelEditorView extends AbstractWebview< ToModelEditorMessage, @@ -640,7 +637,7 @@ export class ModelEditorView extends AbstractWebview< if (event.dbUri === this.databaseItem.databaseUri.toString()) { await this.postMessage({ t: "setModeledMethods", - methods: convertToLegacyModeledMethods(event.modeledMethods), + methods: event.modeledMethods, }); } }), diff --git a/extensions/ql-vscode/src/model-editor/shared/modeled-methods-legacy.ts b/extensions/ql-vscode/src/model-editor/shared/modeled-methods-legacy.ts index 7700abdb62e..ea1d7adca82 100644 --- a/extensions/ql-vscode/src/model-editor/shared/modeled-methods-legacy.ts +++ b/extensions/ql-vscode/src/model-editor/shared/modeled-methods-legacy.ts @@ -1,32 +1,5 @@ import { ModeledMethod } from "../modeled-method"; -/** - * Converts a record of a single ModeledMethod indexed by signature to a record of ModeledMethod[] indexed by signature - * for legacy usage. This function should always be used instead of the trivial conversion to track usages of this - * conversion. - * - * This method should only be called inside a `postMessage` call. If it's used anywhere else, consider whether the - * boundary is correct: the boundary should as close as possible to the extension host -> webview boundary. - * - * @param modeledMethods The record of a single ModeledMethod indexed by signature - */ -export function convertToLegacyModeledMethods( - modeledMethods: Record, -): Record { - // Always take the first modeled method in the array - return Object.fromEntries( - Object.entries(modeledMethods) - .map(([signature, modeledMethods]) => { - const modeledMethod = convertToLegacyModeledMethod(modeledMethods); - if (!modeledMethod) { - return null; - } - return [signature, modeledMethod]; - }) - .filter((entry): entry is [string, ModeledMethod] => entry !== null), - ); -} - /** * Converts a single ModeledMethod to a ModeledMethod[] for legacy usage. This function should always be used instead * of the trivial conversion to track usages of this conversion. diff --git a/extensions/ql-vscode/src/stories/model-editor/LibraryRow.stories.tsx b/extensions/ql-vscode/src/stories/model-editor/LibraryRow.stories.tsx index 9e526c724ff..349312d4ee8 100644 --- a/extensions/ql-vscode/src/stories/model-editor/LibraryRow.stories.tsx +++ b/extensions/ql-vscode/src/stories/model-editor/LibraryRow.stories.tsx @@ -146,67 +146,77 @@ LibraryRow.args = { ], }, ], - modeledMethods: { - "org.sql2o.Sql2o#Sql2o(String)": { - type: "sink", - input: "Argument[0]", - output: "", - kind: "jndi-injection", - provenance: "df-generated", - signature: "org.sql2o.Sql2o#Sql2o(String)", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "Sql2o", - methodParameters: "(String)", - }, - "org.sql2o.Connection#createQuery(String)": { - type: "summary", - input: "Argument[this]", - output: "ReturnValue", - kind: "taint", - provenance: "df-manual", - signature: "org.sql2o.Connection#createQuery(String)", - packageName: "org.sql2o", - typeName: "Connection", - methodName: "createQuery", - methodParameters: "(String)", - }, - "org.sql2o.Sql2o#open()": { - type: "summary", - input: "Argument[this]", - output: "ReturnValue", - kind: "taint", - provenance: "manual", - signature: "org.sql2o.Sql2o#open()", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "open", - methodParameters: "()", - }, - "org.sql2o.Query#executeScalar(Class)": { - type: "neutral", - input: "", - output: "", - kind: "", - provenance: "df-generated", - signature: "org.sql2o.Query#executeScalar(Class)", - packageName: "org.sql2o", - typeName: "Query", - methodName: "executeScalar", - methodParameters: "(Class)", - }, - "org.sql2o.Sql2o#Sql2o(String,String,String)": { - type: "neutral", - input: "", - output: "", - kind: "", - provenance: "df-generated", - signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "Sql2o", - methodParameters: "(String,String,String)", - }, + modeledMethodsMap: { + "org.sql2o.Sql2o#Sql2o(String)": [ + { + type: "sink", + input: "Argument[0]", + output: "", + kind: "jndi-injection", + provenance: "df-generated", + signature: "org.sql2o.Sql2o#Sql2o(String)", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "Sql2o", + methodParameters: "(String)", + }, + ], + "org.sql2o.Connection#createQuery(String)": [ + { + type: "summary", + input: "Argument[this]", + output: "ReturnValue", + kind: "taint", + provenance: "df-manual", + signature: "org.sql2o.Connection#createQuery(String)", + packageName: "org.sql2o", + typeName: "Connection", + methodName: "createQuery", + methodParameters: "(String)", + }, + ], + "org.sql2o.Sql2o#open()": [ + { + type: "summary", + input: "Argument[this]", + output: "ReturnValue", + kind: "taint", + provenance: "manual", + signature: "org.sql2o.Sql2o#open()", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "open", + methodParameters: "()", + }, + ], + "org.sql2o.Query#executeScalar(Class)": [ + { + type: "neutral", + input: "", + output: "", + kind: "", + provenance: "df-generated", + signature: "org.sql2o.Query#executeScalar(Class)", + packageName: "org.sql2o", + typeName: "Query", + methodName: "executeScalar", + methodParameters: "(Class)", + }, + ], + "org.sql2o.Sql2o#Sql2o(String,String,String)": [ + { + type: "neutral", + input: "", + output: "", + kind: "", + provenance: "df-generated", + signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "Sql2o", + methodParameters: "(String,String,String)", + }, + ], }, modifiedSignatures: new Set(["org.sql2o.Sql2o#Sql2o(String)"]), inProgressMethods: new InProgressMethods(), diff --git a/extensions/ql-vscode/src/stories/model-editor/ModelEditor.stories.tsx b/extensions/ql-vscode/src/stories/model-editor/ModelEditor.stories.tsx index 1747054b386..396d09a88eb 100644 --- a/extensions/ql-vscode/src/stories/model-editor/ModelEditor.stories.tsx +++ b/extensions/ql-vscode/src/stories/model-editor/ModelEditor.stories.tsx @@ -216,65 +216,75 @@ ModelEditor.args = { }, ], initialModeledMethods: { - "org.sql2o.Sql2o#Sql2o(String)": { - type: "sink", - input: "Argument[0]", - output: "", - kind: "jndi-injection", - provenance: "df-generated", - signature: "org.sql2o.Sql2o#Sql2o(String)", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "Sql2o", - methodParameters: "(String)", - }, - "org.sql2o.Connection#createQuery(String)": { - type: "summary", - input: "Argument[this]", - output: "ReturnValue", - kind: "taint", - provenance: "df-manual", - signature: "org.sql2o.Connection#createQuery(String)", - packageName: "org.sql2o", - typeName: "Connection", - methodName: "createQuery", - methodParameters: "(String)", - }, - "org.sql2o.Sql2o#open()": { - type: "summary", - input: "Argument[this]", - output: "ReturnValue", - kind: "taint", - provenance: "manual", - signature: "org.sql2o.Sql2o#open()", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "open", - methodParameters: "()", - }, - "org.sql2o.Query#executeScalar(Class)": { - type: "neutral", - input: "", - output: "", - kind: "", - provenance: "df-generated", - signature: "org.sql2o.Query#executeScalar(Class)", - packageName: "org.sql2o", - typeName: "Query", - methodName: "executeScalar", - methodParameters: "(Class)", - }, - "org.sql2o.Sql2o#Sql2o(String,String,String)": { - type: "neutral", - input: "", - output: "", - kind: "", - provenance: "df-generated", - signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", - packageName: "org.sql2o", - typeName: "Sql2o", - methodName: "Sql2o", - methodParameters: "(String,String,String)", - }, + "org.sql2o.Sql2o#Sql2o(String)": [ + { + type: "sink", + input: "Argument[0]", + output: "", + kind: "jndi-injection", + provenance: "df-generated", + signature: "org.sql2o.Sql2o#Sql2o(String)", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "Sql2o", + methodParameters: "(String)", + }, + ], + "org.sql2o.Connection#createQuery(String)": [ + { + type: "summary", + input: "Argument[this]", + output: "ReturnValue", + kind: "taint", + provenance: "df-manual", + signature: "org.sql2o.Connection#createQuery(String)", + packageName: "org.sql2o", + typeName: "Connection", + methodName: "createQuery", + methodParameters: "(String)", + }, + ], + "org.sql2o.Sql2o#open()": [ + { + type: "summary", + input: "Argument[this]", + output: "ReturnValue", + kind: "taint", + provenance: "manual", + signature: "org.sql2o.Sql2o#open()", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "open", + methodParameters: "()", + }, + ], + "org.sql2o.Query#executeScalar(Class)": [ + { + type: "neutral", + input: "", + output: "", + kind: "", + provenance: "df-generated", + signature: "org.sql2o.Query#executeScalar(Class)", + packageName: "org.sql2o", + typeName: "Query", + methodName: "executeScalar", + methodParameters: "(Class)", + }, + ], + "org.sql2o.Sql2o#Sql2o(String,String,String)": [ + { + type: "neutral", + input: "", + output: "", + kind: "", + provenance: "df-generated", + signature: "org.sql2o.Sql2o#Sql2o(String,String,String)", + packageName: "org.sql2o", + typeName: "Sql2o", + methodName: "Sql2o", + methodParameters: "(String,String,String)", + }, + ], }, }; diff --git a/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx b/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx index f1a8d8d1353..39e9a81634a 100644 --- a/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx +++ b/extensions/ql-vscode/src/view/method-modeling/MethodModelingView.tsx @@ -89,7 +89,13 @@ export function MethodModelingView({ initialViewState }: Props): JSX.Element { return ; } - if (!canMethodBeModeled(method, modeledMethod, isMethodModified)) { + if ( + !canMethodBeModeled( + method, + convertFromLegacyModeledMethod(modeledMethod), + isMethodModified, + ) + ) { return ; } diff --git a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx index b717feeb240..24614893aff 100644 --- a/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/LibraryRow.tsx @@ -71,7 +71,7 @@ export type LibraryRowProps = { title: string; libraryVersion?: string; methods: Method[]; - modeledMethods: Record; + modeledMethodsMap: Record; modifiedSignatures: Set; inProgressMethods: InProgressMethods; viewState: ModelEditorViewState; @@ -92,7 +92,7 @@ export const LibraryRow = ({ title, libraryVersion, methods, - modeledMethods, + modeledMethodsMap, modifiedSignatures, inProgressMethods, viewState, @@ -231,7 +231,7 @@ export const LibraryRow = ({ ; + initialModeledMethods?: Record; initialHideModeledMethods?: boolean; }; @@ -113,7 +113,7 @@ export function ModelEditor({ }, [hideModeledMethods]); const [modeledMethods, setModeledMethods] = useState< - Record + Record >(initialModeledMethods); useEffect(() => { @@ -329,7 +329,7 @@ export function ModelEditor({ ; + modeledMethodsMap: Record; modifiedSignatures: Set; inProgressMethods: InProgressMethods; viewState: ModelEditorViewState; @@ -30,7 +30,7 @@ export type ModeledMethodDataGridProps = { export const ModeledMethodDataGrid = ({ packageName, methods, - modeledMethods, + modeledMethodsMap, modifiedSignatures, inProgressMethods, viewState, @@ -45,11 +45,11 @@ export const ModeledMethodDataGrid = ({ const methodsWithModelability = []; let numHiddenMethods = 0; for (const method of sortMethods(methods)) { - const modeledMethod = modeledMethods[method.signature]; + const modeledMethods = modeledMethodsMap[method.signature] ?? []; const methodIsUnsaved = modifiedSignatures.has(method.signature); const methodCanBeModeled = canMethodBeModeled( method, - modeledMethod, + modeledMethods, methodIsUnsaved, ); @@ -60,7 +60,7 @@ export const ModeledMethodDataGrid = ({ } } return [methodsWithModelability, numHiddenMethods]; - }, [hideModeledMethods, methods, modeledMethods, modifiedSignatures]); + }, [hideModeledMethods, methods, modeledMethodsMap, modifiedSignatures]); const someMethodsAreVisible = methodsWithModelability.length > 0; @@ -86,13 +86,13 @@ export const ModeledMethodDataGrid = ({ {methodsWithModelability.map(({ method, methodCanBeModeled }) => { - const modeledMethod = modeledMethods[method.signature]; + const modeledMethods = modeledMethodsMap[method.signature] ?? []; return ( ; + modeledMethodsMap: Record; modifiedSignatures: Set; inProgressMethods: InProgressMethods; revealedMethodSignature: string | null; @@ -36,7 +36,7 @@ const libraryNameOverrides: Record = { export const ModeledMethodsList = ({ methods, - modeledMethods, + modeledMethodsMap, modifiedSignatures, inProgressMethods, viewState, @@ -82,7 +82,7 @@ export const ModeledMethodsList = ({ title={libraryNameOverrides[libraryName] ?? libraryName} libraryVersion={libraryVersions[libraryName]} methods={grouped[libraryName]} - modeledMethods={modeledMethods} + modeledMethodsMap={modeledMethodsMap} modifiedSignatures={modifiedSignatures} inProgressMethods={inProgressMethods} viewState={viewState} diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/LibraryRow.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/LibraryRow.spec.tsx index bbbd6c4c09a..77f14c5fd8e 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/LibraryRow.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/LibraryRow.spec.tsx @@ -30,15 +30,17 @@ describe(LibraryRow.name, () => { title="sql2o" libraryVersion="1.6.0" methods={[method]} - modeledMethods={{ - [method.signature]: { - ...method, - type: "sink", - input: "Argument[0]", - output: "", - kind: "jndi-injection", - provenance: "df-generated", - }, + modeledMethodsMap={{ + [method.signature]: [ + { + ...method, + type: "sink", + input: "Argument[0]", + output: "", + kind: "jndi-injection", + provenance: "df-generated", + }, + ], }} modifiedSignatures={new Set([method.signature])} inProgressMethods={new InProgressMethods()} diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodDataGrid.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodDataGrid.spec.tsx index a9ae87ba68d..8320036a2d9 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodDataGrid.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/ModeledMethodDataGrid.spec.tsx @@ -56,15 +56,17 @@ describe(ModeledMethodDataGrid.name, () => { { reactRender(