Skip to content

Commit

Permalink
Refactor Setup Creation
Browse files Browse the repository at this point in the history
Add Verify Creation
  • Loading branch information
justindbaur committed Nov 19, 2023
1 parent 1c5b308 commit fafdaca
Show file tree
Hide file tree
Showing 48 changed files with 1,972 additions and 907 deletions.
17 changes: 15 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ insert_final_newline = false
[*.{cs,vb}]

# Organize usings
dotnet_separate_import_directive_groups = true
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
file_header_template = unset

Expand Down Expand Up @@ -74,6 +74,11 @@ dotnet_code_quality_unused_parameters = all:suggestion
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none

dotnet_analyzer_diagnostic.category-Performance.severity = suggestion
dotnet_analyzer_diagnostic.category-Design.severity = suggestion
dotnet_analyzer_diagnostic.category-Maintainability.severity = suggestion
dotnet_analyzer_diagnostic.category-Usage.severity = suggestion

#### C# Coding Conventions ####
[*.cs]

Expand Down Expand Up @@ -171,6 +176,12 @@ csharp_space_between_square_brackets = false
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true

# C# Style preferences
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = false:silent

#### Naming styles ####
[*.{cs,vb}]

Expand Down Expand Up @@ -361,4 +372,6 @@ dotnet_naming_style.s_camelcase.required_prefix = s_
dotnet_naming_style.s_camelcase.required_suffix =
dotnet_naming_style.s_camelcase.word_separator =
dotnet_naming_style.s_camelcase.capitalization = camel_case

tab_width = 4
indent_size = 4
end_of_line = crlf
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Pretender

[![Nuget](https://img.shields.io/nuget/v/Pretender)](https://www.nuget.org/packages/Pretender)
[![NuGet](https://img.shields.io/nuget/v/Pretender)](https://www.nuget.org/packages/Pretender)


## Example

```c#
var pretendMyInterface = Pretend.For<IMyInterface>();
var pretendMyInterface = Pretend.That<IMyInterface>();

pretendMyInterface
.Setup(i => i.MyMethod(It.IsAny<string>(), 14))
Expand Down
2 changes: 1 addition & 1 deletion example/Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<Features>$(Features);InterceptorsPreview</Features>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Pretender.SourceGeneration</InterceptorsPreviewNamespaces>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion example/Random.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class TestClass
{
public TestClass()
{
var pretend = Pretend.For<IInterface>();
var pretend = Pretend.That<IInterface>();
pretend.Setup(i => i.Greeting("John", 12));
}
}
Expand Down
18 changes: 14 additions & 4 deletions example/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class UnitTest1
[Fact]
public void Test1()
{
var pretendMyInterface = Pretend.For<IInterface>()
var pretendMyInterface = Pretend.That<IInterface>()
.Setup(i => i.Greeting("Mike", It.IsAny<int>()))
.Returns("Hi Mike!");

Expand All @@ -19,7 +19,7 @@ public void Test1()
[Fact]
public async Task Test2()
{
var pretend = Pretend.For<IMyInterface>();
var pretend = Pretend.That<IMyInterface>();

var local = "Value";

Expand All @@ -29,15 +29,25 @@ public async Task Test2()


var myOtherInterface = pretend.Create();

var value = await myOtherInterface.Greeting("Value");

pretend.Verify(i => i.Greeting(local), 1);
Assert.Equal("Thing", value);
}

[Fact]
[Fact]
public void Test3()
{
var pretend = Pretend.For<IInterface>()
var pretend = Pretend.That<IInterface>()
.Setup(i => i.Greeting("Hello", It.IsAny<int>()));

var item = pretend.Pretend.Create();

var response = item.Greeting("Hello", 12);
Assert.Null(response);

pretend.Verify(1);
}
}

Expand Down
25 changes: 25 additions & 0 deletions example/example.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example.csproj", "{2FBB56BB-D9CA-4D6F-95DB-9096E555B136}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2FBB56BB-D9CA-4D6F-95DB-9096E555B136}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2FBB56BB-D9CA-4D6F-95DB-9096E555B136}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2FBB56BB-D9CA-4D6F-95DB-9096E555B136}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2FBB56BB-D9CA-4D6F-95DB-9096E555B136}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {75D71B61-4537-4257-BCDB-BB11AA0B1EF6}
EndGlobalSection
EndGlobal
2 changes: 1 addition & 1 deletion perf/Comparison/Comparison.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<Features>$(Features);InterceptorsPreview</Features>
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Pretender.SourceGeneration</InterceptorsPreviewNamespaces>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion perf/Comparison/Simple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public string NSubstituteTest()
[Benchmark(Baseline = true)]
public string PretenderTest()
{
var pretend = Pretend.For<ISimpleInterface>();
var pretend = Pretend.That<ISimpleInterface>();

pretend.Setup(i => i.Foo(It.Is<string>(static i => i == "1")))
.Returns("2");
Expand Down
1 change: 1 addition & 0 deletions pretender.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SourceGeneratorTests", "tes
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9E33F6C1-8032-4CCA-A485-685B730A6EAE}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
README.md = README.md
EndProjectSection
EndProject
Expand Down
100 changes: 84 additions & 16 deletions src/Pretender.SourceGenerator/CreateEntrypoint.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,86 @@
using System;
using System.Collections.Generic;
using System.Text;

using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Immutable;

namespace Pretender.SourceGenerator
{
internal class CreateEntrypoint
{
public CreateEntrypoint(IInvocationOperation operation)
public CreateEntrypoint(IInvocationOperation operation, ImmutableArray<ITypeSymbol>? typeArguments)
{
Operation = operation;
Location = new InterceptsLocationInfo(operation);
TypeArguments = typeArguments;

// TODO: Do any Diagnostics?
}

public InterceptsLocationInfo Location { get; }
public IInvocationOperation Operation { get; }
public ImmutableArray<ITypeSymbol>? TypeArguments { get; }

public MethodDeclarationSyntax GetMethodDeclaration(int index)
{
var returnType = Operation.TargetMethod.ReturnType;
var returnTypeName = returnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var returnTypeSyntax = returnType.AsUnknownTypeSyntax();

TypeParameterSyntax[] typeParameters;
ParameterSyntax[] methodParameters;
ArgumentSyntax[] constructorArguments;

if (TypeArguments.HasValue)
{
typeParameters = new TypeParameterSyntax[TypeArguments.Value.Length];

var returnStatement = ReturnStatement(ObjectCreationExpression(ParseTypeName(returnType.ToPretendName()))
.AddArgumentListArguments(Argument(IdentifierName("pretend"))));
// We always take the Pretend<T> argument first as a this parameter
methodParameters = new ParameterSyntax[TypeArguments.Value.Length + 1];
constructorArguments = new ArgumentSyntax[TypeArguments.Value.Length + 1];

return MethodDeclaration(ParseTypeName(returnTypeName), $"Create{index}")
.WithBody(Block(returnStatement))
.WithParameterList(ParameterList(SeparatedList(new[]
for (var i = 0; i < TypeArguments.Value.Length; i++)
{
Parameter(Identifier("pretend"))
.WithType(ParseTypeName($"Pretend<{returnType}>"))
.WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword))),
})))
var typeName = $"T{i}";
var argName = $"arg{i}";

typeParameters[i] = TypeParameter(typeName);
methodParameters[i + 1] = Parameter(Identifier(argName))
.WithType(ParseTypeName(typeName));
constructorArguments[i + 1] = Argument(IdentifierName(argName));
}
}
else
{
typeParameters = [];
methodParameters = new ParameterSyntax[1];
constructorArguments = new ArgumentSyntax[1];
}

methodParameters[0] = Parameter(Identifier("pretend"))
.WithType(GenericName("Pretend")
.AddTypeArgumentListArguments(returnTypeSyntax))
.WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword))
);

constructorArguments[0] = Argument(IdentifierName("pretend"));

var objectCreation = ObjectCreationExpression(ParseTypeName(returnType.ToPretendName()))
.WithArgumentList(ArgumentList(SeparatedList(constructorArguments)));

var method = MethodDeclaration(returnTypeSyntax, $"Create{index}")
.WithBody(Block(ReturnStatement(objectCreation)))
.WithParameterList(ParameterList(SeparatedList(methodParameters)))
.WithModifiers(TokenList(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword)));

if (typeParameters.Length > 0)
{
return method
.WithTypeParameterList(TypeParameterList(SeparatedList(typeParameters)));
}

return method;
}
}

Expand All @@ -49,7 +90,34 @@ public class CreateEntryPointComparer : IEqualityComparer<CreateEntrypoint>

bool IEqualityComparer<CreateEntrypoint>.Equals(CreateEntrypoint x, CreateEntrypoint y)
{
return SymbolEqualityComparer.Default.Equals(x.Operation.TargetMethod.ReturnType, y.Operation.TargetMethod.ReturnType);
return SymbolEqualityComparer.Default.Equals(x.Operation.TargetMethod.ReturnType, y.Operation.TargetMethod.ReturnType)
&& CompareTypeArguments(x.TypeArguments, y.TypeArguments);
}

static bool CompareTypeArguments(ImmutableArray<ITypeSymbol>? x, ImmutableArray<ITypeSymbol>? y)
{
if (!x.HasValue)
{
return !y.HasValue;
}

var xArray = x.Value;
var yArray = y!.Value;

if (xArray.Length != yArray.Length)
{
return false;
}

for (int i = 0; i < xArray.Length; i++)
{
if (!SymbolEqualityComparer.IncludeNullability.Equals(xArray[i], yArray[i]))
{
return false;
}
}

return true;
}

int IEqualityComparer<CreateEntrypoint>.GetHashCode(CreateEntrypoint obj)
Expand Down
38 changes: 38 additions & 0 deletions src/Pretender.SourceGenerator/Emitter/CommonSyntax.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Pretender.SourceGenerator.Emitter
{
internal static class CommonSyntax
{
// General
public static PredefinedTypeSyntax VoidType { get; } = PredefinedType(Token(SyntaxKind.VoidKeyword));
public static TypeSyntax VarType { get; } = ParseTypeName("var");
public static GenericNameSyntax GenericPretendType { get; } = GenericName("Pretend");
public static UsingDirectiveSyntax UsingSystem { get; } = UsingDirective(ParseName("System"));
public static UsingDirectiveSyntax UsingSystemThreadingTasks { get; } = UsingDirective(ParseName("System.Threading.Tasks"));

// Verify
public static SyntaxToken SetupIdentifier { get; } = Identifier("setup");
public static SyntaxToken CalledIdentifier { get; } = Identifier("called");
public static ParameterSyntax CalledParameter { get; } = Parameter(CalledIdentifier)
.WithType(ParseTypeName("Called"));

public static CompilationUnitSyntax CreateVerifyCompilationUnit(MethodDeclarationSyntax[] verifyMethods)
{
var classDeclaration = ClassDeclaration("VerifyInterceptors")
.AddModifiers(Token(SyntaxKind.FileKeyword), Token(SyntaxKind.StaticKeyword))
.AddMembers(verifyMethods);

var namespaceDeclaration = NamespaceDeclaration(ParseName("Pretender.SourceGeneration"))
.AddMembers(classDeclaration)
.AddUsings(UsingSystem, KnownBlocks.CompilerServicesUsing, UsingSystemThreadingTasks, KnownBlocks.PretenderUsing, KnownBlocks.PretenderInternalsUsing);

return CompilationUnit()
.AddMembers(KnownBlocks.InterceptsLocationAttribute, namespaceDeclaration)
.NormalizeWhitespace();
}
}
}
Loading

0 comments on commit fafdaca

Please sign in to comment.