Add supported endpoint types
This commit is contained in:
@@ -33,7 +33,7 @@ export function decodeBqrsToMethods(
|
|||||||
let libraryVersion: string | undefined;
|
let libraryVersion: string | undefined;
|
||||||
let type: ModeledMethodType;
|
let type: ModeledMethodType;
|
||||||
let classification: CallClassification;
|
let classification: CallClassification;
|
||||||
let endpointType = EndpointType.Method;
|
let endpointType: EndpointType | undefined = undefined;
|
||||||
|
|
||||||
if (mode === Mode.Application) {
|
if (mode === Mode.Application) {
|
||||||
[
|
[
|
||||||
@@ -67,8 +67,19 @@ export function decodeBqrsToMethods(
|
|||||||
type = "none";
|
type = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (methodName === "") {
|
if (definition.endpointTypeForEndpoint) {
|
||||||
endpointType = EndpointType.Class;
|
endpointType = definition.endpointTypeForEndpoint({
|
||||||
|
endpointType,
|
||||||
|
packageName,
|
||||||
|
typeName,
|
||||||
|
methodName,
|
||||||
|
methodParameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpointType === undefined) {
|
||||||
|
endpointType =
|
||||||
|
methodName === "" ? EndpointType.Class : EndpointType.Method;
|
||||||
}
|
}
|
||||||
|
|
||||||
const signature = definition.createMethodSignature({
|
const signature = definition.createMethodSignature({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { MethodArgument, MethodDefinition } from "../method";
|
import type { EndpointType, MethodArgument, MethodDefinition } from "../method";
|
||||||
import type {
|
import type {
|
||||||
ModeledMethod,
|
ModeledMethod,
|
||||||
NeutralModeledMethod,
|
NeutralModeledMethod,
|
||||||
@@ -23,6 +23,11 @@ type ReadModeledMethod = (row: DataTuple[]) => ModeledMethod;
|
|||||||
export type ModelsAsDataLanguagePredicate<T> = {
|
export type ModelsAsDataLanguagePredicate<T> = {
|
||||||
extensiblePredicate: string;
|
extensiblePredicate: string;
|
||||||
supportedKinds?: string[];
|
supportedKinds?: string[];
|
||||||
|
/**
|
||||||
|
* The endpoint types that this predicate supports. If not specified, the predicate supports all
|
||||||
|
* endpoint types.
|
||||||
|
*/
|
||||||
|
supportedEndpointTypes?: EndpointType[];
|
||||||
generateMethodDefinition: GenerateMethodDefinition<T>;
|
generateMethodDefinition: GenerateMethodDefinition<T>;
|
||||||
readModeledMethod: ReadModeledMethod;
|
readModeledMethod: ReadModeledMethod;
|
||||||
};
|
};
|
||||||
@@ -76,6 +81,18 @@ export type ModelsAsDataLanguage = {
|
|||||||
*/
|
*/
|
||||||
availableModes?: Mode[];
|
availableModes?: Mode[];
|
||||||
createMethodSignature: (method: MethodDefinition) => string;
|
createMethodSignature: (method: MethodDefinition) => string;
|
||||||
|
/**
|
||||||
|
* This allows modifying the endpoint type automatically assigned to an endpoint. The default
|
||||||
|
* endpoint type is undefined, and if this method returns undefined, the default endpoint type will
|
||||||
|
* be determined by heuristics.
|
||||||
|
* @param method The method to get the endpoint type for. The endpoint type can be undefined if the
|
||||||
|
* query does not return an endpoint type.
|
||||||
|
*/
|
||||||
|
endpointTypeForEndpoint?: (
|
||||||
|
method: Omit<MethodDefinition, "endpointType"> & {
|
||||||
|
endpointType: EndpointType | undefined;
|
||||||
|
},
|
||||||
|
) => EndpointType | undefined;
|
||||||
predicates: ModelsAsDataLanguagePredicates;
|
predicates: ModelsAsDataLanguagePredicates;
|
||||||
modelGeneration?: ModelsAsDataLanguageModelGeneration;
|
modelGeneration?: ModelsAsDataLanguageModelGeneration;
|
||||||
accessPathSuggestions?: ModelsAsDataLanguageAccessPathSuggestions;
|
accessPathSuggestions?: ModelsAsDataLanguageAccessPathSuggestions;
|
||||||
|
|||||||
@@ -64,6 +64,27 @@ export function rubyPath(methodName: string, path: string) {
|
|||||||
return `${methodPath}.${path}`;
|
return `${methodPath}.${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rubyEndpointType(methodName: string) {
|
/** For the purpose of the model editor, we are defining the endpoint types as follows:
|
||||||
return methodName === "" ? EndpointType.Class : EndpointType.Method;
|
* - Class: A class instance
|
||||||
|
* - Module: The class itself
|
||||||
|
* - Method: A method in a class
|
||||||
|
* - Constructor: A constructor method
|
||||||
|
* @param typeName
|
||||||
|
* @param methodName
|
||||||
|
*/
|
||||||
|
export function rubyEndpointType(typeName: string, methodName: string) {
|
||||||
|
if (typeName.endsWith("!") && methodName === "new") {
|
||||||
|
// This is a constructor
|
||||||
|
return EndpointType.Constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeName.endsWith("!") && methodName === "") {
|
||||||
|
return EndpointType.Module;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodName === "") {
|
||||||
|
return EndpointType.Class;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EndpointType.Method;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { sharedExtensiblePredicates, sharedKinds } from "../shared";
|
|||||||
import { Mode } from "../../shared/mode";
|
import { Mode } from "../../shared/mode";
|
||||||
import { parseGenerateModelResults } from "./generate";
|
import { parseGenerateModelResults } from "./generate";
|
||||||
import type { MethodArgument } from "../../method";
|
import type { MethodArgument } from "../../method";
|
||||||
import { getArgumentsList } from "../../method";
|
import { EndpointType, getArgumentsList } from "../../method";
|
||||||
import {
|
import {
|
||||||
parseRubyAccessPath,
|
parseRubyAccessPath,
|
||||||
parseRubyMethodFromPath,
|
parseRubyMethodFromPath,
|
||||||
@@ -19,10 +19,13 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
availableModes: [Mode.Framework],
|
availableModes: [Mode.Framework],
|
||||||
createMethodSignature: ({ typeName, methodName }) =>
|
createMethodSignature: ({ typeName, methodName }) =>
|
||||||
`${typeName}#${methodName}`,
|
`${typeName}#${methodName}`,
|
||||||
|
endpointTypeForEndpoint: ({ typeName, methodName }) =>
|
||||||
|
rubyEndpointType(typeName, methodName),
|
||||||
predicates: {
|
predicates: {
|
||||||
source: {
|
source: {
|
||||||
extensiblePredicate: sharedExtensiblePredicates.source,
|
extensiblePredicate: sharedExtensiblePredicates.source,
|
||||||
supportedKinds: sharedKinds.source,
|
supportedKinds: sharedKinds.source,
|
||||||
|
supportedEndpointTypes: [EndpointType.Method, EndpointType.Class],
|
||||||
// extensible predicate sourceModel(
|
// extensible predicate sourceModel(
|
||||||
// string type, string path, string kind
|
// string type, string path, string kind
|
||||||
// );
|
// );
|
||||||
@@ -42,7 +45,7 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
kind: row[2] as string,
|
kind: row[2] as string,
|
||||||
provenance: "manual",
|
provenance: "manual",
|
||||||
signature: rubyMethodSignature(typeName, methodName),
|
signature: rubyMethodSignature(typeName, methodName),
|
||||||
endpointType: rubyEndpointType(methodName),
|
endpointType: rubyEndpointType(typeName, methodName),
|
||||||
packageName: "",
|
packageName: "",
|
||||||
typeName,
|
typeName,
|
||||||
methodName,
|
methodName,
|
||||||
@@ -53,6 +56,7 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
sink: {
|
sink: {
|
||||||
extensiblePredicate: sharedExtensiblePredicates.sink,
|
extensiblePredicate: sharedExtensiblePredicates.sink,
|
||||||
supportedKinds: sharedKinds.sink,
|
supportedKinds: sharedKinds.sink,
|
||||||
|
supportedEndpointTypes: [EndpointType.Method, EndpointType.Constructor],
|
||||||
// extensible predicate sinkModel(
|
// extensible predicate sinkModel(
|
||||||
// string type, string path, string kind
|
// string type, string path, string kind
|
||||||
// );
|
// );
|
||||||
@@ -74,7 +78,7 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
kind: row[2] as string,
|
kind: row[2] as string,
|
||||||
provenance: "manual",
|
provenance: "manual",
|
||||||
signature: rubyMethodSignature(typeName, methodName),
|
signature: rubyMethodSignature(typeName, methodName),
|
||||||
endpointType: rubyEndpointType(methodName),
|
endpointType: rubyEndpointType(typeName, methodName),
|
||||||
packageName: "",
|
packageName: "",
|
||||||
typeName,
|
typeName,
|
||||||
methodName,
|
methodName,
|
||||||
@@ -85,6 +89,7 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
summary: {
|
summary: {
|
||||||
extensiblePredicate: sharedExtensiblePredicates.summary,
|
extensiblePredicate: sharedExtensiblePredicates.summary,
|
||||||
supportedKinds: sharedKinds.summary,
|
supportedKinds: sharedKinds.summary,
|
||||||
|
supportedEndpointTypes: [EndpointType.Method, EndpointType.Constructor],
|
||||||
// extensible predicate summaryModel(
|
// extensible predicate summaryModel(
|
||||||
// string type, string path, string input, string output, string kind
|
// string type, string path, string input, string output, string kind
|
||||||
// );
|
// );
|
||||||
@@ -105,7 +110,7 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
kind: row[4] as string,
|
kind: row[4] as string,
|
||||||
provenance: "manual",
|
provenance: "manual",
|
||||||
signature: rubyMethodSignature(typeName, methodName),
|
signature: rubyMethodSignature(typeName, methodName),
|
||||||
endpointType: rubyEndpointType(methodName),
|
endpointType: rubyEndpointType(typeName, methodName),
|
||||||
packageName: "",
|
packageName: "",
|
||||||
typeName,
|
typeName,
|
||||||
methodName,
|
methodName,
|
||||||
@@ -132,7 +137,7 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
kind: row[2] as string,
|
kind: row[2] as string,
|
||||||
provenance: "manual",
|
provenance: "manual",
|
||||||
signature: rubyMethodSignature(typeName, methodName),
|
signature: rubyMethodSignature(typeName, methodName),
|
||||||
endpointType: rubyEndpointType(methodName),
|
endpointType: rubyEndpointType(typeName, methodName),
|
||||||
packageName: "",
|
packageName: "",
|
||||||
typeName,
|
typeName,
|
||||||
methodName,
|
methodName,
|
||||||
@@ -157,7 +162,7 @@ export const ruby: ModelsAsDataLanguage = {
|
|||||||
relatedTypeName: row[0] as string,
|
relatedTypeName: row[0] as string,
|
||||||
path,
|
path,
|
||||||
signature: rubyMethodSignature(typeName, methodName),
|
signature: rubyMethodSignature(typeName, methodName),
|
||||||
endpointType: rubyEndpointType(methodName),
|
endpointType: rubyEndpointType(typeName, methodName),
|
||||||
packageName: "",
|
packageName: "",
|
||||||
typeName,
|
typeName,
|
||||||
methodName,
|
methodName,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function parseAccessPathSuggestionsResults(
|
|||||||
return {
|
return {
|
||||||
method: {
|
method: {
|
||||||
packageName: "",
|
packageName: "",
|
||||||
endpointType: rubyEndpointType(methodName),
|
endpointType: rubyEndpointType(type, methodName),
|
||||||
typeName: type,
|
typeName: type,
|
||||||
methodName,
|
methodName,
|
||||||
methodParameters: "",
|
methodParameters: "",
|
||||||
|
|||||||
@@ -17,9 +17,17 @@ export type Usage = Call & {
|
|||||||
readonly classification: CallClassification;
|
readonly classification: CallClassification;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint types are generic and can be used to represent different types of endpoints in different languages.
|
||||||
|
*
|
||||||
|
* For a reference of symbol kinds used in the LSP protocol (which is a good reference for widely supported features), see
|
||||||
|
* https://github.com/microsoft/vscode-languageserver-node/blob/4c8115f40b52f2e13adab41109c5b1208fc155ab/types/src/main.ts#L2890-L2920
|
||||||
|
*/
|
||||||
export enum EndpointType {
|
export enum EndpointType {
|
||||||
Method = "method",
|
Module = "module",
|
||||||
Class = "class",
|
Class = "class",
|
||||||
|
Method = "method",
|
||||||
|
Constructor = "constructor",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MethodDefinition {
|
export interface MethodDefinition {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import type { Method } from "../../model-editor/method";
|
|||||||
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
|
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
|
||||||
import type { Mutable } from "../../common/mutable";
|
import type { Mutable } from "../../common/mutable";
|
||||||
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
|
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
|
||||||
import { QueryLanguage } from "../../common/query-language";
|
import type { QueryLanguage } from "../../common/query-language";
|
||||||
|
import type { ModelsAsDataLanguagePredicates } from "../../model-editor/languages";
|
||||||
import { getModelsAsDataLanguage } from "../../model-editor/languages";
|
import { getModelsAsDataLanguage } from "../../model-editor/languages";
|
||||||
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
|
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
|
||||||
import { InputDropdown } from "./InputDropdown";
|
import { InputDropdown } from "./InputDropdown";
|
||||||
@@ -25,6 +26,16 @@ type Props = {
|
|||||||
onChange: (modeledMethod: ModeledMethod) => void;
|
onChange: (modeledMethod: ModeledMethod) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const typeLabels: Record<keyof ModelsAsDataLanguagePredicates, string> = {
|
||||||
|
source: "Source",
|
||||||
|
sink: "Sink",
|
||||||
|
summary: "Flow summary",
|
||||||
|
neutral: "Neutral",
|
||||||
|
type: "Type",
|
||||||
|
};
|
||||||
|
|
||||||
|
type Option = { value: ModeledMethodType; label: string };
|
||||||
|
|
||||||
export const ModelTypeDropdown = ({
|
export const ModelTypeDropdown = ({
|
||||||
language,
|
language,
|
||||||
method,
|
method,
|
||||||
@@ -33,19 +44,31 @@ export const ModelTypeDropdown = ({
|
|||||||
onChange,
|
onChange,
|
||||||
}: Props): React.JSX.Element => {
|
}: Props): React.JSX.Element => {
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
const baseOptions: Array<{ value: ModeledMethodType; label: string }> = [
|
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
|
||||||
|
|
||||||
|
const baseOptions: Option[] = [
|
||||||
{ value: "none", label: "Unmodeled" },
|
{ value: "none", label: "Unmodeled" },
|
||||||
{ value: "source", label: "Source" },
|
...Object.entries(modelsAsDataLanguage.predicates)
|
||||||
{ value: "sink", label: "Sink" },
|
.map(([predicateKey, predicate]): Option | null => {
|
||||||
{ value: "summary", label: "Flow summary" },
|
const type = predicateKey as keyof ModelsAsDataLanguagePredicates;
|
||||||
{ value: "neutral", label: "Neutral" },
|
|
||||||
|
if (
|
||||||
|
predicate.supportedEndpointTypes &&
|
||||||
|
!predicate.supportedEndpointTypes.includes(method.endpointType)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: type,
|
||||||
|
label: typeLabels[type],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((option): option is Option => option !== null),
|
||||||
];
|
];
|
||||||
if (language === QueryLanguage.Ruby) {
|
|
||||||
baseOptions.push({ value: "type", label: "Type" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseOptions;
|
return baseOptions;
|
||||||
}, [language]);
|
}, [language, method.endpointType]);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLSelectElement>) => {
|
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ describe("parseGenerateModelResults", () => {
|
|||||||
typeName: "SQLite3::Database",
|
typeName: "SQLite3::Database",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpointType: EndpointType.Method,
|
endpointType: EndpointType.Constructor,
|
||||||
input: "Argument[1]",
|
input: "Argument[1]",
|
||||||
kind: "value",
|
kind: "value",
|
||||||
methodName: "new",
|
methodName: "new",
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ describe("runGenerateQueries", () => {
|
|||||||
typeName: "SQLite3::Database",
|
typeName: "SQLite3::Database",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
endpointType: EndpointType.Method,
|
endpointType: EndpointType.Constructor,
|
||||||
input: "Argument[1]",
|
input: "Argument[1]",
|
||||||
kind: "value",
|
kind: "value",
|
||||||
methodName: "new",
|
methodName: "new",
|
||||||
|
|||||||
Reference in New Issue
Block a user