Merge pull request #3331 from github/koesie10/endpoint-type-supported

Add supported endpoint types to predicates
This commit is contained in:
Koen Vlaswinkel 2024-02-13 12:04:45 +01:00 коммит произвёл GitHub
Родитель be6166497f 0b4c611a5f
Коммит d7e9606bfa
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
9 изменённых файлов: 111 добавлений и 26 удалений

Просмотреть файл

@ -33,7 +33,7 @@ export function decodeBqrsToMethods(
let libraryVersion: string | undefined;
let type: ModeledMethodType;
let classification: CallClassification;
let endpointType = EndpointType.Method;
let endpointType: EndpointType | undefined = undefined;
if (mode === Mode.Application) {
[
@ -67,8 +67,19 @@ export function decodeBqrsToMethods(
type = "none";
}
if (methodName === "") {
endpointType = EndpointType.Class;
if (definition.endpointTypeForEndpoint) {
endpointType = definition.endpointTypeForEndpoint({
endpointType,
packageName,
typeName,
methodName,
methodParameters,
});
}
if (endpointType === undefined) {
endpointType =
methodName === "" ? EndpointType.Class : EndpointType.Method;
}
const signature = definition.createMethodSignature({

Просмотреть файл

@ -1,4 +1,4 @@
import type { MethodArgument, MethodDefinition } from "../method";
import type { EndpointType, MethodArgument, MethodDefinition } from "../method";
import type {
ModeledMethod,
NeutralModeledMethod,
@ -23,6 +23,11 @@ type ReadModeledMethod = (row: DataTuple[]) => ModeledMethod;
export type ModelsAsDataLanguagePredicate<T> = {
extensiblePredicate: string;
supportedKinds?: string[];
/**
* The endpoint types that this predicate supports. If not specified, the predicate supports all
* endpoint types.
*/
supportedEndpointTypes?: EndpointType[];
generateMethodDefinition: GenerateMethodDefinition<T>;
readModeledMethod: ReadModeledMethod;
};
@ -76,6 +81,18 @@ export type ModelsAsDataLanguage = {
*/
availableModes?: Mode[];
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;
modelGeneration?: ModelsAsDataLanguageModelGeneration;
accessPathSuggestions?: ModelsAsDataLanguageAccessPathSuggestions;

Просмотреть файл

@ -64,6 +64,27 @@ export function rubyPath(methodName: string, path: string) {
return `${methodPath}.${path}`;
}
export function rubyEndpointType(methodName: string) {
return methodName === "" ? EndpointType.Class : EndpointType.Method;
/** For the purpose of the model editor, we are defining the endpoint types as follows:
* - 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 { parseGenerateModelResults } from "./generate";
import type { MethodArgument } from "../../method";
import { getArgumentsList } from "../../method";
import { EndpointType, getArgumentsList } from "../../method";
import {
parseRubyAccessPath,
parseRubyMethodFromPath,
@ -19,10 +19,13 @@ export const ruby: ModelsAsDataLanguage = {
availableModes: [Mode.Framework],
createMethodSignature: ({ typeName, methodName }) =>
`${typeName}#${methodName}`,
endpointTypeForEndpoint: ({ typeName, methodName }) =>
rubyEndpointType(typeName, methodName),
predicates: {
source: {
extensiblePredicate: sharedExtensiblePredicates.source,
supportedKinds: sharedKinds.source,
supportedEndpointTypes: [EndpointType.Method, EndpointType.Class],
// extensible predicate sourceModel(
// string type, string path, string kind
// );
@ -42,7 +45,7 @@ export const ruby: ModelsAsDataLanguage = {
kind: row[2] as string,
provenance: "manual",
signature: rubyMethodSignature(typeName, methodName),
endpointType: rubyEndpointType(methodName),
endpointType: rubyEndpointType(typeName, methodName),
packageName: "",
typeName,
methodName,
@ -53,6 +56,7 @@ export const ruby: ModelsAsDataLanguage = {
sink: {
extensiblePredicate: sharedExtensiblePredicates.sink,
supportedKinds: sharedKinds.sink,
supportedEndpointTypes: [EndpointType.Method, EndpointType.Constructor],
// extensible predicate sinkModel(
// string type, string path, string kind
// );
@ -74,7 +78,7 @@ export const ruby: ModelsAsDataLanguage = {
kind: row[2] as string,
provenance: "manual",
signature: rubyMethodSignature(typeName, methodName),
endpointType: rubyEndpointType(methodName),
endpointType: rubyEndpointType(typeName, methodName),
packageName: "",
typeName,
methodName,
@ -85,6 +89,7 @@ export const ruby: ModelsAsDataLanguage = {
summary: {
extensiblePredicate: sharedExtensiblePredicates.summary,
supportedKinds: sharedKinds.summary,
supportedEndpointTypes: [EndpointType.Method, EndpointType.Constructor],
// extensible predicate summaryModel(
// string type, string path, string input, string output, string kind
// );
@ -105,7 +110,7 @@ export const ruby: ModelsAsDataLanguage = {
kind: row[4] as string,
provenance: "manual",
signature: rubyMethodSignature(typeName, methodName),
endpointType: rubyEndpointType(methodName),
endpointType: rubyEndpointType(typeName, methodName),
packageName: "",
typeName,
methodName,
@ -132,7 +137,7 @@ export const ruby: ModelsAsDataLanguage = {
kind: row[2] as string,
provenance: "manual",
signature: rubyMethodSignature(typeName, methodName),
endpointType: rubyEndpointType(methodName),
endpointType: rubyEndpointType(typeName, methodName),
packageName: "",
typeName,
methodName,
@ -157,7 +162,7 @@ export const ruby: ModelsAsDataLanguage = {
relatedTypeName: row[0] as string,
path,
signature: rubyMethodSignature(typeName, methodName),
endpointType: rubyEndpointType(methodName),
endpointType: rubyEndpointType(typeName, methodName),
packageName: "",
typeName,
methodName,

Просмотреть файл

@ -68,7 +68,7 @@ export function parseAccessPathSuggestionsResults(
return {
method: {
packageName: "",
endpointType: rubyEndpointType(methodName),
endpointType: rubyEndpointType(type, methodName),
typeName: type,
methodName,
methodParameters: "",

Просмотреть файл

@ -17,9 +17,17 @@ export type Usage = Call & {
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 {
Method = "method",
Module = "module",
Class = "class",
Method = "method",
Constructor = "constructor",
}
export interface MethodDefinition {

Просмотреть файл

@ -12,7 +12,8 @@ import type { Method } from "../../model-editor/method";
import { createEmptyModeledMethod } from "../../model-editor/modeled-method-empty";
import type { Mutable } from "../../common/mutable";
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 type { ModelingStatus } from "../../model-editor/shared/modeling-status";
import { InputDropdown } from "./InputDropdown";
@ -25,6 +26,16 @@ type Props = {
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 = ({
language,
method,
@ -33,19 +44,31 @@ export const ModelTypeDropdown = ({
onChange,
}: Props): React.JSX.Element => {
const options = useMemo(() => {
const baseOptions: Array<{ value: ModeledMethodType; label: string }> = [
const modelsAsDataLanguage = getModelsAsDataLanguage(language);
const baseOptions: Option[] = [
{ value: "none", label: "Unmodeled" },
{ value: "source", label: "Source" },
{ value: "sink", label: "Sink" },
{ value: "summary", label: "Flow summary" },
{ value: "neutral", label: "Neutral" },
];
if (language === QueryLanguage.Ruby) {
baseOptions.push({ value: "type", label: "Type" });
...Object.entries(modelsAsDataLanguage.predicates)
.map(([predicateKey, predicate]): Option | null => {
const type = predicateKey as keyof ModelsAsDataLanguagePredicates;
if (
predicate.supportedEndpointTypes &&
!predicate.supportedEndpointTypes.includes(method.endpointType)
) {
return null;
}
return {
value: type,
label: typeLabels[type],
};
})
.filter((option): option is Option => option !== null),
];
return baseOptions;
}, [language]);
}, [language, method.endpointType]);
const handleChange = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => {

Просмотреть файл

@ -136,7 +136,7 @@ describe("parseGenerateModelResults", () => {
typeName: "SQLite3::Database",
},
{
endpointType: EndpointType.Method,
endpointType: EndpointType.Constructor,
input: "Argument[1]",
kind: "value",
methodName: "new",

Просмотреть файл

@ -196,7 +196,7 @@ describe("runGenerateQueries", () => {
typeName: "SQLite3::Database",
},
{
endpointType: EndpointType.Method,
endpointType: EndpointType.Constructor,
input: "Argument[1]",
kind: "value",
methodName: "new",