From 2789152331df24e7446f588375e237b0c3efcd71 Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Mon, 7 Oct 2024 22:10:30 +0100 Subject: [PATCH] Import interface-mock generator (#14) --- .github/workflows/dotnet.yaml | 47 +++ .../version.json | 2 + .../AssemblyInfo.fs | 4 + .../GeneratedMockExample.fs | 195 ++++++++++ .../GeneratedMockExample2.fs | 195 ++++++++++ .../MockExample.fs | 41 +++ .../MockExample2.fs | 41 +++ ...ippet.Plugin.InterfaceMock.Consumer.fsproj | 45 +++ .../DesiredGenerator.fs | 11 + .../InterfaceMockGenerator.fs | 333 ++++++++++++++++++ .../README.md | 82 +++++ .../TestMockGenerator.fs | 36 ++ .../TestMockGeneratorNoAttr.fs | 36 ++ ...e.Whippet.Plugin.InterfaceMock.Test.fsproj | 26 ++ ...ofWare.Whippet.Plugin.InterfaceMock.fsproj | 38 ++ .../version.json | 13 + .../DesiredGenerator.fs | 14 +- .../JsonParseGenerator.fs | 2 +- .../JsonSerializeGenerator.fs | 2 +- WoofWare.Whippet.sln | 18 + 20 files changed, 1172 insertions(+), 9 deletions(-) create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/AssemblyInfo.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/GeneratedMockExample.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/GeneratedMockExample2.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/MockExample.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/MockExample2.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/DesiredGenerator.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/InterfaceMockGenerator.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestMockGenerator.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestMockGeneratorNoAttr.fs create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.fsproj create mode 100644 Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/version.json diff --git a/.github/workflows/dotnet.yaml b/.github/workflows/dotnet.yaml index 43f77c1..c3f5e65 100644 --- a/.github/workflows/dotnet.yaml +++ b/.github/workflows/dotnet.yaml @@ -190,6 +190,11 @@ jobs: with: name: nuget-package-httpclient path: Plugins/HttpClient/WoofWare.Whippet.Plugin.HttpClient/bin/Release/WoofWare.Whippet.Plugin.HttpClient.*.nupkg + - name: Upload NuGet artifact (interfacemock plugin) + uses: actions/upload-artifact@v4 + with: + name: nuget-package-interfacemock + path: Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/bin/Release/WoofWare.Whippet.Plugin.InterfaceMock.*.nupkg expected-pack: needs: [nuget-pack] @@ -267,6 +272,14 @@ jobs: - name: Check NuGet contents # Verify that there is exactly one nupkg in the artifact that would be NuGet published run: if [[ $(find packed-httpclient -maxdepth 1 -name 'WoofWare.Whippet.Plugin.HttpClient.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi + - name: Download NuGet artifact (interfacemock plugin) + uses: actions/download-artifact@v4 + with: + name: nuget-package-interfacemock + path: packed-interfacemock + - name: Check NuGet contents + # Verify that there is exactly one nupkg in the artifact that would be NuGet published + run: if [[ $(find packed-interfacemock -maxdepth 1 -name 'WoofWare.Whippet.Plugin.InterfaceMock.*.nupkg' -printf c | wc -c) -ne "1" ]]; then exit 1; fi check-accurate-generations: runs-on: ubuntu-latest @@ -565,3 +578,37 @@ jobs: nuget-key: ${{ secrets.NUGET_API_KEY }} nupkg-dir: packed/ dotnet: ${{ steps.dotnet-identify.outputs.dotnet }} + + nuget-publish-interfacemock-plugin: + runs-on: ubuntu-latest + if: ${{ !github.event.repository.fork && github.ref == 'refs/heads/main' }} + needs: [all-required-checks-complete] + environment: main-deploy + permissions: + id-token: write + attestations: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: Install Nix + uses: cachix/install-nix-action@v29 + with: + extra_nix_config: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Download NuGet artifact + uses: actions/download-artifact@v4 + with: + name: nuget-package-interfacemock + path: packed + - name: Identify `dotnet` + id: dotnet-identify + run: nix develop --command bash -c 'echo "dotnet=$(which dotnet)" >> $GITHUB_OUTPUT' + - name: Publish to NuGet + id: publish-success + uses: G-Research/common-actions/publish-nuget@2b7dc49cb14f3344fbe6019c14a31165e258c059 + with: + package-name: WoofWare.Whippet.Plugin.InterfaceMock + nuget-key: ${{ secrets.NUGET_API_KEY }} + nupkg-dir: packed/ + dotnet: ${{ steps.dotnet-identify.outputs.dotnet }} + diff --git a/Plugins/HttpClient/WoofWare.Whippet.Plugin.HttpClient/version.json b/Plugins/HttpClient/WoofWare.Whippet.Plugin.HttpClient/version.json index c5ad053..177cfe6 100644 --- a/Plugins/HttpClient/WoofWare.Whippet.Plugin.HttpClient/version.json +++ b/Plugins/HttpClient/WoofWare.Whippet.Plugin.HttpClient/version.json @@ -7,6 +7,8 @@ "./", ":/Plugins/Json/WoofWare.Whippet.Plugin.Json", "!:/Plugins/Json/WoofWare.Whippet.Plugin.Json/WoofWare.Whippet.Plugin.Json.Test/", + ":/WoofWare.Whippet.Core/", + ":/WoofWare.Whippet.Fantomas/", ":/global.json", ":/Directory.Build.props" ] diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/AssemblyInfo.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/AssemblyInfo.fs new file mode 100644 index 0000000..1224ab1 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/AssemblyInfo.fs @@ -0,0 +1,4 @@ +namespace WoofWare.Whippet.Plugin.InterfaceMock.Consumer + +[] +do () diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/GeneratedMockExample.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/GeneratedMockExample.fs new file mode 100644 index 0000000..dfd53d2 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/GeneratedMockExample.fs @@ -0,0 +1,195 @@ +namespace SomeNamespace + +open System + +/// Mock record type for an interface +type internal PublicTypeNoAttrMock = + { + Mem1 : string * int -> string list + Mem2 : string -> int + Mem3 : int * option -> string + } + + /// An implementation where every 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")) + } + + 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 + +open System + +/// Mock record type for an interface +type public PublicTypeInternalFalseNoAttrMock = + { + Mem1 : string * int -> string list + Mem2 : string -> int + Mem3 : int * option -> string + } + + /// An implementation where every 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")) + } + + 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 + +open System + +/// Mock record type for an interface +type internal InternalTypeNoAttrMock = + { + Mem1 : string * int -> unit + Mem2 : string -> int + } + + /// An implementation where every method throws. + static member Empty : InternalTypeNoAttrMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + } + + 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 + +open System + +/// Mock record type for an interface +type private PrivateTypeNoAttrMock = + { + Mem1 : string * int -> unit + Mem2 : string -> int + } + + /// An implementation where every method throws. + static member Empty : PrivateTypeNoAttrMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + } + + 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 + +open System + +/// Mock record type for an interface +type private PrivateTypeInternalFalseNoAttrMock = + { + Mem1 : string * int -> unit + Mem2 : string -> int + } + + /// An implementation where every method throws. + static member Empty : PrivateTypeInternalFalseNoAttrMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + } + + 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 + +open System + +/// Mock record type for an interface +type internal VeryPublicTypeNoAttrMock<'a, 'b> = + { + Mem1 : 'a -> 'b + } + + /// An implementation where every method throws. + static member Empty () : VeryPublicTypeNoAttrMock<'a, 'b> = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + } + + interface VeryPublicTypeNoAttr<'a, 'b> with + member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) +namespace SomeNamespace + +open System + +/// Mock record type for an interface +type internal CurriedNoAttrMock<'a> = + { + Mem1 : int -> 'a -> string + Mem2 : int * string -> 'a -> string + Mem3 : (int * string) -> 'a -> string + Mem4 : (int * string) -> ('a * int) -> string + Mem5 : int * string -> ('a * int) -> string + Mem6 : int * string -> 'a * int -> string + } + + /// An implementation where every 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")) + } + + 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 + +open System + +/// Mock record type for an interface +type internal TypeWithInterfaceNoAttrMock = + { + /// Implementation of IDisposable.Dispose + Dispose : unit -> unit + Mem1 : string option -> string[] Async + Mem2 : unit -> string[] Async + } + + /// An implementation where every 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")) + } + + 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/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/GeneratedMockExample2.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/GeneratedMockExample2.fs new file mode 100644 index 0000000..09fccf0 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/GeneratedMockExample2.fs @@ -0,0 +1,195 @@ +namespace SomeNamespace + +open System + +/// Mock record type for an interface +type internal PublicTypeMock = + { + Mem1 : string * int -> string list + Mem2 : string -> int + Mem3 : int * option -> string + } + + /// An implementation where every 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")) + } + + 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 + +open System + +/// Mock record type for an interface +type public PublicTypeInternalFalseMock = + { + Mem1 : string * int -> string list + Mem2 : string -> int + Mem3 : int * option -> string + } + + /// An implementation where every 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")) + } + + 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 + +open System + +/// Mock record type for an interface +type internal InternalTypeMock = + { + Mem1 : string * int -> unit + Mem2 : string -> int + } + + /// An implementation where every method throws. + static member Empty : InternalTypeMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + } + + 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 + +open System + +/// Mock record type for an interface +type private PrivateTypeMock = + { + Mem1 : string * int -> unit + Mem2 : string -> int + } + + /// An implementation where every method throws. + static member Empty : PrivateTypeMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + } + + 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 + +open System + +/// Mock record type for an interface +type private PrivateTypeInternalFalseMock = + { + Mem1 : string * int -> unit + Mem2 : string -> int + } + + /// An implementation where every method throws. + static member Empty : PrivateTypeInternalFalseMock = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + Mem2 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem2")) + } + + 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 + +open System + +/// Mock record type for an interface +type internal VeryPublicTypeMock<'a, 'b> = + { + Mem1 : 'a -> 'b + } + + /// An implementation where every method throws. + static member Empty () : VeryPublicTypeMock<'a, 'b> = + { + Mem1 = (fun _ -> raise (System.NotImplementedException "Unimplemented mock function: Mem1")) + } + + interface VeryPublicType<'a, 'b> with + member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0) +namespace SomeNamespace + +open System + +/// Mock record type for an interface +type internal CurriedMock<'a> = + { + Mem1 : int -> 'a -> string + Mem2 : int * string -> 'a -> string + Mem3 : (int * string) -> 'a -> string + Mem4 : (int * string) -> ('a * int) -> string + Mem5 : int * string -> ('a * int) -> string + Mem6 : int * string -> 'a * int -> string + } + + /// An implementation where every 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")) + } + + 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 + +open System + +/// Mock record type for an interface +type internal TypeWithInterfaceMock = + { + /// Implementation of IDisposable.Dispose + Dispose : unit -> unit + Mem1 : string option -> string[] Async + Mem2 : unit -> string[] Async + } + + /// An implementation where every 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")) + } + + 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 () diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/MockExample.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/MockExample.fs new file mode 100644 index 0000000..3d181fb --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/MockExample.fs @@ -0,0 +1,41 @@ +namespace SomeNamespace + +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/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/MockExample2.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/MockExample2.fs new file mode 100644 index 0000000..7600640 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/MockExample2.fs @@ -0,0 +1,41 @@ +namespace SomeNamespace + +open System + +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 diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj new file mode 100644 index 0000000..7cc005c --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Consumer/WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj @@ -0,0 +1,45 @@ + + + + net8.0 + true + false + + + + + + + MockExample.fs + InterfaceMock + InterfaceMock + InterfaceMock(false) + InterfaceMock + InterfaceMock + InterfaceMock(false) + InterfaceMock + InterfaceMock + InterfaceMock + + + + MockExample2.fs + InterfaceMock + InterfaceMock(false) + InterfaceMock + InterfaceMock + InterfaceMock(false) + InterfaceMock + InterfaceMock + InterfaceMock + + + + + + + + + + + diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/DesiredGenerator.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/DesiredGenerator.fs new file mode 100644 index 0000000..12b8ff8 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/DesiredGenerator.fs @@ -0,0 +1,11 @@ +namespace WoofWare.Whippet.Plugin.InterfaceMock + +type internal DesiredGenerator = + | InterfaceMock of isInternal : bool option + + static member Parse (s : string) = + match s with + | "InterfaceMock" -> DesiredGenerator.InterfaceMock None |> Some + | "InterfaceMock(true)" -> DesiredGenerator.InterfaceMock (Some true) |> Some + | "InterfaceMock(false)" -> DesiredGenerator.InterfaceMock (Some false) |> Some + | _ -> None diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/InterfaceMockGenerator.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/InterfaceMockGenerator.fs new file mode 100644 index 0000000..56b76d1 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/InterfaceMockGenerator.fs @@ -0,0 +1,333 @@ +namespace WoofWare.Whippet.Plugin.InterfaceMock + +open System +open Fantomas.FCS.Syntax +open Fantomas.FCS.Xml +open WoofWare.Whippet.Core +open WoofWare.Whippet.Fantomas + +type internal GenerateMockOutputSpec = + { + IsInternal : bool + } + +[] +module internal InterfaceMockGenerator = + open Fantomas.FCS.Text.Range + + let private getName (SynField (_, _, id, _, _, _, _, _, _)) = + match id with + | None -> failwith "Expected record field to have a name, but it was somehow anonymous" + | Some id -> id + + [] + type private KnownInheritance = | IDisposable + + let createType + (spec : GenerateMockOutputSpec) + (name : string) + (interfaceType : InterfaceType) + (xmlDoc : PreXmlDoc) + (fields : SynField list) + : SynModuleDecl + = + 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 + + let failwithFun (SynField (_, _, idOpt, _, _, _, _, _, _)) = + let failString = + match idOpt with + | None -> SynExpr.CreateConst "Unimplemented mock function" + | Some ident -> SynExpr.CreateConst $"Unimplemented mock function: %s{ident.idText}" + + 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 constructorFields = + let extras = + if inherits.Contains KnownInheritance.IDisposable then + let unitFun = SynExpr.createThunk (SynExpr.CreateConst ()) + + [ SynLongIdent.createS "Dispose", unitFun ] + else + [] + + let nonExtras = + fields + |> List.map (fun field -> SynLongIdent.createI (getName field), failwithFun field) + + extras @ nonExtras + + let constructor = + SynBinding.basic + [ Ident.create "Empty" ] + (if interfaceType.Generics.IsNone then + [] + else + [ SynPat.unit ]) + (AstHelper.instantiateRecord constructorFields) + |> SynBinding.withXmlDoc (PreXmlDoc.create "An implementation where every method throws.") + |> SynBinding.withReturnAnnotation constructorReturnType + |> SynMemberDefn.staticMember + + let fields = + 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 + [] + + extras @ fields + + 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 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, 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 = fields + Members = Some ([ constructor ; interfaceMembers ] @ extraInterfaces) + XmlDoc = Some xmlDoc + Generics = interfaceType.Generics + TypeAccessibility = Some access + ImplAccessibility = None + Attributes = [] + } + + let typeDecl = RecordType.ToAst record + + SynModuleDecl.Types ([ typeDecl ], range0) + + 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 constructMember (mem : MemberInfo) : SynField = + let inputType = mem.Args |> List.map constructMemberSinglePlace + + let funcType = SynType.toFun inputType mem.ReturnType + + { + Type = funcType + Attrs = [] + Ident = Some mem.Identifier + } + |> SynField.make + |> SynField.withDocString (mem.XmlDoc |> Option.defaultValue PreXmlDoc.Empty) + + let createRecord + (namespaceId : LongIdent) + (opens : SynOpenDeclTarget list) + (interfaceType : SynTypeDefn, spec : GenerateMockOutputSpec) + : SynModuleOrNamespace + = + let interfaceType = AstHelper.parseInterface interfaceType + let fields = interfaceType.Members |> List.map constructMember + 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 fields + + [ yield! opens |> List.map SynModuleDecl.openAny ; yield typeDecl ] + |> SynModuleOrNamespace.createNamespace namespaceId + +/// Myriad generator that creates a record which implements the given interface, +/// but with every field mocked out. +[] +type InterfaceMockGenerator () = + + interface IGenerateRawFromRaw with + member _.GenerateRawFromRaw (context : RawSourceGenerationArgs) = + if not (context.FilePath.EndsWith (".fs", StringComparison.Ordinal)) then + null + else + + let targetedTypes = + context.Parameters + |> Seq.map (fun (KeyValue (k, v)) -> k, v.Split '!' |> Array.toList |> List.map DesiredGenerator.Parse) + |> Map.ofSeq + + let ast = Ast.parse (System.Text.Encoding.UTF8.GetString context.FileContents) + + let types = Ast.getTypes ast + + let namespaceAndInterfaces = + types + |> List.choose (fun (ns, types) -> + types + |> List.choose (fun typeDef -> + 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 + | Some (DesiredGenerator.InterfaceMock arg) -> + let spec = + { + IsInternal = arg |> Option.defaultValue true + } + + Some (typeDef, spec) + | _ -> None + ) + | _ -> None + ) + |> function + | [] -> None + | ty -> Some (ns, ty) + ) + + let opens = AstHelper.extractOpens ast + + let modules = + namespaceAndInterfaces + |> List.collect (fun (ns, records) -> + records |> List.map (InterfaceMockGenerator.createRecord ns opens) + ) + + Ast.render modules |> Option.toObj diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md new file mode 100644 index 0000000..a410c0d --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/README.md @@ -0,0 +1,82 @@ +# WoofWare.Whippet.Plugin.InterfaceMock + +This is a [Whippet](https://github.com/Smaug123/WoofWare.Whippet) plugin for defining mocks for interfaces. + +It is a copy of the corresponding [Myriad](https://github.com/MoiraeSoftware/myriad) HttpClient plugin in [WoofWare.Myriad](https://github.com/Smaug123/WoofWare.Myriad), taken from commit d59ebdfccb87a06579fb99008a15f58ea8be394e. + +## Usage + +Define a file like `Client.fs`: + +```fsharp +type IPublicType = + abstract Mem1 : string * int -> string list + abstract Mem2 : string -> int +``` + +In your fsproj: + +```xml + + + + + Client.fs + InterfaceMock + + + + + + + + + +``` + +(This plugin follows a standard convention taken by `WoofWare.Whippet.Plugin` plugins, +where you use Whippet parameters with the same name as each input type, +whose contents are a `!`-delimited list of the generators which you wish to apply to that input type.) + +The generator produces a type like this: + +```fsharp +/// Mock record type for an interface +type internal PublicTypeMock = + { + Mem1 : string * int -> string list + Mem2 : string -> int + } + + static member Empty : PublicTypeMock = + { + Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) + Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function")) + } + + interface IPublicType with + member this.Mem1 (arg0, arg1) = this.Mem1 (arg0, arg1) + member this.Mem2 (arg0) = this.Mem2 (arg0) +``` + +### What's the point? + +Reflective mocking libraries like [Foq](https://github.com/fsprojects/Foq) in my experience are a rich source of flaky tests. +The [Grug-brained developer](https://grugbrain.dev/) would prefer to do this without reflection, and this reduces the rate of strange one-in-ten-thousand "failed to generate IL" errors. +But since F# does not let you partially update an interface definition, we instead stamp out a record, +thereby allowing the programmer to use F#'s record-update syntax. + +### Features + +You may supply an `isInternal : bool` argument: + +```xml + + Client.fs + InterfaceMock(false) + +``` + +By default, we make the resulting record type at most internal (never public), +since this is intended only to be used in tests; +but you can instead make it public by setting the `false` boolean. diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestMockGenerator.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestMockGenerator.fs new file mode 100644 index 0000000..cb322c8 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestMockGenerator.fs @@ -0,0 +1,36 @@ +namespace WoofWare.Whippet.Plugin.InterfaceMock.Test + +open System +open SomeNamespace +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" diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestMockGeneratorNoAttr.fs b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestMockGeneratorNoAttr.fs new file mode 100644 index 0000000..2e0fd08 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/TestMockGeneratorNoAttr.fs @@ -0,0 +1,36 @@ +namespace WoofWare.Whippet.Plugin.InterfaceMock.Test + +open System +open SomeNamespace +open NUnit.Framework +open FsUnitTyped + +[] +module TestMockGeneratorNoAttr = + + [] + 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/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj new file mode 100644 index 0000000..a428a3d --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.Test/WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj @@ -0,0 +1,26 @@ + + + + net8.0 + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.fsproj b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.fsproj new file mode 100644 index 0000000..f6ac97c --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock.fsproj @@ -0,0 +1,38 @@ + + + + netstandard2.1 + true + Patrick Stevens + Copyright (c) Patrick Stevens 2024 + Whippet F# source generator plugin, for generating records that mock an interface. + git + https://github.com/Smaug123/WoofWare.Whippet + MIT + README.md + fsharp;source-generator;source-gen;whippet;mock + true + FS3559 + WoofWare.Whippet.Plugin.InterfaceMock + true + true + NU5118 + + + + + + + True + / + README.md + + + + + + + + + + diff --git a/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/version.json b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/version.json new file mode 100644 index 0000000..41c1bb2 --- /dev/null +++ b/Plugins/InterfaceMock/WoofWare.Whippet.Plugin.InterfaceMock/version.json @@ -0,0 +1,13 @@ +{ + "version": "0.1", + "publicReleaseRefSpec": [ + "^refs/heads/main$" + ], + "pathFilters": [ + "./", + ":/WoofWare.Whippet.Core/", + ":/WoofWare.Whippet.Fantomas/", + ":/global.json", + ":/Directory.Build.props" + ] +} diff --git a/Plugins/Json/WoofWare.Whippet.Plugin.Json/DesiredGenerator.fs b/Plugins/Json/WoofWare.Whippet.Plugin.Json/DesiredGenerator.fs index 65e8cd9..f3010c2 100644 --- a/Plugins/Json/WoofWare.Whippet.Plugin.Json/DesiredGenerator.fs +++ b/Plugins/Json/WoofWare.Whippet.Plugin.Json/DesiredGenerator.fs @@ -6,10 +6,10 @@ type internal DesiredGenerator = static member Parse (s : string) = match s with - | "JsonParse" -> DesiredGenerator.JsonParse None - | "JsonParse(true)" -> DesiredGenerator.JsonParse (Some true) - | "JsonParse(false)" -> DesiredGenerator.JsonParse (Some false) - | "JsonSerialize" -> DesiredGenerator.JsonSerialize None - | "JsonSerialize(true)" -> DesiredGenerator.JsonSerialize (Some true) - | "JsonSerialize(false)" -> DesiredGenerator.JsonSerialize (Some false) - | _ -> failwith $"Failed to parse as a generator specification: %s{s}" + | "JsonParse" -> DesiredGenerator.JsonParse None |> Some + | "JsonParse(true)" -> DesiredGenerator.JsonParse (Some true) |> Some + | "JsonParse(false)" -> DesiredGenerator.JsonParse (Some false) |> Some + | "JsonSerialize" -> DesiredGenerator.JsonSerialize None |> Some + | "JsonSerialize(true)" -> DesiredGenerator.JsonSerialize (Some true) |> Some + | "JsonSerialize(false)" -> DesiredGenerator.JsonSerialize (Some false) |> Some + | _ -> None diff --git a/Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonParseGenerator.fs b/Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonParseGenerator.fs index 6debe22..8c340d6 100644 --- a/Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonParseGenerator.fs +++ b/Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonParseGenerator.fs @@ -739,7 +739,7 @@ type JsonParseGenerator () = desired |> List.tryPick (fun generator -> match generator with - | DesiredGenerator.JsonParse arg -> + | Some (DesiredGenerator.JsonParse arg) -> let spec = { ExtensionMethods = diff --git a/Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonSerializeGenerator.fs b/Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonSerializeGenerator.fs index 6f9a1f7..0029a3d 100644 --- a/Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonSerializeGenerator.fs +++ b/Plugins/Json/WoofWare.Whippet.Plugin.Json/JsonSerializeGenerator.fs @@ -555,7 +555,7 @@ type JsonSerializeGenerator () = desired |> List.tryPick (fun generator -> match generator with - | DesiredGenerator.JsonSerialize arg -> + | Some (DesiredGenerator.JsonSerialize arg) -> let spec = { ExtensionMethods = diff --git a/WoofWare.Whippet.sln b/WoofWare.Whippet.sln index 75e04d0..95036ea 100644 --- a/WoofWare.Whippet.sln +++ b/WoofWare.Whippet.sln @@ -36,6 +36,12 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Plugin.Htt EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Plugin.HttpClient.Test", "Plugins\HttpClient\WoofWare.Whippet.Plugin.HttpClient\WoofWare.Whippet.Plugin.HttpClient.Test\WoofWare.Whippet.Plugin.HttpClient.Test.fsproj", "{4DDD15F1-F273-441B-92F3-76BD9C089529}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Plugin.InterfaceMock", "Plugins\InterfaceMock\WoofWare.Whippet.Plugin.InterfaceMock\WoofWare.Whippet.Plugin.InterfaceMock.fsproj", "{C69E6668-87D9-4371-ACB3-2E671938A598}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Plugin.InterfaceMock.Consumer", "Plugins\InterfaceMock\WoofWare.Whippet.Plugin.InterfaceMock.Consumer\WoofWare.Whippet.Plugin.InterfaceMock.Consumer.fsproj", "{65C9BCE0-00CD-4CAE-AB0F-A9194B34ABAA}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "WoofWare.Whippet.Plugin.InterfaceMock.Test", "Plugins\InterfaceMock\WoofWare.Whippet.Plugin.InterfaceMock\WoofWare.Whippet.Plugin.InterfaceMock.Test\WoofWare.Whippet.Plugin.InterfaceMock.Test.fsproj", "{1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -114,5 +120,17 @@ Global {4DDD15F1-F273-441B-92F3-76BD9C089529}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DDD15F1-F273-441B-92F3-76BD9C089529}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DDD15F1-F273-441B-92F3-76BD9C089529}.Release|Any CPU.Build.0 = Release|Any CPU + {C69E6668-87D9-4371-ACB3-2E671938A598}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C69E6668-87D9-4371-ACB3-2E671938A598}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C69E6668-87D9-4371-ACB3-2E671938A598}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C69E6668-87D9-4371-ACB3-2E671938A598}.Release|Any CPU.Build.0 = Release|Any CPU + {65C9BCE0-00CD-4CAE-AB0F-A9194B34ABAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65C9BCE0-00CD-4CAE-AB0F-A9194B34ABAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65C9BCE0-00CD-4CAE-AB0F-A9194B34ABAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65C9BCE0-00CD-4CAE-AB0F-A9194B34ABAA}.Release|Any CPU.Build.0 = Release|Any CPU + {1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B0D4C01-66CD-4E91-9DA5-9ED4F3319AFA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal