diff --git a/ConsumePlugin/CapturingMockExample.fs b/ConsumePlugin/CapturingMockExample.fs new file mode 100644 index 0000000..9ed23b3 --- /dev/null +++ b/ConsumePlugin/CapturingMockExample.fs @@ -0,0 +1,57 @@ +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +[] +type IPublicType = + abstract Mem1 : string * int -> string list + abstract Mem2 : string -> int + abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string + +[] +type IPublicTypeInternalFalse = + abstract Mem1 : string * int -> string list + abstract Mem2 : string -> int + abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string + +[] +type internal InternalType = + abstract Mem1 : string * int -> unit + abstract Mem2 : string -> int + +[] +type private PrivateType = + abstract Mem1 : string * int -> unit + abstract Mem2 : string -> int + +[] +type private PrivateTypeInternalFalse = + abstract Mem1 : string * int -> unit + abstract Mem2 : string -> int + +[] +type VeryPublicType<'a, 'b> = + abstract Mem1 : 'a -> 'b + +[] +type Curried<'a> = + abstract Mem1 : int -> 'a -> string + abstract Mem2 : int * string -> 'a -> string + abstract Mem3 : (int * string) -> 'a -> string + abstract Mem4 : (int * string) -> ('a * int) -> string + abstract Mem5 : x : int * string -> ('a * int) -> string + abstract Mem6 : int * string -> y : 'a * int -> string + +[] +type TypeWithInterface = + inherit IDisposable + abstract Mem1 : string option -> string[] Async + abstract Mem2 : unit -> string[] Async + +[] +type TypeWithProperties = + inherit IDisposable + abstract Mem1 : string option -> string[] Async + abstract Prop1 : int + abstract Prop2 : unit Async diff --git a/ConsumePlugin/CapturingMockExampleNoAttributes.fs b/ConsumePlugin/CapturingMockExampleNoAttributes.fs new file mode 100644 index 0000000..83b5f64 --- /dev/null +++ b/ConsumePlugin/CapturingMockExampleNoAttributes.fs @@ -0,0 +1,41 @@ +namespace SomeNamespace.CapturingMock + +open System + +type IPublicTypeNoAttr = + abstract Mem1 : string * int -> string list + abstract Mem2 : string -> int + abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string + +type IPublicTypeInternalFalseNoAttr = + abstract Mem1 : string * int -> string list + abstract Mem2 : string -> int + abstract Mem3 : x : int * ?ct : System.Threading.CancellationToken -> string + +type internal InternalTypeNoAttr = + abstract Mem1 : string * int -> unit + abstract Mem2 : string -> int + +type private PrivateTypeNoAttr = + abstract Mem1 : string * int -> unit + abstract Mem2 : string -> int + +type private PrivateTypeInternalFalseNoAttr = + abstract Mem1 : string * int -> unit + abstract Mem2 : string -> int + +type VeryPublicTypeNoAttr<'a, 'b> = + abstract Mem1 : 'a -> 'b + +type CurriedNoAttr<'a> = + abstract Mem1 : int -> 'a -> string + abstract Mem2 : int * string -> 'a -> string + abstract Mem3 : (int * string) -> 'a -> string + abstract Mem4 : (int * string) -> ('a * int) -> string + abstract Mem5 : x : int * string -> ('a * int) -> string + abstract Mem6 : int * string -> y : 'a * int -> string + +type TypeWithInterfaceNoAttr = + inherit IDisposable + abstract Mem1 : string option -> string[] Async + abstract Mem2 : unit -> string[] Async diff --git a/ConsumePlugin/ConsumePlugin.fsproj b/ConsumePlugin/ConsumePlugin.fsproj index 1f2add3..49e133d 100644 --- a/ConsumePlugin/ConsumePlugin.fsproj +++ b/ConsumePlugin/ConsumePlugin.fsproj @@ -33,6 +33,10 @@ MockExample.fs + + + CapturingMockExample.fs + MockExampleNoAttributes.fs @@ -47,6 +51,20 @@ GenerateMock + + + CapturingMockExampleNoAttributes.fs + + GenerateCapturingMock + GenerateCapturingMock(false) + GenerateCapturingMock + GenerateCapturingMock + GenerateCapturingMock(false) + GenerateCapturingMock + GenerateCapturingMock + GenerateCapturingMock + + Vault.fs diff --git a/ConsumePlugin/Generated2SwaggerGitea.fs b/ConsumePlugin/Generated2SwaggerGitea.fs index 5c537fb..eaae4e9 100644 --- a/ConsumePlugin/Generated2SwaggerGitea.fs +++ b/ConsumePlugin/Generated2SwaggerGitea.fs @@ -4,6 +4,7 @@ //------------------------------------------------------------------------------ + namespace Gitea open WoofWare.Myriad.Plugins diff --git a/ConsumePlugin/GeneratedArgs.fs b/ConsumePlugin/GeneratedArgs.fs index 4114cd8..5e89d70 100644 --- a/ConsumePlugin/GeneratedArgs.fs +++ b/ConsumePlugin/GeneratedArgs.fs @@ -8,6 +8,7 @@ + namespace ConsumePlugin open System diff --git a/ConsumePlugin/GeneratedCapturingMock.fs b/ConsumePlugin/GeneratedCapturingMock.fs new file mode 100644 index 0000000..0585d38 --- /dev/null +++ b/ConsumePlugin/GeneratedCapturingMock.fs @@ -0,0 +1,330 @@ +//------------------------------------------------------------------------------ +// This code was generated by myriad. +// Changes to this file will be lost when the code is regenerated. +//------------------------------------------------------------------------------ + + +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type internal PublicTypeMock = + { + Mem1 : string * int -> string list + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + Mem3 : int * option -> string + Mem3_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : PublicTypeMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + Mem3_Calls = ResizeArray () + } + + interface IPublicType with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) + member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1) +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type public PublicTypeInternalFalseMock = + { + Mem1 : string * int -> string list + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + Mem3 : int * option -> string + Mem3_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : PublicTypeInternalFalseMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + Mem3_Calls = ResizeArray () + } + + interface IPublicTypeInternalFalse with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) + member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1) +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type internal InternalTypeMock = + { + Mem1 : string * int -> unit + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : InternalTypeMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + } + + interface InternalType with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type private PrivateTypeMock = + { + Mem1 : string * int -> unit + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : PrivateTypeMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + } + + interface PrivateType with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type private PrivateTypeInternalFalseMock = + { + Mem1 : string * int -> unit + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : PrivateTypeInternalFalseMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + } + + interface PrivateTypeInternalFalse with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type internal VeryPublicTypeMock<'a, 'b> = + { + Mem1 : 'a -> 'b + Mem1_Calls : ResizeArray<'a> + } + + /// An implementation where every non-unit method throws. + static member Empty () : VeryPublicTypeMock<'a, 'b> = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem1_Calls = ResizeArray () + } + + interface VeryPublicType<'a, 'b> with + member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// A single call to the Mem1 method +type internal Mem1Call<'a> = + { + arg0 : int + arg1 : 'a + } + +/// A single call to the Mem2 method +type internal Mem2Call<'a> = + { + arg0 : int * string + arg1 : 'a + } + +/// A single call to the Mem3 method +type internal Mem3Call<'a> = + { + arg0 : int * string + arg1 : 'a + } + +/// A single call to the Mem4 method +type internal Mem4Call<'a> = + { + arg0 : int * string + arg1 : 'a * int + } + +/// A single call to the Mem5 method +type internal Mem5Call<'a> = + { + arg0 : int * string + arg1 : 'a * int + } + +/// A single call to the Mem6 method +type internal Mem6Call<'a> = + { + arg0 : int * string + arg1 : 'a * int + } + +/// Mock record type for an interface +type internal CurriedMock<'a> = + { + Mem1 : int -> 'a -> string + Mem1_Calls : ResizeArray> + Mem2 : int * string -> 'a -> string + Mem2_Calls : ResizeArray> + Mem3 : (int * string) -> 'a -> string + Mem3_Calls : ResizeArray> + Mem4 : (int * string) -> ('a * int) -> string + Mem4_Calls : ResizeArray> + Mem5 : int * string -> ('a * int) -> string + Mem5_Calls : ResizeArray> + Mem6 : int * string -> 'a * int -> string + Mem6_Calls : ResizeArray> + } + + /// An implementation where every non-unit method throws. + static member Empty () : CurriedMock<'a> = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3")) + Mem4 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem4")) + Mem5 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem5")) + Mem6 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem6")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + Mem3_Calls = ResizeArray () + Mem4_Calls = ResizeArray () + Mem5_Calls = ResizeArray () + Mem6_Calls = ResizeArray () + } + + interface Curried<'a> with + member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0) + member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0) + member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0) + + member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) = + this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) + + member this.Mem5 (arg_0_0, arg_0_1) ((arg_1_0, arg_1_1)) = + this.Mem5 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) + + member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) = + this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type internal TypeWithInterfaceMock = + { + /// Implementation of IDisposable.Dispose + Dispose : unit -> unit + Mem1 : string option -> string[] Async + Mem1_Calls : ResizeArray + Mem2 : unit -> string[] Async + Mem2_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : TypeWithInterfaceMock = + { + Dispose = (fun () -> ()) + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + } + + interface TypeWithInterface with + member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) + member this.Mem2 () = this.Mem2 (()) + + interface System.IDisposable with + member this.Dispose () : unit = this.Dispose () +namespace SomeNamespace.CapturingMock + +open System +open WoofWare.Myriad.Plugins + +/// Mock record type for an interface +type internal TypeWithPropertiesMock = + { + /// Implementation of IDisposable.Dispose + Dispose : unit -> unit + Prop1 : unit -> int + Prop1_Calls : ResizeArray + Prop2 : unit -> unit Async + Prop2_Calls : ResizeArray + Mem1 : string option -> string[] Async + Mem1_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : TypeWithPropertiesMock = + { + Dispose = (fun () -> ()) + Prop1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop1")) + Prop2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Prop2")) + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Prop1_Calls = ResizeArray () + Prop2_Calls = ResizeArray () + Mem1_Calls = ResizeArray () + } + + interface TypeWithProperties with + member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) + member this.Prop1 = this.Prop1 () + member this.Prop2 = this.Prop2 () + + interface System.IDisposable with + member this.Dispose () : unit = this.Dispose () diff --git a/ConsumePlugin/GeneratedCapturingMockNoAttributes.fs b/ConsumePlugin/GeneratedCapturingMockNoAttributes.fs new file mode 100644 index 0000000..7b1d747 --- /dev/null +++ b/ConsumePlugin/GeneratedCapturingMockNoAttributes.fs @@ -0,0 +1,285 @@ +//------------------------------------------------------------------------------ +// This code was generated by myriad. +// Changes to this file will be lost when the code is regenerated. +//------------------------------------------------------------------------------ + + +namespace SomeNamespace.CapturingMock + +open System + +/// Mock record type for an interface +type internal PublicTypeNoAttrMock = + { + Mem1 : string * int -> string list + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + Mem3 : int * option -> string + Mem3_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : PublicTypeNoAttrMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + Mem3_Calls = ResizeArray () + } + + interface IPublicTypeNoAttr with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) + member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1) +namespace SomeNamespace.CapturingMock + +open System + +/// Mock record type for an interface +type public PublicTypeInternalFalseNoAttrMock = + { + Mem1 : string * int -> string list + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + Mem3 : int * option -> string + Mem3_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : PublicTypeInternalFalseNoAttrMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + Mem3_Calls = ResizeArray () + } + + interface IPublicTypeInternalFalseNoAttr with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) + member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1) +namespace SomeNamespace.CapturingMock + +open System + +/// Mock record type for an interface +type internal InternalTypeNoAttrMock = + { + Mem1 : string * int -> unit + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : InternalTypeNoAttrMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + } + + interface InternalTypeNoAttr with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) +namespace SomeNamespace.CapturingMock + +open System + +/// Mock record type for an interface +type private PrivateTypeNoAttrMock = + { + Mem1 : string * int -> unit + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : PrivateTypeNoAttrMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + } + + interface PrivateTypeNoAttr with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) +namespace SomeNamespace.CapturingMock + +open System + +/// Mock record type for an interface +type private PrivateTypeInternalFalseNoAttrMock = + { + Mem1 : string * int -> unit + Mem1_Calls : ResizeArray + Mem2 : string -> int + Mem2_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : PrivateTypeInternalFalseNoAttrMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + } + + interface PrivateTypeInternalFalseNoAttr with + member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) + member this.Mem2 arg_0_0 = this.Mem2 (arg_0_0) +namespace SomeNamespace.CapturingMock + +open System + +/// Mock record type for an interface +type internal VeryPublicTypeNoAttrMock<'a, 'b> = + { + Mem1 : 'a -> 'b + Mem1_Calls : ResizeArray<'a> + } + + /// An implementation where every non-unit method throws. + static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem1_Calls = ResizeArray () + } + + interface VeryPublicTypeNoAttr<'a, 'b> with + member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) +namespace SomeNamespace.CapturingMock + +open System + +/// A single call to the Mem1 method +type internal Mem1Call<'a> = + { + arg0 : int + arg1 : 'a + } + +/// A single call to the Mem2 method +type internal Mem2Call<'a> = + { + arg0 : int * string + arg1 : 'a + } + +/// A single call to the Mem3 method +type internal Mem3Call<'a> = + { + arg0 : int * string + arg1 : 'a + } + +/// A single call to the Mem4 method +type internal Mem4Call<'a> = + { + arg0 : int * string + arg1 : 'a * int + } + +/// A single call to the Mem5 method +type internal Mem5Call<'a> = + { + arg0 : int * string + arg1 : 'a * int + } + +/// A single call to the Mem6 method +type internal Mem6Call<'a> = + { + arg0 : int * string + arg1 : 'a * int + } + +/// Mock record type for an interface +type internal CurriedNoAttrMock<'a> = + { + Mem1 : int -> 'a -> string + Mem1_Calls : ResizeArray> + Mem2 : int * string -> 'a -> string + Mem2_Calls : ResizeArray> + Mem3 : (int * string) -> 'a -> string + Mem3_Calls : ResizeArray> + Mem4 : (int * string) -> ('a * int) -> string + Mem4_Calls : ResizeArray> + Mem5 : int * string -> ('a * int) -> string + Mem5_Calls : ResizeArray> + Mem6 : int * string -> 'a * int -> string + Mem6_Calls : ResizeArray> + } + + /// An implementation where every non-unit method throws. + static member Empty () : CurriedNoAttrMock<'a> = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem3 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem3")) + Mem4 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem4")) + Mem5 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem5")) + Mem6 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem6")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + Mem3_Calls = ResizeArray () + Mem4_Calls = ResizeArray () + Mem5_Calls = ResizeArray () + Mem6_Calls = ResizeArray () + } + + interface CurriedNoAttr<'a> with + member this.Mem1 arg_0_0 arg_1_0 = this.Mem1 (arg_0_0) (arg_1_0) + member this.Mem2 (arg_0_0, arg_0_1) arg_1_0 = this.Mem2 (arg_0_0, arg_0_1) (arg_1_0) + member this.Mem3 ((arg_0_0, arg_0_1)) arg_1_0 = this.Mem3 (arg_0_0, arg_0_1) (arg_1_0) + + member this.Mem4 ((arg_0_0, arg_0_1)) ((arg_1_0, arg_1_1)) = + this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) + + member this.Mem5 (arg_0_0, arg_0_1) ((arg_1_0, arg_1_1)) = + this.Mem5 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) + + member this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) = + this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1) +namespace SomeNamespace.CapturingMock + +open System + +/// Mock record type for an interface +type internal TypeWithInterfaceNoAttrMock = + { + /// Implementation of IDisposable.Dispose + Dispose : unit -> unit + Mem1 : string option -> string[] Async + Mem1_Calls : ResizeArray + Mem2 : unit -> string[] Async + Mem2_Calls : ResizeArray + } + + /// An implementation where every non-unit method throws. + static member Empty : TypeWithInterfaceNoAttrMock = + { + Dispose = (fun () -> ()) + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + Mem1_Calls = ResizeArray () + Mem2_Calls = ResizeArray () + } + + interface TypeWithInterfaceNoAttr with + member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) + member this.Mem2 () = this.Mem2 (()) + + interface System.IDisposable with + member this.Dispose () : unit = this.Dispose () diff --git a/ConsumePlugin/GeneratedCatamorphism.fs b/ConsumePlugin/GeneratedCatamorphism.fs index 4d280ee..3c4adfd 100644 --- a/ConsumePlugin/GeneratedCatamorphism.fs +++ b/ConsumePlugin/GeneratedCatamorphism.fs @@ -7,6 +7,7 @@ + namespace ConsumePlugin open WoofWare.Myriad.Plugins diff --git a/ConsumePlugin/GeneratedFileSystem.fs b/ConsumePlugin/GeneratedFileSystem.fs index 4a2fff9..31382f7 100644 --- a/ConsumePlugin/GeneratedFileSystem.fs +++ b/ConsumePlugin/GeneratedFileSystem.fs @@ -7,6 +7,7 @@ + namespace ConsumePlugin open WoofWare.Myriad.Plugins diff --git a/ConsumePlugin/GeneratedJson.fs b/ConsumePlugin/GeneratedJson.fs index 62892d6..92f3e1e 100644 --- a/ConsumePlugin/GeneratedJson.fs +++ b/ConsumePlugin/GeneratedJson.fs @@ -4,6 +4,7 @@ //------------------------------------------------------------------------------ + namespace ConsumePlugin open System.Text.Json.Serialization diff --git a/ConsumePlugin/GeneratedPureGymDto.fs b/ConsumePlugin/GeneratedPureGymDto.fs index de21a8d..fb9bc79 100644 --- a/ConsumePlugin/GeneratedPureGymDto.fs +++ b/ConsumePlugin/GeneratedPureGymDto.fs @@ -4,6 +4,7 @@ //------------------------------------------------------------------------------ + namespace PureGym open System diff --git a/ConsumePlugin/GeneratedRestClient.fs b/ConsumePlugin/GeneratedRestClient.fs index 4e08f9c..687bdf1 100644 --- a/ConsumePlugin/GeneratedRestClient.fs +++ b/ConsumePlugin/GeneratedRestClient.fs @@ -6,6 +6,7 @@ + namespace PureGym open System diff --git a/ConsumePlugin/GeneratedSerde.fs b/ConsumePlugin/GeneratedSerde.fs index 076928c..b11f135 100644 --- a/ConsumePlugin/GeneratedSerde.fs +++ b/ConsumePlugin/GeneratedSerde.fs @@ -4,6 +4,7 @@ //------------------------------------------------------------------------------ + namespace ConsumePlugin open System diff --git a/ConsumePlugin/GeneratedSwaggerGitea.fs b/ConsumePlugin/GeneratedSwaggerGitea.fs index c7f7305..a7335fd 100644 --- a/ConsumePlugin/GeneratedSwaggerGitea.fs +++ b/ConsumePlugin/GeneratedSwaggerGitea.fs @@ -9,6 +9,7 @@ + namespace Gitea open WoofWare.Myriad.Plugins diff --git a/ConsumePlugin/GeneratedVault.fs b/ConsumePlugin/GeneratedVault.fs index 8e006aa..88f5362 100644 --- a/ConsumePlugin/GeneratedVault.fs +++ b/ConsumePlugin/GeneratedVault.fs @@ -5,6 +5,7 @@ + namespace ConsumePlugin /// Module containing JSON parsing methods for the JwtVaultAuthResponse type diff --git a/ConsumePlugin/ListCata.fs b/ConsumePlugin/ListCata.fs index a6e3e84..e2d0fba 100644 --- a/ConsumePlugin/ListCata.fs +++ b/ConsumePlugin/ListCata.fs @@ -7,6 +7,7 @@ + namespace ConsumePlugin open WoofWare.Myriad.Plugins diff --git a/WoofWare.Myriad.Plugins.Attributes/Attributes.fs b/WoofWare.Myriad.Plugins.Attributes/Attributes.fs index 63dbf1e..765b059 100644 --- a/WoofWare.Myriad.Plugins.Attributes/Attributes.fs +++ b/WoofWare.Myriad.Plugins.Attributes/Attributes.fs @@ -14,6 +14,9 @@ type RemoveOptionsAttribute () = /// but where each method is represented as a record field, so you can use /// record update syntax to easily specify partially-implemented mock objects. /// You may optionally specify `isInternal = false` to get a mock with the public visibility modifier. +/// +/// The default implementation of each field throws (except for default implementations of IDisposable, which are +/// no-ops). type GenerateMockAttribute (isInternal : bool) = inherit Attribute () /// The default value of `isInternal`, the optional argument to the GenerateMockAttribute constructor. @@ -22,6 +25,22 @@ type GenerateMockAttribute (isInternal : bool) = /// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details. new () = GenerateMockAttribute GenerateMockAttribute.DefaultIsInternal +/// Attribute indicating an interface type for which the "Generate Capturing Mock" Myriad +/// generator should apply during build. +/// This generator creates a record which implements the interface, +/// but where each method is represented as a record field, so you can use +/// record update syntax to easily specify partially-implemented mock objects. +/// You may optionally specify `isInternal = false` to get a mock with the public visibility modifier. +/// +/// The default implementation of each field captures all calls made to it, which can then be accessed later. +type GenerateCapturingMockAttribute (isInternal : bool) = + inherit Attribute () + /// The default value of `isInternal`, the optional argument to the GenerateCapturingMockAttribute constructor. + static member DefaultIsInternal = true + + /// Shorthand for the "isExtensionMethod = false" constructor; see documentation there for details. + new () = GenerateCapturingMockAttribute GenerateCapturingMockAttribute.DefaultIsInternal + /// Attribute indicating a record type to which the "Add JSON serializer" Myriad /// generator should apply during build. /// The purpose of this generator is to create methods (possibly extension methods) of the form diff --git a/WoofWare.Myriad.Plugins.Test/TestCapturingMockGenerator/TestCapturingMockGenerator.fs b/WoofWare.Myriad.Plugins.Test/TestCapturingMockGenerator/TestCapturingMockGenerator.fs new file mode 100644 index 0000000..b2afb29 --- /dev/null +++ b/WoofWare.Myriad.Plugins.Test/TestCapturingMockGenerator/TestCapturingMockGenerator.fs @@ -0,0 +1,49 @@ +namespace WoofWare.Myriad.Plugins.Test + +open System +open SomeNamespace.CapturingMock +open NUnit.Framework +open FsUnitTyped + +[] +module TestMockGenerator = + + [] + let ``Example of use: IPublicType`` () = + let mock : IPublicType = + { PublicTypeMock.Empty with + Mem1 = fun (s, count) -> List.replicate count s + } + :> _ + + let _ = + Assert.Throws (fun () -> mock.Mem2 "hi" |> ignore) + + mock.Mem1 ("hi", 3) |> shouldEqual [ "hi" ; "hi" ; "hi" ] + + [] + let ``Example of use: curried args`` () = + let mock : Curried<_> = + { CurriedMock.Empty () with + Mem1 = fun i c -> Array.replicate i c |> String + Mem2 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s) + Mem3 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s) + } + :> _ + + mock.Mem1 3 'a' |> shouldEqual "aaa" + mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi" + mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi" + + [] + let ``Example of use: properties`` () = + let mock : TypeWithProperties = + { TypeWithPropertiesMock.Empty with + Mem1 = fun i -> async { return Option.toArray i } + Prop1 = fun () -> 44 + } + :> _ + + mock.Mem1 (Some "hi") |> Async.RunSynchronously |> shouldEqual [| "hi" |] + + mock.Prop1 |> shouldEqual 44 diff --git a/WoofWare.Myriad.Plugins.Test/TestCapturingMockGenerator/TestCapturingMockGeneratorNoAttr.fs b/WoofWare.Myriad.Plugins.Test/TestCapturingMockGenerator/TestCapturingMockGeneratorNoAttr.fs new file mode 100644 index 0000000..2da7b77 --- /dev/null +++ b/WoofWare.Myriad.Plugins.Test/TestCapturingMockGenerator/TestCapturingMockGeneratorNoAttr.fs @@ -0,0 +1,36 @@ +namespace WoofWare.Myriad.Plugins.Test + +open System +open SomeNamespace.CapturingMock +open NUnit.Framework +open FsUnitTyped + +[] +module TestCapturingMockGeneratorNoAttr = + + [] + let ``Example of use: IPublicType`` () = + let mock : IPublicTypeNoAttr = + { PublicTypeNoAttrMock.Empty with + Mem1 = fun (s, count) -> List.replicate count s + } + :> _ + + let _ = + Assert.Throws (fun () -> mock.Mem2 "hi" |> ignore) + + mock.Mem1 ("hi", 3) |> shouldEqual [ "hi" ; "hi" ; "hi" ] + + [] + let ``Example of use: curried args`` () = + let mock : CurriedNoAttr<_> = + { CurriedNoAttrMock.Empty () with + Mem1 = fun i c -> Array.replicate i c |> String + Mem2 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s) + Mem3 = fun (i, s) c -> String.concat $"%c{c}" (List.replicate i s) + } + :> _ + + mock.Mem1 3 'a' |> shouldEqual "aaa" + mock.Mem2 (3, "hi") 'a' |> shouldEqual "hiahiahi" + mock.Mem3 (3, "hi") 'a' |> shouldEqual "hiahiahi" diff --git a/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGeneratorNoAttr.fs b/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestCapturingMockGeneratorNoAttr.fs similarity index 96% rename from WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGeneratorNoAttr.fs rename to WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestCapturingMockGeneratorNoAttr.fs index bff8aba..84702af 100644 --- a/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestMockGeneratorNoAttr.fs +++ b/WoofWare.Myriad.Plugins.Test/TestMockGenerator/TestCapturingMockGeneratorNoAttr.fs @@ -6,7 +6,7 @@ open NUnit.Framework open FsUnitTyped [] -module TestMockGeneratorNoAttr = +module TestCapturingMockGeneratorNoAttr = [] let ``Example of use: IPublicType`` () = diff --git a/WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj b/WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj index 9dfd80c..8f1989f 100644 --- a/WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj +++ b/WoofWare.Myriad.Plugins.Test/WoofWare.Myriad.Plugins.Test.fsproj @@ -28,7 +28,7 @@ - + @@ -67,4 +67,9 @@ + + + + + diff --git a/WoofWare.Myriad.Plugins/CapturingInterfaceMockGenerator.fs b/WoofWare.Myriad.Plugins/CapturingInterfaceMockGenerator.fs new file mode 100644 index 0000000..129931d --- /dev/null +++ b/WoofWare.Myriad.Plugins/CapturingInterfaceMockGenerator.fs @@ -0,0 +1,561 @@ +namespace WoofWare.Myriad.Plugins + +open System +open Fantomas.FCS.Syntax +open Fantomas.FCS.Xml +open WoofWare.Whippet.Fantomas + +type internal CapturingInterfaceMockOutputSpec = + { + IsInternal : bool + } + +type private CallField = + | ArgsObject of Ident * SynTypeDefn * SynTyparDecls option + | Original of SynType + +[] +module internal CapturingInterfaceMockGenerator = + open Fantomas.FCS.Text.Range + + [] + type private KnownInheritance = | IDisposable + + /// Expects the input `args` list to have more than one element. + let private createTypeForArgs + (spec : CapturingInterfaceMockOutputSpec) + (memberName : Ident) + (generics : SynTyparDecls option) + (args : TupledArg list) + : Ident * SynTypeDefn + = + let name = memberName.idText + "Call" |> Ident.create + + let access = + if spec.IsInternal then + SynAccess.Internal range0 + else + SynAccess.Public range0 + + let recordFields = + args + |> List.mapi (fun i tupledArg -> + { + SynFieldData.Ident = $"arg%i{i}" |> Ident.create |> Some + Attrs = [] + Type = + tupledArg.Args + |> List.map (fun pi -> pi.Type) + |> SynType.tupleNoParen + |> Option.get + } + |> SynField.make + ) + + let record = + { + Name = name + Fields = recordFields + Members = None + XmlDoc = Some (PreXmlDoc.create $"A single call to the %s{memberName.idText} method") + Generics = generics + TypeAccessibility = Some access + ImplAccessibility = None + Attributes = [] + } + + let typeDecl = AstHelper.defineRecordType record + + name, typeDecl + + let private buildType (x : ParameterInfo) : SynType = + if x.IsOptional then + SynType.app "option" [ x.Type ] + else + x.Type + + let private constructMemberSinglePlace (tuple : TupledArg) : SynType = + tuple.Args + |> List.map buildType + |> SynType.tupleNoParen + |> Option.defaultWith (fun () -> failwith "no-arg functions not supported yet") + |> if tuple.HasParen then SynType.paren else id + + let rec private collectGenerics' (ty : SynType) : Ident list = + match ty with + | SynType.Var (typar = SynTypar (ident = typar)) -> [ typar ] + | SynType.HashConstraint (innerType = ty) + | SynType.WithGlobalConstraints (typeName = ty) + | SynType.Paren (innerType = ty) + | SynType.MeasurePower (baseMeasure = ty) + | SynType.SignatureParameter (usedType = ty) + | SynType.Array (elementType = ty) -> collectGenerics' ty + | SynType.StaticConstant _ + | SynType.StaticConstantNamed _ + | SynType.StaticConstantExpr _ + | SynType.FromParseError _ + | SynType.Anon _ + | SynType.LongIdent _ -> [] + | SynType.LongIdentApp (typeArgs = tys) + | SynType.App (typeArgs = tys) -> tys |> List.collect collectGenerics' + | SynType.Tuple (path = path) -> + path + |> List.collect (fun seg -> + match seg with + | SynTupleTypeSegment.Type ty -> collectGenerics' ty + | SynTupleTypeSegment.Star _ + | SynTupleTypeSegment.Slash _ -> [] + ) + | SynType.AnonRecd (fields = fields) -> fields |> List.collect (fun (_, ty) -> collectGenerics' ty) + | SynType.Fun (argType = t1 ; returnType = t2) + | SynType.Or (lhsType = t1 ; rhsType = t2) -> collectGenerics' t1 @ collectGenerics' t2 + + let private collectGenerics (ty : SynType) = + collectGenerics' ty |> List.distinctBy _.idText + + /// Builds the record field for the mock object, and also if applicable a type representing a single call to + /// that object (packaging up the args of the call). + let private constructMember + (spec : CapturingInterfaceMockOutputSpec) + (generics : SynTyparDecls option) + (mem : MemberInfo) + : SynField * CallField + = + let inputType = mem.Args |> List.map constructMemberSinglePlace + + let funcType = SynType.toFun inputType mem.ReturnType + + let field = + { + Type = funcType + Attrs = [] + Ident = Some mem.Identifier + } + |> SynField.make + |> SynField.withDocString (mem.XmlDoc |> Option.defaultValue PreXmlDoc.Empty) + + let argsType = + match mem.Args with + | [] -> failwith "expected args in member" + | [ ty ] -> + ty.Args + |> List.map _.Type + |> SynType.tupleNoParen + |> Option.get + |> CallField.Original + | args -> + let genericsUsed = + args + |> List.collect (fun arg -> arg.Args |> List.map _.Type |> List.collect collectGenerics) + |> List.distinctBy _.idText + + let genericsUsed = + match genericsUsed with + | [] -> None + | genericsUsed -> + genericsUsed + |> List.map (fun i -> + SynTyparDecl.SynTyparDecl ([], SynTypar.SynTypar (i, TyparStaticReq.None, false)) + ) + |> fun l -> SynTyparDecls.PostfixList (l, [], range0) + |> Some + + let name, defn = createTypeForArgs spec mem.Identifier genericsUsed args + CallField.ArgsObject (name, defn, genericsUsed) + + field, argsType + + let constructProperty (prop : PropertyInfo) : SynField = + { + Attrs = [] + Ident = Some prop.Identifier + Type = SynType.toFun [ SynType.unit ] prop.Type + } + |> SynField.make + |> SynField.withDocString (prop.XmlDoc |> Option.defaultValue PreXmlDoc.Empty) + + let createType + (spec : CapturingInterfaceMockOutputSpec) + (name : string) + (interfaceType : InterfaceType) + (xmlDoc : PreXmlDoc) + : SynModuleDecl + = + let fields = + interfaceType.Members + |> List.map (constructMember spec interfaceType.Generics) + |> List.append ( + interfaceType.Properties + |> List.map constructProperty + |> List.map (Tuple.withRight (CallField.Original SynType.unit)) + ) + + let inherits = + interfaceType.Inherits + |> Seq.map (fun ty -> + match ty with + | SynType.LongIdent (SynLongIdent.SynLongIdent (name, _, _)) -> + match name |> List.map _.idText with + | [] -> failwith "Unexpected empty identifier in inheritance declaration" + | [ "IDisposable" ] + | [ "System" ; "IDisposable" ] -> KnownInheritance.IDisposable + | _ -> failwithf $"Unrecognised inheritance identifier: %+A{name}" + | x -> failwithf $"Unrecognised type in inheritance: %+A{x}" + ) + |> Set.ofSeq + + // TODO: for each field, if there are multiple arguments to the member, stamp out a new type to represent them; + // then store that type name in this list alongside the field name + let fields = + fields + |> List.map (fun (SynField (idOpt = idOpt) as f, extraType) -> + let fieldName = + match idOpt with + | None -> failwith $"unexpectedly got a field with no identifier: %O{f}" + | Some idOpt -> idOpt.idText + + f, extraType, fieldName + ) + + let failwithNotImplemented (fieldName : string) = + let failString = SynExpr.CreateConst $"Unimplemented mock function: %s{fieldName}" + + SynExpr.createLongIdent [ "System" ; "NotImplementedException" ] + |> SynExpr.applyTo failString + |> SynExpr.paren + |> SynExpr.applyFunction (SynExpr.createIdent "raise") + |> SynExpr.createLambda "_" + + let constructorReturnType = + match interfaceType.Generics with + | None -> SynType.createLongIdent' [ name ] + | Some generics -> + + let generics = + generics.TyparDecls + |> List.map (fun (SynTyparDecl (_, typar)) -> SynType.var typar) + + SynType.app name generics + + let emptyRecordFieldInstantiations = + let interfaceExtras = + if inherits.Contains KnownInheritance.IDisposable then + let unitFun = SynExpr.createThunk (SynExpr.CreateConst ()) + + [ SynLongIdent.createS "Dispose", unitFun ] + else + [] + + let originalMembers = + fields + |> List.map (fun (_, _, fieldName) -> SynLongIdent.createS fieldName, failwithNotImplemented fieldName) + + let callsArrays = + fields + |> List.map (fun (_field, extraType, fieldName) -> + let name = SynLongIdent.createS $"{fieldName}_Calls" + + let init = + match extraType with + | CallField.Original _ -> + SynExpr.createIdent "ResizeArray" |> SynExpr.applyTo (SynExpr.CreateConst ()) + | CallField.ArgsObject _ -> + SynExpr.createIdent "ResizeArray" |> SynExpr.applyTo (SynExpr.CreateConst ()) + + name, init + ) + + interfaceExtras @ originalMembers @ callsArrays + + let staticMemberEmpty = + SynBinding.basic + [ Ident.create "Empty" ] + (if interfaceType.Generics.IsNone then + [] + else + [ SynPat.unit ]) + (SynExpr.createRecord None emptyRecordFieldInstantiations) + |> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every non-unit method throws.") + |> SynBinding.withReturnAnnotation constructorReturnType + |> SynMemberDefn.staticMember + + let recordFields = + let extras = + if inherits.Contains KnownInheritance.IDisposable then + { + Attrs = [] + Ident = Some (Ident.create "Dispose") + Type = SynType.funFromDomain SynType.unit SynType.unit + } + |> SynField.make + |> SynField.withDocString (PreXmlDoc.create "Implementation of IDisposable.Dispose") + |> List.singleton + else + [] + + let nonExtras = + fields + |> List.collect (fun (field, callType, fieldName) -> + let callField = + match callType with + | CallField.Original ty -> + { + Attrs = [] + Ident = Some (fieldName + "_Calls" |> Ident.create) + Type = SynType.app "ResizeArray" [ ty ] + } + |> SynField.make + | CallField.ArgsObject (name, _, generics) -> + { + Attrs = [] + Ident = Some (fieldName + "_Calls" |> Ident.create) + Type = + match generics with + | None -> SynType.named name.idText + | Some generics -> + generics.TyparDecls + |> List.map (fun (SynTyparDecl.SynTyparDecl (_, typar)) -> SynType.var typar) + |> SynType.app name.idText + |> List.singleton + |> SynType.app "ResizeArray" + } + |> SynField.make + + [ field ; callField ] + ) + + extras @ nonExtras + + let interfaceMembers = + let members = + interfaceType.Members + |> List.map (fun memberInfo -> + let headArgs = + memberInfo.Args + |> List.mapi (fun i tupledArgs -> + let args = + tupledArgs.Args + |> List.mapi (fun j ty -> + match ty.Type with + | UnitType -> SynPat.unit + | _ -> SynPat.named $"arg_%i{i}_%i{j}" + ) + + match args with + | [] -> failwith "somehow got no args at all" + | [ arg ] -> arg + | args -> SynPat.tuple args + |> fun i -> if tupledArgs.HasParen then SynPat.paren i else i + ) + + let body = + let tuples = + memberInfo.Args + |> List.mapi (fun i args -> + args.Args + |> List.mapi (fun j arg -> + match arg.Type with + | UnitType -> SynExpr.CreateConst () + | _ -> SynExpr.createIdent $"arg_%i{i}_%i{j}" + ) + |> SynExpr.tuple + ) + + match tuples |> List.rev with + | [] -> failwith "expected args but got none" + | last :: rest -> + + (last, rest) + ||> List.fold SynExpr.applyTo + |> SynExpr.applyFunction ( + SynExpr.createLongIdent' [ Ident.create "this" ; memberInfo.Identifier ] + ) + + SynBinding.basic [ Ident.create "this" ; memberInfo.Identifier ] headArgs body + |> SynMemberDefn.memberImplementation + ) + + let properties = + interfaceType.Properties + |> List.map (fun pi -> + SynExpr.createLongIdent' [ Ident.create "this" ; pi.Identifier ] + |> SynExpr.applyTo (SynExpr.CreateConst ()) + |> SynBinding.basic [ Ident.create "this" ; pi.Identifier ] [] + |> SynMemberDefn.memberImplementation + ) + + let interfaceName = + let baseName = SynType.createLongIdent interfaceType.Name + + match interfaceType.Generics with + | None -> baseName + | Some generics -> + let generics = + match generics with + | SynTyparDecls.PostfixList (decls, _, _) -> decls + | SynTyparDecls.PrefixList (decls, _) -> decls + | SynTyparDecls.SinglePrefix (decl, _) -> [ decl ] + |> List.map (fun (SynTyparDecl (_, typar)) -> SynType.var typar) + + SynType.app' baseName generics + + SynMemberDefn.Interface (interfaceName, Some range0, Some (members @ properties), range0) + + let access = + match interfaceType.Accessibility, spec.IsInternal with + | Some (SynAccess.Public _), true + | None, true -> SynAccess.Internal range0 + | Some (SynAccess.Public _), false -> SynAccess.Public range0 + | None, false -> SynAccess.Public range0 + | Some (SynAccess.Internal _), _ -> SynAccess.Internal range0 + | Some (SynAccess.Private _), _ -> SynAccess.Private range0 + + let extraInterfaces = + inherits + |> Seq.map (fun inheritance -> + match inheritance with + | KnownInheritance.IDisposable -> + let mem = + SynExpr.createLongIdent [ "this" ; "Dispose" ] + |> SynExpr.applyTo (SynExpr.CreateConst ()) + |> SynBinding.basic [ Ident.create "this" ; Ident.create "Dispose" ] [ SynPat.unit ] + |> SynBinding.withReturnAnnotation SynType.unit + |> SynMemberDefn.memberImplementation + + SynMemberDefn.Interface ( + SynType.createLongIdent' [ "System" ; "IDisposable" ], + Some range0, + Some [ mem ], + range0 + ) + ) + |> Seq.toList + + let record = + { + Name = Ident.create name + Fields = recordFields + Members = Some ([ staticMemberEmpty ; interfaceMembers ] @ extraInterfaces) + XmlDoc = Some xmlDoc + Generics = interfaceType.Generics + TypeAccessibility = Some access + ImplAccessibility = None + Attributes = [] + } + + let typeDecl = AstHelper.defineRecordType record + + SynModuleDecl.Types ( + [ + for _, field, _ in fields do + match field with + | CallField.Original _ -> () + | CallField.ArgsObject (_, callType, _) -> yield callType + yield typeDecl + ], + range0 + ) + + let createRecord + (namespaceId : LongIdent) + (opens : SynOpenDeclTarget list) + (interfaceType : SynTypeDefn, spec : CapturingInterfaceMockOutputSpec) + : SynModuleOrNamespace + = + let interfaceType = AstHelper.parseInterface interfaceType + + let docString = PreXmlDoc.create "Mock record type for an interface" + + let name = + List.last interfaceType.Name + |> _.idText + |> fun s -> + if s.StartsWith 'I' && s.Length > 1 && Char.IsUpper s.[1] then + s.Substring 1 + else + s + |> fun s -> s + "Mock" + + let typeDecl = createType spec name interfaceType docString + + [ yield! opens |> List.map SynModuleDecl.openAny ; yield typeDecl ] + |> SynModuleOrNamespace.createNamespace namespaceId + +open Myriad.Core + +/// Myriad generator that creates a record which implements the given interface, +/// but with every field mocked out. +[] +type CapturingInterfaceMockGenerator () = + + interface IMyriadGenerator with + member _.ValidInputExtensions = [ ".fs" ] + + member _.Generate (context : GeneratorContext) = + let targetedTypes = + MyriadParamParser.render context.AdditionalParameters + |> Map.map (fun _ v -> v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse) + + let ast, _ = + Ast.fromFilename context.InputFilename |> Async.RunSynchronously |> Array.head + + let types = Ast.getTypes ast + + let namespaceAndInterfaces = + types + |> List.choose (fun (ns, types) -> + types + |> List.choose (fun typeDef -> + match SynTypeDefn.getAttribute typeof.Name typeDef with + | None -> + let name = SynTypeDefn.getName typeDef |> List.map _.idText |> String.concat "." + + match Map.tryFind name targetedTypes with + | Some desired -> + desired + |> List.tryPick (fun generator -> + match generator with + | DesiredGenerator.CapturingInterfaceMock arg -> + let spec = + { + IsInternal = + arg + |> Option.defaultValue + GenerateCapturingMockAttribute.DefaultIsInternal + } + + Some (typeDef, spec) + | _ -> None + ) + | _ -> None + + | Some attr -> + let arg = + match SynExpr.stripOptionalParen attr.ArgExpr with + | SynExpr.Const (SynConst.Bool value, _) -> value + | SynExpr.Const (SynConst.Unit, _) -> GenerateCapturingMockAttribute.DefaultIsInternal + | arg -> + failwith + $"Unrecognised argument %+A{arg} to [<%s{nameof GenerateCapturingMockAttribute}>]. Literals are not supported. Use `true` or `false` (or unit) only." + + let spec = + { + IsInternal = arg + } + + Some (typeDef, spec) + ) + |> function + | [] -> None + | ty -> Some (ns, ty) + ) + + let opens = AstHelper.extractOpens ast + + let modules = + namespaceAndInterfaces + |> List.collect (fun (ns, records) -> + records |> List.map (CapturingInterfaceMockGenerator.createRecord ns opens) + ) + + Output.Ast modules diff --git a/WoofWare.Myriad.Plugins/Parameters.fs b/WoofWare.Myriad.Plugins/Parameters.fs index 9fc9b31..15565a4 100644 --- a/WoofWare.Myriad.Plugins/Parameters.fs +++ b/WoofWare.Myriad.Plugins/Parameters.fs @@ -2,6 +2,7 @@ namespace WoofWare.Myriad.Plugins type internal DesiredGenerator = | InterfaceMock of isInternal : bool option + | CapturingInterfaceMock of isInternal : bool option | JsonParse of extensionMethod : bool option | JsonSerialize of extensionMethod : bool option | HttpClient of extensionMethod : bool option @@ -11,6 +12,9 @@ type internal DesiredGenerator = | "GenerateMock" -> DesiredGenerator.InterfaceMock None | "GenerateMock(true)" -> DesiredGenerator.InterfaceMock (Some true) | "GenerateMock(false)" -> DesiredGenerator.InterfaceMock (Some false) + | "GenerateCapturingMock" -> DesiredGenerator.CapturingInterfaceMock None + | "GenerateCapturingMock(true)" -> DesiredGenerator.CapturingInterfaceMock (Some true) + | "GenerateCapturingMock(false)" -> DesiredGenerator.CapturingInterfaceMock (Some false) | "JsonParse" -> DesiredGenerator.JsonParse None | "JsonParse(true)" -> DesiredGenerator.JsonParse (Some true) | "JsonParse(false)" -> DesiredGenerator.JsonParse (Some false) diff --git a/WoofWare.Myriad.Plugins/Tuple.fs b/WoofWare.Myriad.Plugins/Tuple.fs new file mode 100644 index 0000000..23d7169 --- /dev/null +++ b/WoofWare.Myriad.Plugins/Tuple.fs @@ -0,0 +1,6 @@ +namespace WoofWare.Myriad.Plugins + +[] +module internal Tuple = + let withLeft left right = left, right + let withRight right left = left, right diff --git a/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj b/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj index 68c690d..01ab1de 100644 --- a/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj +++ b/WoofWare.Myriad.Plugins/WoofWare.Myriad.Plugins.fsproj @@ -30,6 +30,7 @@ + @@ -37,6 +38,7 @@ +