Skip to content

Commit

Permalink
Swich to Text Writer dotnet/runtime#95882
Browse files Browse the repository at this point in the history
  • Loading branch information
justindbaur committed Dec 24, 2023
1 parent ae0dc6b commit de8c87d
Show file tree
Hide file tree
Showing 36 changed files with 1,947 additions and 1,049 deletions.
12 changes: 8 additions & 4 deletions example/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,19 @@ public async Task Test2()
[Fact]
public void Test3()
{
var pretend = Pretend.That<IInterface>()
var pretend = Pretend.That<IInterface>();

var setup = pretend
.Setup(i => i.Greeting("Hello", It.IsAny<int>()));

var item = pretend.Pretend.Create();
setup.Returns("2");

var item = pretend.Create();

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

pretend.Verify(1);
setup.Verify(1);
}
}

Expand Down
6 changes: 3 additions & 3 deletions perf/Comparison/Simple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public string MoqTest()
{
var mock = new Moq.Mock<ISimpleInterface>();

mock.Setup(i => i.Foo(Moq.It.Is<string>(static i => i == "1")))
mock.Setup(i => i.Foo(Moq.It.IsAny<string>()))
.Returns("2");

var simpleInterface = mock.Object;
Expand All @@ -23,7 +23,7 @@ public string NSubstituteTest()
{
var substitute = NSubstitute.Substitute.For<ISimpleInterface>();

NSubstitute.SubstituteExtensions.Returns(substitute.Foo(NSubstitute.Arg.Is<string>(static i => i == "1")), "2");
NSubstitute.SubstituteExtensions.Returns(substitute.Foo(NSubstitute.Arg.Any<string>()), "2");

return substitute.Foo("1");
}
Expand All @@ -33,7 +33,7 @@ public string PretenderTest()
{
var pretend = Pretend.That<ISimpleInterface>();

pretend.Setup(i => i.Foo(It.Is<string>(static i => i == "1")))
pretend.Setup(i => i.Foo(It.IsAny<string>()))
.Returns("2");

var simpleInterface = pretend.Create();
Expand Down
56 changes: 56 additions & 0 deletions src/Pretender.SourceGenerator/CSharpSyntaxUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Globalization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Pretender.SourceGenerator
{
internal static class CSharpSyntaxUtilities
{
// Standard format for double and single on non-inbox frameworks to ensure value is round-trippable.
public const string DoubleFormatString = "G17";
public const string SingleFormatString = "G9";

// Format a literal in C# format -- works around https://github.com/dotnet/roslyn/issues/58705

public static string FormatLiteral(object? value, ITypeSymbol type)
{
if (value == null)
{
return $"default({type.ToFullDisplayString()})";
}

switch (value)
{
case string @string:
return SymbolDisplay.FormatLiteral(@string, quote: true);
case char @char:
return SymbolDisplay.FormatLiteral(@char, quote: true);
case double.NegativeInfinity:
return "double.NegativeInfinity";
case double.PositiveInfinity:
return "double.PositiveInfinity";
case double.NaN:
return "double.NaN";
case double @double:
return $"{@double.ToString(DoubleFormatString, CultureInfo.InvariantCulture)}D";
case float.NegativeInfinity:
return "float.NegativeInfinity";
case float.PositiveInfinity:
return "float.PositiveInfinity";
case float.NaN:
return "float.NaN";
case float @float:
return $"{@float.ToString(SingleFormatString, CultureInfo.InvariantCulture)}F";
case decimal @decimal:
// we do not need to specify a format string for decimal as it's default is round-trippable on all frameworks.
return $"{@decimal.ToString(CultureInfo.InvariantCulture)}M";
case bool @bool:
return @bool ? "true" : "false";
default:
// Assume this is a number.
return FormatNumber();
}
string FormatNumber() => $"({type.ToFullDisplayString()})({Convert.ToString(value, CultureInfo.InvariantCulture)})";
}
}
}
17 changes: 0 additions & 17 deletions src/Pretender.SourceGenerator/Emitter/CommonSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,11 @@ internal static class CommonSyntax
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();
}
}
}
83 changes: 39 additions & 44 deletions src/Pretender.SourceGenerator/Emitter/CreateEmitter.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Pretender.SourceGenerator.Writing;

namespace Pretender.SourceGenerator.Emitter
{
Expand All @@ -24,67 +24,62 @@ public CreateEmitter(IInvocationOperation originalOperation, ImmutableArray<ITyp

public IInvocationOperation Operation => _originalOperation;

public MethodDeclarationSyntax Emit(CancellationToken cancellationToken)
public void Emit(IndentedTextWriter writer, CancellationToken cancellationToken)
{
var returnType = _originalOperation.TargetMethod.ReturnType;

var returnTypeSyntax = returnType.AsUnknownTypeSyntax();
var returnTypeSyntax = returnType.ToUnknownTypeString();

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

if (_typeArguments.HasValue)
foreach (var location in _locations)
{
typeParameters = new TypeParameterSyntax[_typeArguments.Value.Length];
writer.WriteLine(@$"[InterceptsLocation(@""{location.FilePath}"", {location.LineNumber}, {location.CharacterNumber})]");
}
writer.Write($"internal static {returnType.ToUnknownTypeString()} Create{_index}");

// 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];
if (_typeArguments is ImmutableArray<ITypeSymbol> typeArguments && typeArguments.Length > 0)
{
// <T0, T1>(this Pretend<IInterface> pretend, T0 arg0, T1 arg1)
writer.Write("<");

for (var i = 0; i < _typeArguments.Value.Length; i++)
{
var typeName = $"T{i}";
var argName = $"arg{i}";
writer.Write($"T{i}");
}

typeParameters[i] = TypeParameter(typeName);
methodParameters[i + 1] = Parameter(Identifier(argName))
.WithType(ParseTypeName(typeName));
constructorArguments[i + 1] = Argument(IdentifierName(argName));
writer.Write($">(this Pretend<{returnTypeSyntax}> pretend");

for (var i = 0; i < _typeArguments.Value.Length; i++)
{
writer.Write($", T{i} arg{i}");
}

writer.WriteLine(")");
}
else
{
typeParameters = [];
methodParameters = new ParameterSyntax[1];
constructorArguments = new ArgumentSyntax[1];
// TODO: Handle the params overload
writer.WriteLine($"(this Pretend<{returnTypeSyntax}> pretend)");
}

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)));
using (writer.WriteBlock())
{
writer.Write($"return new {returnType.ToPretendName()}(pretend");

method = method.WithAttributeLists(List(CreateInterceptsAttributes()));
if (_typeArguments.HasValue)
{
for (int i = 0; i < _typeArguments.Value.Length; i++)
{
writer.Write($", arg{i}");
}

if (typeParameters.Length > 0)
{
return method
.WithTypeParameterList(TypeParameterList(SeparatedList(typeParameters)));
writer.WriteLine(");");
}
else
{
// TODO: Handle params overload
writer.WriteLine(");");
}
}

return method;
}

private ImmutableArray<AttributeListSyntax> CreateInterceptsAttributes()
Expand Down
Loading

0 comments on commit de8c87d

Please sign in to comment.