Compare commits

...

4 Commits

Author SHA1 Message Date
Patrick Stevens
e3081c3136 Deal with unit type in generated mock (#124) 2024-04-17 08:44:31 +01:00
Patrick Stevens
232d2ba5ec Relax arg checking strictness (#123) 2024-04-16 22:47:06 +01:00
Patrick Stevens
f7458f521e Track inheritance in GenerateMock (#122) 2024-04-16 22:23:32 +01:00
dependabot[bot]
bfc25a672b Bump fantomas from 6.3.0 to 6.3.3 (#120)
* Bump fantomas from 6.3.0 to 6.3.3

Bumps [fantomas](https://github.com/fsprojects/fantomas) from 6.3.0 to 6.3.3.
- [Release notes](https://github.com/fsprojects/fantomas/releases)
- [Changelog](https://github.com/fsprojects/fantomas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsprojects/fantomas/compare/v6.3.0...v6.3.3)

---
updated-dependencies:
- dependency-name: fantomas
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix dep

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Smaug123 <3138005+Smaug123@users.noreply.github.com>
2024-04-15 21:23:09 +00:00
7 changed files with 247 additions and 28 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"fantomas": { "fantomas": {
"version": "6.3.0", "version": "6.3.3",
"commands": [ "commands": [
"fantomas" "fantomas"
] ]

View File

@@ -5,6 +5,7 @@
namespace SomeNamespace namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
@@ -25,10 +26,11 @@ type internal PublicTypeMock =
interface IPublicType with interface IPublicType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) 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.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) member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
@@ -49,10 +51,11 @@ type public PublicTypeInternalFalseMock =
interface IPublicTypeInternalFalse with interface IPublicTypeInternalFalse with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) 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.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) member this.Mem3 (arg_0_0, arg_0_1) = this.Mem3 (arg_0_0, arg_0_1)
namespace SomeNamespace namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
@@ -71,9 +74,10 @@ type internal InternalTypeMock =
interface InternalType with interface InternalType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) 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.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
@@ -92,9 +96,10 @@ type private PrivateTypeMock =
interface PrivateType with interface PrivateType with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) 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.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
@@ -113,9 +118,10 @@ type private PrivateTypeInternalFalseMock =
interface PrivateTypeInternalFalse with interface PrivateTypeInternalFalse with
member this.Mem1 (arg_0_0, arg_0_1) = this.Mem1 (arg_0_0, arg_0_1) 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.Mem2 arg_0_0 = this.Mem2 (arg_0_0)
namespace SomeNamespace namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
@@ -131,9 +137,10 @@ type internal VeryPublicTypeMock<'a, 'b> =
} }
interface VeryPublicType<'a, 'b> with interface VeryPublicType<'a, 'b> with
member this.Mem1 (arg_0_0) = this.Mem1 (arg_0_0) member this.Mem1 arg_0_0 = this.Mem1 (arg_0_0)
namespace SomeNamespace namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
/// Mock record type for an interface /// Mock record type for an interface
@@ -159,9 +166,9 @@ type internal CurriedMock<'a> =
} }
interface Curried<'a> with interface Curried<'a> with
member this.Mem1 (arg_0_0) (arg_1_0) = this.Mem1 (arg_0_0) (arg_1_0) 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.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.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)) = 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) this.Mem4 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
@@ -171,3 +178,31 @@ type internal CurriedMock<'a> =
member this.Mem6 (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) this.Mem6 (arg_0_0, arg_0_1) (arg_1_0, arg_1_1)
namespace SomeNamespace
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
Mem2 : unit -> string[] Async
}
/// An implementation where every method throws.
static member Empty : TypeWithInterfaceMock =
{
Dispose = (fun _ -> ())
Mem1 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
Mem2 = (fun x -> raise (System.NotImplementedException "Unimplemented mock function"))
}
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 ()

View File

@@ -1,5 +1,6 @@
namespace SomeNamespace namespace SomeNamespace
open System
open WoofWare.Myriad.Plugins open WoofWare.Myriad.Plugins
[<GenerateMock>] [<GenerateMock>]
@@ -41,3 +42,9 @@ type Curried<'a> =
abstract Mem4 : (int * string) -> ('a * int) -> string abstract Mem4 : (int * string) -> ('a * int) -> string
abstract Mem5 : x : int * string -> ('a * int) -> string abstract Mem5 : x : int * string -> ('a * int) -> string
abstract Mem6 : int * string -> y : 'a * int -> string abstract Mem6 : int * string -> y : 'a * int -> string
[<GenerateMock>]
type TypeWithInterface =
inherit IDisposable
abstract Mem1 : string option -> string[] Async
abstract Mem2 : unit -> string[] Async

View File

@@ -1,5 +1,6 @@
namespace WoofWare.Myriad.Plugins namespace WoofWare.Myriad.Plugins
open System.IO
open Fantomas.FCS.Syntax open Fantomas.FCS.Syntax
open Fantomas.FCS.SyntaxTrivia open Fantomas.FCS.SyntaxTrivia
open Fantomas.FCS.Text.Range open Fantomas.FCS.Text.Range
@@ -54,6 +55,7 @@ type internal InterfaceType =
{ {
Attributes : SynAttribute list Attributes : SynAttribute list
Name : LongIdent Name : LongIdent
Inherits : SynType list
Members : MemberInfo list Members : MemberInfo list
Properties : PropertyInfo list Properties : PropertyInfo list
Generics : SynTyparDecls option Generics : SynTyparDecls option
@@ -131,6 +133,11 @@ module internal AstHelper =
// TODO: consider Microsoft.FSharp.Option or whatever it is // TODO: consider Microsoft.FSharp.Option or whatever it is
| _ -> false | _ -> false
let isUnitIdent (ident : SynLongIdent) : bool =
match ident.LongIdent with
| [ i ] when System.String.Equals (i.idText, "unit", System.StringComparison.OrdinalIgnoreCase) -> true
| _ -> false
let isListIdent (ident : SynLongIdent) : bool = let isListIdent (ident : SynLongIdent) : bool =
match ident.LongIdent with match ident.LongIdent with
| [ i ] when System.String.Equals (i.idText, "list", System.StringComparison.OrdinalIgnoreCase) -> true | [ i ] when System.String.Equals (i.idText, "list", System.StringComparison.OrdinalIgnoreCase) -> true
@@ -342,7 +349,18 @@ module internal AstHelper =
} }
|> List.singleton |> List.singleton
} }
| _ -> failwith $"Unrecognised args in interface method declaration: %+A{args}" | arg ->
{
HasParen = false
Args =
{
Attributes = []
IsOptional = false
Id = None
Type = arg
}
|> List.singleton
}
|> fun ty -> |> fun ty ->
{ ty with { ty with
HasParen = ty.HasParen || hasParen HasParen = ty.HasParen || hasParen
@@ -386,22 +404,26 @@ module internal AstHelper =
let attrs = attrs |> List.collect (fun s -> s.Attributes) let attrs = attrs |> List.collect (fun s -> s.Attributes)
let members, properties = let members, inherits =
match synTypeDefnRepr with match synTypeDefnRepr with
| SynTypeDefnRepr.ObjectModel (_kind, members, _) -> | SynTypeDefnRepr.ObjectModel (_kind, members, _) ->
members members
|> List.map (fun defn -> |> List.map (fun defn ->
match defn with match defn with
| SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> parseMember slotSig flags | SynMemberDefn.AbstractSlot (slotSig, flags, _, _) -> Choice1Of2 (parseMember slotSig flags)
| SynMemberDefn.Inherit (baseType, _asIdent, _) -> Choice2Of2 baseType
| _ -> failwith $"Unrecognised member definition: %+A{defn}" | _ -> failwith $"Unrecognised member definition: %+A{defn}"
) )
| _ -> failwith $"Unrecognised SynTypeDefnRepr for an interface type: %+A{synTypeDefnRepr}" | _ -> failwith $"Unrecognised SynTypeDefnRepr for an interface type: %+A{synTypeDefnRepr}"
|> List.partitionChoice |> List.partitionChoice
let members, properties = members |> List.partitionChoice
{ {
Members = members Members = members
Properties = properties Properties = properties
Name = interfaceName Name = interfaceName
Inherits = inherits
Attributes = attrs Attributes = attrs
Generics = typars Generics = typars
Accessibility = accessibility Accessibility = accessibility
@@ -486,6 +508,11 @@ module internal SynTypePatterns =
Some innerType Some innerType
| _ -> None | _ -> None
let (|UnitType|_|) (fieldType : SynType) : unit option =
match fieldType with
| SynType.LongIdent ident when AstHelper.isUnitIdent ident -> Some ()
| _ -> None
let (|ListType|_|) (fieldType : SynType) = let (|ListType|_|) (fieldType : SynType) =
match fieldType with match fieldType with
| SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isListIdent ident -> | SynType.App (SynType.LongIdent ident, _, [ innerType ], _, _, _, _) when AstHelper.isListIdent ident ->

View File

@@ -764,6 +764,10 @@ module internal HttpClientGenerator =
= =
let interfaceType = AstHelper.parseInterface interfaceType let interfaceType = AstHelper.parseInterface interfaceType
if not (List.isEmpty interfaceType.Inherits) then
failwith
"HttpClientGenerator does not support inheritance. Remove the `inherit` keyword if you want to use this generator."
let constantHeaders = let constantHeaders =
interfaceType.Attributes interfaceType.Attributes
|> extractHeaderInformation |> extractHeaderInformation

View File

@@ -21,6 +21,9 @@ module internal InterfaceMockGenerator =
| None -> failwith "Expected record field to have a name, but it was somehow anonymous" | None -> failwith "Expected record field to have a name, but it was somehow anonymous"
| Some id -> id | Some id -> id
[<RequireQualifiedAccess>]
type private KnownInheritance = | IDisposable
let createType let createType
(spec : GenerateMockOutputSpec) (spec : GenerateMockOutputSpec)
(name : string) (name : string)
@@ -29,6 +32,20 @@ module internal InterfaceMockGenerator =
(fields : SynField list) (fields : SynField list)
: SynModuleDecl : 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 synValData = let synValData =
{ {
SynMemberFlags.IsInstance = false SynMemberFlags.IsInstance = false
@@ -90,6 +107,23 @@ module internal InterfaceMockGenerator =
) )
|> SynBindingReturnInfo.Create |> SynBindingReturnInfo.Create
let constructorFields =
let extras =
if inherits.Contains KnownInheritance.IDisposable then
let unitFun = SynExpr.createLambda "_" SynExpr.CreateUnit
[
(SynLongIdent.CreateFromLongIdent [ Ident.Create "Dispose" ], true), Some unitFun
]
else
[]
let nonExtras =
fields
|> List.map (fun field -> (SynLongIdent.CreateFromLongIdent [ getName field ], true), Some failwithFun)
extras @ nonExtras
let constructor = let constructor =
SynMemberDefn.Member ( SynMemberDefn.Member (
SynBinding.SynBinding ( SynBinding.SynBinding (
@@ -102,12 +136,7 @@ module internal InterfaceMockGenerator =
SynValData.SynValData (Some synValData, SynValInfo.Empty, None), SynValData.SynValData (Some synValData, SynValInfo.Empty, None),
constructorIdent, constructorIdent,
Some constructorReturnType, Some constructorReturnType,
AstHelper.instantiateRecord ( AstHelper.instantiateRecord constructorFields,
fields
|> List.map (fun field ->
((SynLongIdent.CreateFromLongIdent [ getName field ], true), Some failwithFun)
)
),
range0, range0,
DebugPointAtBinding.Yes range0, DebugPointAtBinding.Yes range0,
{ SynExpr.synBindingTriviaZero true with { SynExpr.synBindingTriviaZero true with
@@ -117,6 +146,21 @@ module internal InterfaceMockGenerator =
range0 range0
) )
let fields =
let extras =
if inherits.Contains KnownInheritance.IDisposable then
[
SynField.Create (
SynType.CreateFun (SynType.CreateUnit, SynType.CreateUnit),
Ident.Create "Dispose",
xmldoc = PreXmlDoc.Create " Implementation of IDisposable.Dispose"
)
]
else
[]
extras @ fields
let interfaceMembers = let interfaceMembers =
let members = let members =
interfaceType.Members interfaceType.Members
@@ -150,7 +194,9 @@ module internal InterfaceMockGenerator =
|> List.mapi (fun i arg -> |> List.mapi (fun i arg ->
arg.Args arg.Args
|> List.mapi (fun j arg -> |> List.mapi (fun j arg ->
SynArgInfo.CreateIdString $"arg_%i{i}_%i{j}" match arg.Type with
| UnitType -> SynArgInfo.SynArgInfo ([], false, None)
| _ -> SynArgInfo.CreateIdString $"arg_%i{i}_%i{j}"
) )
) )
], ],
@@ -165,10 +211,18 @@ module internal InterfaceMockGenerator =
|> List.mapi (fun i tupledArgs -> |> List.mapi (fun i tupledArgs ->
let args = let args =
tupledArgs.Args tupledArgs.Args
|> List.mapi (fun j _ -> SynPat.CreateNamed (Ident.Create $"arg_%i{i}_%i{j}")) |> List.mapi (fun j ty ->
match ty.Type with
| UnitType -> SynPat.Const (SynConst.Unit, range0)
| _ -> SynPat.CreateNamed (Ident.Create $"arg_%i{i}_%i{j}")
)
SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0) match args with
|> SynPat.CreateParen | [] -> failwith "somehow got no args at all"
| [ arg ] -> arg
| args ->
SynPat.Tuple (false, args, List.replicate (args.Length - 1) range0, range0)
|> SynPat.CreateParen
|> fun i -> if tupledArgs.HasParen then SynPat.Paren (i, range0) else i |> fun i -> if tupledArgs.HasParen then SynPat.Paren (i, range0) else i
) )
@@ -187,7 +241,11 @@ module internal InterfaceMockGenerator =
memberInfo.Args memberInfo.Args
|> List.mapi (fun i args -> |> List.mapi (fun i args ->
args.Args args.Args
|> List.mapi (fun j args -> SynExpr.CreateIdentString $"arg_%i{i}_%i{j}") |> List.mapi (fun j arg ->
match arg.Type with
| UnitType -> SynExpr.CreateConst SynConst.Unit
| _ -> SynExpr.CreateIdentString $"arg_%i{i}_%i{j}"
)
|> SynExpr.CreateParenedTuple |> SynExpr.CreateParenedTuple
) )
@@ -264,11 +322,100 @@ module internal InterfaceMockGenerator =
| Some (SynAccess.Internal _), _ -> SynAccess.Internal range0 | Some (SynAccess.Internal _), _ -> SynAccess.Internal range0
| Some (SynAccess.Private _), _ -> SynAccess.Private range0 | Some (SynAccess.Private _), _ -> SynAccess.Private range0
let extraInterfaces =
inherits
|> Seq.map (fun inheritance ->
match inheritance with
| KnownInheritance.IDisposable ->
let valData =
SynValData.SynValData (
Some
{
IsInstance = true
IsDispatchSlot = false
IsOverrideOrExplicitImpl = true
IsFinal = false
GetterOrSetterIsCompilerGenerated = false
MemberKind = SynMemberKind.Member
},
valInfo =
SynValInfo.SynValInfo (
curriedArgInfos =
[
yield
[
SynArgInfo.SynArgInfo (
attributes = [],
optional = false,
ident = None
)
]
],
returnInfo =
SynArgInfo.SynArgInfo (attributes = [], optional = false, ident = None)
),
thisIdOpt = None
)
let headArgs = [ SynPat.Const (SynConst.Unit, range0) ]
let headPat =
SynPat.LongIdent (
SynLongIdent.CreateFromLongIdent [ Ident.Create "this" ; Ident.Create "Dispose" ],
None,
None,
SynArgPats.Pats headArgs,
None,
range0
)
let binding =
SynBinding.SynBinding (
None,
SynBindingKind.Normal,
false,
false,
[],
PreXmlDoc.Empty,
valData,
headPat,
Some (
SynBindingReturnInfo.SynBindingReturnInfo (
SynType.Unit (),
range0,
[],
SynBindingReturnInfoTrivia.Zero
)
),
SynExpr.CreateApp (
SynExpr.CreateLongIdent (SynLongIdent.Create [ "this" ; "Dispose" ]),
SynExpr.CreateUnit
),
range0,
DebugPointAtBinding.Yes range0,
{
LeadingKeyword = SynLeadingKeyword.Member range0
InlineKeyword = None
EqualsRange = Some range0
}
)
let mem = SynMemberDefn.Member (binding, range0)
SynMemberDefn.Interface (
SynType.CreateLongIdent (SynLongIdent.Create [ "System" ; "IDisposable" ]),
Some range0,
Some [ mem ],
range0
)
)
|> Seq.toList
let record = let record =
{ {
Name = Ident.Create name Name = Ident.Create name
Fields = fields Fields = fields
Members = Some [ constructor ; interfaceMembers ] Members = Some ([ constructor ; interfaceMembers ] @ extraInterfaces)
XmlDoc = Some xmlDoc XmlDoc = Some xmlDoc
Generics = interfaceType.Generics Generics = interfaceType.Generics
Accessibility = Some access Accessibility = Some access
@@ -333,7 +480,6 @@ module internal InterfaceMockGenerator =
let typeDecl = createType spec name interfaceType docString fields let typeDecl = createType spec name interfaceType docString fields
SynModuleOrNamespace.CreateNamespace ( SynModuleOrNamespace.CreateNamespace (
namespaceId, namespaceId,
decls = (opens |> List.map SynModuleDecl.CreateOpen) @ [ typeDecl ] decls = (opens |> List.map SynModuleDecl.CreateOpen) @ [ typeDecl ]

View File

@@ -8,8 +8,8 @@
}) })
(fetchNuGet { (fetchNuGet {
pname = "fantomas"; pname = "fantomas";
version = "6.3.0"; version = "6.3.3";
sha256 = "sha256-PWiyzkiDL8LBE/fwClS0d6PrE0D5pKYYZiMDZmyk9Y0="; sha256 = "sha256-02uTwRPJkRZtjJ7fOJdHSvc17DszkXjT5X9jGuRZlA4=";
}) })
(fetchNuGet { (fetchNuGet {
pname = "ApiSurface"; pname = "ApiSurface";