Improve immutability of modeling store state
This improves the immutability of the modeling store state by using TypeScript's readonly types to ensure that state can only be modified from within the modeling store or when it's copied. This mostly consists of adding `readonly` to properties and arrays, but this also adds a `DeepReadonly` type to use in `postMessage` arguments to ensure that readonly objects can be passed in. `postMessage` will never modify the objects, so this is safe.
This commit is contained in:
14
extensions/ql-vscode/src/common/readonly.ts
Normal file
14
extensions/ql-vscode/src/common/readonly.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type DeepReadonly<T> = T extends Array<infer R>
|
||||
? DeepReadonlyArray<R>
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-types
|
||||
T extends Function
|
||||
? T
|
||||
: T extends object
|
||||
? DeepReadonlyObject<T>
|
||||
: T;
|
||||
|
||||
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
|
||||
|
||||
type DeepReadonlyObject<T> = {
|
||||
readonly [P in keyof T]: DeepReadonly<T[P]>;
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import { Uri, WebviewViewProvider } from "vscode";
|
||||
import { WebviewKind, WebviewMessage, getHtmlForWebview } from "./webview-html";
|
||||
import { Disposable } from "../disposable-object";
|
||||
import { App } from "../app";
|
||||
import { DeepReadonly } from "../readonly";
|
||||
|
||||
export abstract class AbstractWebviewViewProvider<
|
||||
ToMessage extends WebviewMessage,
|
||||
@@ -53,7 +54,7 @@ export abstract class AbstractWebviewViewProvider<
|
||||
return this.webviewView?.visible ?? false;
|
||||
}
|
||||
|
||||
protected async postMessage(msg: ToMessage): Promise<void> {
|
||||
protected async postMessage(msg: DeepReadonly<ToMessage>): Promise<void> {
|
||||
await this.webviewView?.webview.postMessage(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { App } from "../app";
|
||||
import { Disposable } from "../disposable-object";
|
||||
import { tmpDir } from "../../tmp-dir";
|
||||
import { getHtmlForWebview, WebviewMessage, WebviewKind } from "./webview-html";
|
||||
import { DeepReadonly } from "../readonly";
|
||||
|
||||
export type WebviewPanelConfig = {
|
||||
viewId: string;
|
||||
@@ -146,7 +147,7 @@ export abstract class AbstractWebview<
|
||||
this.panelLoadedCallBacks = [];
|
||||
}
|
||||
|
||||
protected async postMessage(msg: ToMessage): Promise<boolean> {
|
||||
protected async postMessage(msg: DeepReadonly<ToMessage>): Promise<boolean> {
|
||||
const panel = await this.getPanel();
|
||||
return panel.webview.postMessage(msg);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import { groupMethods, sortGroupNames, sortMethods } from "./shared/sorting";
|
||||
*/
|
||||
export function getCandidates(
|
||||
mode: Mode,
|
||||
methods: Method[],
|
||||
modeledMethodsBySignature: Record<string, ModeledMethod[]>,
|
||||
methods: readonly Method[],
|
||||
modeledMethodsBySignature: Record<string, readonly ModeledMethod[]>,
|
||||
): MethodSignature[] {
|
||||
// Sort the same way as the UI so we send the first ones listed in the UI first
|
||||
const grouped = groupMethods(methods, mode);
|
||||
@@ -32,8 +32,9 @@ export function getCandidates(
|
||||
const candidates: MethodSignature[] = [];
|
||||
|
||||
for (const method of sortedMethods) {
|
||||
const modeledMethods: ModeledMethod[] =
|
||||
modeledMethodsBySignature[method.signature] ?? [];
|
||||
const modeledMethods: ModeledMethod[] = [
|
||||
...(modeledMethodsBySignature[method.signature] ?? []),
|
||||
];
|
||||
|
||||
// Anything that is modeled is not a candidate
|
||||
if (modeledMethods.some((m) => m.type !== "none")) {
|
||||
|
||||
@@ -58,8 +58,8 @@ export class AutoModeler {
|
||||
*/
|
||||
public async startModeling(
|
||||
packageName: string,
|
||||
methods: Method[],
|
||||
modeledMethods: Record<string, ModeledMethod[]>,
|
||||
methods: readonly Method[],
|
||||
modeledMethods: Record<string, readonly ModeledMethod[]>,
|
||||
mode: Mode,
|
||||
): Promise<void> {
|
||||
if (this.jobs.has(packageName)) {
|
||||
@@ -105,8 +105,8 @@ export class AutoModeler {
|
||||
|
||||
private async modelPackage(
|
||||
packageName: string,
|
||||
methods: Method[],
|
||||
modeledMethods: Record<string, ModeledMethod[]>,
|
||||
methods: readonly Method[],
|
||||
modeledMethods: Record<string, readonly ModeledMethod[]>,
|
||||
mode: Mode,
|
||||
cancellationTokenSource: CancellationTokenSource,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -88,9 +88,16 @@ export function decodeBqrsToMethods(
|
||||
}
|
||||
|
||||
const method = methodsByApiName.get(signature)!;
|
||||
method.usages.push({
|
||||
const usages = [
|
||||
...method.usages,
|
||||
{
|
||||
...usage,
|
||||
classification,
|
||||
},
|
||||
];
|
||||
methodsByApiName.set(signature, {
|
||||
...method,
|
||||
usages,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ResolvableLocationValue } from "../common/bqrs-cli-types";
|
||||
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
|
||||
|
||||
export type Call = {
|
||||
label: string;
|
||||
url: ResolvableLocationValue;
|
||||
readonly label: string;
|
||||
readonly url: Readonly<ResolvableLocationValue>;
|
||||
};
|
||||
|
||||
export enum CallClassification {
|
||||
@@ -14,14 +14,14 @@ export enum CallClassification {
|
||||
}
|
||||
|
||||
export type Usage = Call & {
|
||||
classification: CallClassification;
|
||||
readonly classification: CallClassification;
|
||||
};
|
||||
|
||||
export interface MethodSignature {
|
||||
/**
|
||||
* Contains the version of the library if it can be determined by CodeQL, e.g. `4.2.2.2`
|
||||
*/
|
||||
libraryVersion?: string;
|
||||
readonly libraryVersion?: string;
|
||||
/**
|
||||
* A unique signature that can be used to identify this external API usage.
|
||||
*
|
||||
@@ -29,33 +29,33 @@ export interface MethodSignature {
|
||||
* in the form "packageName.typeName#methodName(methodParameters)".
|
||||
* e.g. `org.sql2o.Connection#createQuery(String)`
|
||||
*/
|
||||
signature: string;
|
||||
readonly signature: string;
|
||||
/**
|
||||
* The package name in Java, or the namespace in C#, e.g. `org.sql2o` or `System.Net.Http.Headers`.
|
||||
*
|
||||
* If the class is not in a package, the value should be an empty string.
|
||||
*/
|
||||
packageName: string;
|
||||
typeName: string;
|
||||
methodName: string;
|
||||
readonly packageName: string;
|
||||
readonly typeName: string;
|
||||
readonly methodName: string;
|
||||
/**
|
||||
* The method parameters, including enclosing parentheses, e.g. `(String, String)`
|
||||
*/
|
||||
methodParameters: string;
|
||||
readonly methodParameters: string;
|
||||
}
|
||||
|
||||
export interface Method extends MethodSignature {
|
||||
/**
|
||||
* Contains the name of the library containing the method declaration, e.g. `sql2o-1.6.0.jar` or `System.Runtime.dll`
|
||||
*/
|
||||
library: string;
|
||||
readonly library: string;
|
||||
/**
|
||||
* Is this method already supported by CodeQL standard libraries.
|
||||
* If so, there is no need for the user to model it themselves.
|
||||
*/
|
||||
supported: boolean;
|
||||
supportedType: ModeledMethodType;
|
||||
usages: Usage[];
|
||||
readonly supported: boolean;
|
||||
readonly supportedType: ModeledMethodType;
|
||||
readonly usages: readonly Usage[];
|
||||
}
|
||||
|
||||
export function getArgumentsList(methodParameters: string): string[] {
|
||||
@@ -68,7 +68,7 @@ export function getArgumentsList(methodParameters: string): string[] {
|
||||
|
||||
export function canMethodBeModeled(
|
||||
method: Method,
|
||||
modeledMethods: ModeledMethod[],
|
||||
modeledMethods: readonly ModeledMethod[],
|
||||
methodIsUnsaved: boolean,
|
||||
): boolean {
|
||||
return (
|
||||
|
||||
@@ -24,16 +24,17 @@ export class MethodsUsageDataProvider
|
||||
extends DisposableObject
|
||||
implements TreeDataProvider<MethodsUsageTreeViewItem>
|
||||
{
|
||||
private methods: Method[] = [];
|
||||
private methods: readonly Method[] = [];
|
||||
// sortedMethods is a separate field so we can check if the methods have changed
|
||||
// by reference, which is faster than checking if the methods have changed by value.
|
||||
private sortedMethods: Method[] = [];
|
||||
private sortedMethods: readonly Method[] = [];
|
||||
private databaseItem: DatabaseItem | undefined = undefined;
|
||||
private sourceLocationPrefix: string | undefined = undefined;
|
||||
private hideModeledMethods: boolean = INITIAL_HIDE_MODELED_METHODS_VALUE;
|
||||
private mode: Mode = INITIAL_MODE;
|
||||
private modeledMethods: Record<string, ModeledMethod[]> = {};
|
||||
private modifiedMethodSignatures: Set<string> = new Set();
|
||||
private modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>> =
|
||||
{};
|
||||
private modifiedMethodSignatures: ReadonlySet<string> = new Set();
|
||||
|
||||
private readonly onDidChangeTreeDataEmitter = this.push(
|
||||
new EventEmitter<void>(),
|
||||
@@ -55,12 +56,12 @@ export class MethodsUsageDataProvider
|
||||
* method and instead always pass new objects/arrays.
|
||||
*/
|
||||
public async setState(
|
||||
methods: Method[],
|
||||
methods: readonly Method[],
|
||||
databaseItem: DatabaseItem,
|
||||
hideModeledMethods: boolean,
|
||||
mode: Mode,
|
||||
modeledMethods: Record<string, ModeledMethod[]>,
|
||||
modifiedMethodSignatures: Set<string>,
|
||||
modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
|
||||
modifiedMethodSignatures: ReadonlySet<string>,
|
||||
): Promise<void> {
|
||||
if (
|
||||
this.methods !== methods ||
|
||||
@@ -145,10 +146,10 @@ export class MethodsUsageDataProvider
|
||||
if (this.hideModeledMethods) {
|
||||
return this.sortedMethods.filter((api) => !api.supported);
|
||||
} else {
|
||||
return this.sortedMethods;
|
||||
return [...this.sortedMethods];
|
||||
}
|
||||
} else if (isExternalApiUsage(item)) {
|
||||
return item.usages;
|
||||
return [...item.usages];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
@@ -194,7 +195,7 @@ function usagesAreEqual(u1: Usage, u2: Usage): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function sortMethodsInGroups(methods: Method[], mode: Mode): Method[] {
|
||||
function sortMethodsInGroups(methods: readonly Method[], mode: Mode): Method[] {
|
||||
const grouped = groupMethods(methods, mode);
|
||||
|
||||
const sortedGroupNames = sortGroupNames(grouped);
|
||||
|
||||
@@ -32,12 +32,12 @@ export class MethodsUsagePanel extends DisposableObject {
|
||||
}
|
||||
|
||||
public async setState(
|
||||
methods: Method[],
|
||||
methods: readonly Method[],
|
||||
databaseItem: DatabaseItem,
|
||||
hideModeledMethods: boolean,
|
||||
mode: Mode,
|
||||
modeledMethods: Record<string, ModeledMethod[]>,
|
||||
modifiedMethodSignatures: Set<string>,
|
||||
modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
|
||||
modifiedMethodSignatures: ReadonlySet<string>,
|
||||
): Promise<void> {
|
||||
await this.dataProvider.setState(
|
||||
methods,
|
||||
|
||||
@@ -14,8 +14,8 @@ import { pathsEqual } from "../common/files";
|
||||
export async function saveModeledMethods(
|
||||
extensionPack: ExtensionPack,
|
||||
language: string,
|
||||
methods: Method[],
|
||||
modeledMethods: Record<string, ModeledMethod[]>,
|
||||
methods: readonly Method[],
|
||||
modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
|
||||
mode: Mode,
|
||||
cliServer: CodeQLCliServer,
|
||||
logger: NotificationLogger,
|
||||
|
||||
@@ -20,11 +20,11 @@ export type Provenance =
|
||||
| "manual";
|
||||
|
||||
export interface ModeledMethod extends MethodSignature {
|
||||
type: ModeledMethodType;
|
||||
input: string;
|
||||
output: string;
|
||||
kind: ModeledMethodKind;
|
||||
provenance: Provenance;
|
||||
readonly type: ModeledMethodType;
|
||||
readonly input: string;
|
||||
readonly output: string;
|
||||
readonly kind: ModeledMethodKind;
|
||||
readonly provenance: Provenance;
|
||||
}
|
||||
|
||||
export type ModeledMethodKind = string;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ModeledMethod } from "./modeled-method";
|
||||
import { INITIAL_HIDE_MODELED_METHODS_VALUE } from "./shared/hide-modeled-methods";
|
||||
import { INITIAL_MODE, Mode } from "./shared/mode";
|
||||
|
||||
interface DbModelingState {
|
||||
interface InternalDbModelingState {
|
||||
databaseItem: DatabaseItem;
|
||||
methods: Method[];
|
||||
hideModeledMethods: boolean;
|
||||
@@ -18,40 +18,59 @@ interface DbModelingState {
|
||||
selectedUsage: Usage | undefined;
|
||||
}
|
||||
|
||||
interface DbModelingState {
|
||||
readonly databaseItem: DatabaseItem;
|
||||
readonly methods: readonly Method[];
|
||||
readonly hideModeledMethods: boolean;
|
||||
readonly mode: Mode;
|
||||
readonly modeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>;
|
||||
readonly modifiedMethodSignatures: ReadonlySet<string>;
|
||||
readonly selectedMethod: Method | undefined;
|
||||
readonly selectedUsage: Usage | undefined;
|
||||
}
|
||||
|
||||
interface SelectedMethodDetails {
|
||||
readonly databaseItem: DatabaseItem;
|
||||
readonly method: Method;
|
||||
readonly usage: Usage | undefined;
|
||||
readonly modeledMethods: readonly ModeledMethod[];
|
||||
readonly isModified: boolean;
|
||||
}
|
||||
|
||||
interface MethodsChangedEvent {
|
||||
methods: Method[];
|
||||
dbUri: string;
|
||||
isActiveDb: boolean;
|
||||
readonly methods: readonly Method[];
|
||||
readonly dbUri: string;
|
||||
readonly isActiveDb: boolean;
|
||||
}
|
||||
|
||||
interface HideModeledMethodsChangedEvent {
|
||||
hideModeledMethods: boolean;
|
||||
isActiveDb: boolean;
|
||||
readonly hideModeledMethods: boolean;
|
||||
readonly isActiveDb: boolean;
|
||||
}
|
||||
|
||||
interface ModeChangedEvent {
|
||||
mode: Mode;
|
||||
isActiveDb: boolean;
|
||||
readonly mode: Mode;
|
||||
readonly isActiveDb: boolean;
|
||||
}
|
||||
|
||||
interface ModeledMethodsChangedEvent {
|
||||
modeledMethods: Record<string, ModeledMethod[]>;
|
||||
dbUri: string;
|
||||
isActiveDb: boolean;
|
||||
readonly modeledMethods: Readonly<Record<string, ModeledMethod[]>>;
|
||||
readonly dbUri: string;
|
||||
readonly isActiveDb: boolean;
|
||||
}
|
||||
|
||||
interface ModifiedMethodsChangedEvent {
|
||||
modifiedMethods: Set<string>;
|
||||
dbUri: string;
|
||||
isActiveDb: boolean;
|
||||
readonly modifiedMethods: ReadonlySet<string>;
|
||||
readonly dbUri: string;
|
||||
readonly isActiveDb: boolean;
|
||||
}
|
||||
|
||||
interface SelectedMethodChangedEvent {
|
||||
databaseItem: DatabaseItem;
|
||||
method: Method;
|
||||
usage: Usage;
|
||||
modeledMethods: ModeledMethod[];
|
||||
isModified: boolean;
|
||||
readonly databaseItem: DatabaseItem;
|
||||
readonly method: Method;
|
||||
readonly usage: Usage;
|
||||
readonly modeledMethods: readonly ModeledMethod[];
|
||||
readonly isModified: boolean;
|
||||
}
|
||||
|
||||
export class ModelingStore extends DisposableObject {
|
||||
@@ -65,7 +84,7 @@ export class ModelingStore extends DisposableObject {
|
||||
public readonly onModifiedMethodsChanged: AppEvent<ModifiedMethodsChangedEvent>;
|
||||
public readonly onSelectedMethodChanged: AppEvent<SelectedMethodChangedEvent>;
|
||||
|
||||
private readonly state: Map<string, DbModelingState>;
|
||||
private readonly state: Map<string, InternalDbModelingState>;
|
||||
private activeDb: string | undefined;
|
||||
|
||||
private readonly onActiveDbChangedEventEmitter: AppEventEmitter<void>;
|
||||
@@ -82,7 +101,7 @@ export class ModelingStore extends DisposableObject {
|
||||
super();
|
||||
|
||||
// State initialization
|
||||
this.state = new Map<string, DbModelingState>();
|
||||
this.state = new Map<string, InternalDbModelingState>();
|
||||
|
||||
// Event initialization
|
||||
this.onActiveDbChangedEventEmitter = this.push(
|
||||
@@ -179,6 +198,14 @@ export class ModelingStore extends DisposableObject {
|
||||
return this.state.get(this.activeDb);
|
||||
}
|
||||
|
||||
private getInternalStateForActiveDb(): InternalDbModelingState | undefined {
|
||||
if (!this.activeDb) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.state.get(this.activeDb);
|
||||
}
|
||||
|
||||
public hasStateForActiveDb(): boolean {
|
||||
return !!this.getStateForActiveDb();
|
||||
}
|
||||
@@ -194,7 +221,7 @@ export class ModelingStore extends DisposableObject {
|
||||
public getMethods(
|
||||
dbItem: DatabaseItem,
|
||||
methodSignatures?: string[],
|
||||
): Method[] {
|
||||
): readonly Method[] {
|
||||
const methods = this.getState(dbItem).methods;
|
||||
if (!methodSignatures) {
|
||||
return methods;
|
||||
@@ -255,7 +282,7 @@ export class ModelingStore extends DisposableObject {
|
||||
public getModeledMethods(
|
||||
dbItem: DatabaseItem,
|
||||
methodSignatures?: string[],
|
||||
): Record<string, ModeledMethod[]> {
|
||||
): Readonly<Record<string, readonly ModeledMethod[]>> {
|
||||
const modeledMethods = this.getState(dbItem).modeledMethods;
|
||||
if (!methodSignatures) {
|
||||
return modeledMethods;
|
||||
@@ -369,8 +396,8 @@ export class ModelingStore extends DisposableObject {
|
||||
});
|
||||
}
|
||||
|
||||
public getSelectedMethodDetails() {
|
||||
const dbState = this.getStateForActiveDb();
|
||||
public getSelectedMethodDetails(): SelectedMethodDetails | undefined {
|
||||
const dbState = this.getInternalStateForActiveDb();
|
||||
if (!dbState) {
|
||||
throw new Error("No active state found in modeling store");
|
||||
}
|
||||
@@ -391,7 +418,7 @@ export class ModelingStore extends DisposableObject {
|
||||
};
|
||||
}
|
||||
|
||||
private getState(databaseItem: DatabaseItem): DbModelingState {
|
||||
private getState(databaseItem: DatabaseItem): InternalDbModelingState {
|
||||
if (!this.state.has(databaseItem.databaseUri.toString())) {
|
||||
throw Error(
|
||||
"Cannot get state for a database that has not been initialized",
|
||||
@@ -403,7 +430,7 @@ export class ModelingStore extends DisposableObject {
|
||||
|
||||
private changeModifiedMethods(
|
||||
dbItem: DatabaseItem,
|
||||
updateState: (state: DbModelingState) => void,
|
||||
updateState: (state: InternalDbModelingState) => void,
|
||||
) {
|
||||
const state = this.getState(dbItem);
|
||||
|
||||
@@ -418,7 +445,7 @@ export class ModelingStore extends DisposableObject {
|
||||
|
||||
private changeModeledMethods(
|
||||
dbItem: DatabaseItem,
|
||||
updateState: (state: DbModelingState) => void,
|
||||
updateState: (state: InternalDbModelingState) => void,
|
||||
) {
|
||||
const state = this.getState(dbItem);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Method } from "../method";
|
||||
|
||||
export function calculateModeledPercentage(methods: Method[]): number {
|
||||
export function calculateModeledPercentage(methods: readonly Method[]): number {
|
||||
if (methods.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import { ModeledMethod } from "../modeled-method";
|
||||
export type ModelingStatus = "unmodeled" | "unsaved" | "saved";
|
||||
|
||||
export function getModelingStatus(
|
||||
modeledMethods: Array<ModeledMethod | undefined>,
|
||||
modeledMethods: readonly ModeledMethod[],
|
||||
methodIsUnsaved: boolean,
|
||||
): ModelingStatus {
|
||||
if (modeledMethods.length > 0) {
|
||||
if (methodIsUnsaved) {
|
||||
return "unsaved";
|
||||
} else if (modeledMethods.some((m) => m && m.type !== "none")) {
|
||||
} else if (modeledMethods.some((m) => m.type !== "none")) {
|
||||
return "saved";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Mode } from "./mode";
|
||||
import { calculateModeledPercentage } from "./modeled-percentage";
|
||||
|
||||
export function groupMethods(
|
||||
methods: Method[],
|
||||
methods: readonly Method[],
|
||||
mode: Mode,
|
||||
): Record<string, Method[]> {
|
||||
const groupedByLibrary: Record<string, Method[]> = {};
|
||||
@@ -19,22 +19,24 @@ export function groupMethods(
|
||||
return groupedByLibrary;
|
||||
}
|
||||
|
||||
export function sortGroupNames(methods: Record<string, Method[]>): string[] {
|
||||
export function sortGroupNames(
|
||||
methods: Record<string, readonly Method[]>,
|
||||
): string[] {
|
||||
return Object.keys(methods).sort((a, b) =>
|
||||
compareGroups(methods[a], a, methods[b], b),
|
||||
);
|
||||
}
|
||||
|
||||
export function sortMethods(methods: Method[]): Method[] {
|
||||
export function sortMethods(methods: readonly Method[]): Method[] {
|
||||
const sortedMethods = [...methods];
|
||||
sortedMethods.sort((a, b) => compareMethod(a, b));
|
||||
return sortedMethods;
|
||||
}
|
||||
|
||||
function compareGroups(
|
||||
a: Method[],
|
||||
a: readonly Method[],
|
||||
aName: string,
|
||||
b: Method[],
|
||||
b: readonly Method[],
|
||||
bName: string,
|
||||
): number {
|
||||
const supportedPercentageA = calculateModeledPercentage(a);
|
||||
|
||||
@@ -16,7 +16,7 @@ const ajv = new Ajv({ allErrors: true, allowUnionTypes: true });
|
||||
const modelExtensionFileSchemaValidate = ajv.compile(modelExtensionFileSchema);
|
||||
|
||||
function createDataProperty(
|
||||
methods: ModeledMethod[],
|
||||
methods: readonly ModeledMethod[],
|
||||
definition: ExtensiblePredicateDefinition,
|
||||
) {
|
||||
if (methods.length === 0) {
|
||||
@@ -35,7 +35,7 @@ function createDataProperty(
|
||||
|
||||
export function createDataExtensionYaml(
|
||||
language: string,
|
||||
modeledMethods: ModeledMethod[],
|
||||
modeledMethods: readonly ModeledMethod[],
|
||||
) {
|
||||
const methodsByType: Record<
|
||||
Exclude<ModeledMethodType, "none">,
|
||||
@@ -70,9 +70,11 @@ ${extensions.join("\n")}`;
|
||||
|
||||
export function createDataExtensionYamls(
|
||||
language: string,
|
||||
methods: Method[],
|
||||
newModeledMethods: Record<string, ModeledMethod[]>,
|
||||
existingModeledMethods: Record<string, Record<string, ModeledMethod[]>>,
|
||||
methods: readonly Method[],
|
||||
newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
|
||||
existingModeledMethods: Readonly<
|
||||
Record<string, Record<string, readonly ModeledMethod[]>>
|
||||
>,
|
||||
mode: Mode,
|
||||
) {
|
||||
switch (mode) {
|
||||
@@ -97,9 +99,11 @@ export function createDataExtensionYamls(
|
||||
|
||||
function createDataExtensionYamlsByGrouping(
|
||||
language: string,
|
||||
methods: Method[],
|
||||
newModeledMethods: Record<string, ModeledMethod[]>,
|
||||
existingModeledMethods: Record<string, Record<string, ModeledMethod[]>>,
|
||||
methods: readonly Method[],
|
||||
newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
|
||||
existingModeledMethods: Readonly<
|
||||
Record<string, Record<string, readonly ModeledMethod[]>>
|
||||
>,
|
||||
createFilename: (method: Method) => string,
|
||||
): Record<string, string> {
|
||||
const methodsByFilename: Record<string, Record<string, ModeledMethod[]>> = {};
|
||||
@@ -119,7 +123,7 @@ function createDataExtensionYamlsByGrouping(
|
||||
)) {
|
||||
if (filename in methodsByFilename) {
|
||||
for (const [signature, methods] of Object.entries(methodsBySignature)) {
|
||||
methodsByFilename[filename][signature] = methods;
|
||||
methodsByFilename[filename][signature] = [...methods];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +136,7 @@ function createDataExtensionYamlsByGrouping(
|
||||
const filename = createFilename(method);
|
||||
|
||||
// Override any existing modeled methods with the new ones.
|
||||
methodsByFilename[filename][method.signature] = newMethods;
|
||||
methodsByFilename[filename][method.signature] = [...newMethods];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,9 +154,11 @@ function createDataExtensionYamlsByGrouping(
|
||||
|
||||
export function createDataExtensionYamlsForApplicationMode(
|
||||
language: string,
|
||||
methods: Method[],
|
||||
newModeledMethods: Record<string, ModeledMethod[]>,
|
||||
existingModeledMethods: Record<string, Record<string, ModeledMethod[]>>,
|
||||
methods: readonly Method[],
|
||||
newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
|
||||
existingModeledMethods: Readonly<
|
||||
Record<string, Record<string, readonly ModeledMethod[]>>
|
||||
>,
|
||||
): Record<string, string> {
|
||||
return createDataExtensionYamlsByGrouping(
|
||||
language,
|
||||
@@ -165,9 +171,11 @@ export function createDataExtensionYamlsForApplicationMode(
|
||||
|
||||
export function createDataExtensionYamlsForFrameworkMode(
|
||||
language: string,
|
||||
methods: Method[],
|
||||
newModeledMethods: Record<string, ModeledMethod[]>,
|
||||
existingModeledMethods: Record<string, Record<string, ModeledMethod[]>>,
|
||||
methods: readonly Method[],
|
||||
newModeledMethods: Readonly<Record<string, readonly ModeledMethod[]>>,
|
||||
existingModeledMethods: Readonly<
|
||||
Record<string, Record<string, readonly ModeledMethod[]>>
|
||||
>,
|
||||
): Record<string, string> {
|
||||
return createDataExtensionYamlsByGrouping(
|
||||
language,
|
||||
|
||||
@@ -16,8 +16,9 @@ describe(MethodName.name, () => {
|
||||
});
|
||||
|
||||
it("renders method name without package name", () => {
|
||||
const method = createMethod();
|
||||
method.packageName = "";
|
||||
const method = createMethod({
|
||||
packageName: "",
|
||||
});
|
||||
render(method);
|
||||
|
||||
const name = `${method.typeName}.${method.methodName}${method.methodParameters}`;
|
||||
|
||||
Reference in New Issue
Block a user