Skip to content

Commit

Permalink
feature: add parameterless AutoFixture constructor (#1184)
Browse files Browse the repository at this point in the history
* feature: add parameterless constructor

fixed value types using mocking frameworks

* ensure value types do not use mocking frameworks

* more test consolidation to memberdata

switch the comparer
regen tests, hopefully dont break the things

* remove example class

* Automatically linting code

---------

Co-authored-by: Rocket Understudy <33589210+rsg-bot@users.noreply.github.com>
  • Loading branch information
RLittlesII and rsg-bot authored Mar 30, 2024
1 parent 3c80cbe commit 8721019
Show file tree
Hide file tree
Showing 24 changed files with 314 additions and 275 deletions.
9 changes: 6 additions & 3 deletions src/Testing.AutoFixtures/Attribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ namespace Rocket.Surgery.Extensions.Testing.AutoFixtures;

internal static class Attribute
{
public const string Source = @"using System;
public const string Source = @"#nullable enable
using System;
using System.Diagnostics;
namespace Rocket.Surgery.Extensions.Testing.AutoFixtures
Expand All @@ -11,9 +12,11 @@ namespace Rocket.Surgery.Extensions.Testing.AutoFixtures
[Conditional(""CODEGEN"")]
internal class AutoFixtureAttribute : Attribute
{
public AutoFixtureAttribute(Type type) => Type = type;
public AutoFixtureAttribute() : this(null) {}
public Type Type { get; }
public AutoFixtureAttribute(Type? type) => Type = type;
public Type? Type { get; }
}
}";
}
49 changes: 45 additions & 4 deletions src/Testing.AutoFixtures/AutoFixtureGenerator+StaticGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,51 @@ private static ClassDeclarationSyntax BuildClassDeclaration(ISymbol namedTypeSym

private static MemberDeclarationSyntax BuildFields(
IParameterSymbol parameterSymbol,
InvocationExpressionSyntax invocationExpressionSyntax
Compilation compilation
)
{
var isAbstract = parameterSymbol.IsAbstract;
var isInterface = parameterSymbol.Type.TypeKind == TypeKind.Interface;
var isValueType = parameterSymbol.Type.IsValueType;

var symbolName = $"_{parameterSymbol.Name}";
if (isValueType)
{
return FieldDeclaration(
VariableDeclaration(
IdentifierName(
Identifier(
TriviaList(),
parameterSymbol.Type.GetGenericDisplayName(),
TriviaList(
Space
)
)
)
)
.WithVariables(
SingletonSeparatedList(
VariableDeclarator(
Identifier(symbolName)
)
.WithInitializer(
EqualsValueClause(
LiteralExpression(
SyntaxKind.DefaultLiteralExpression,
Token(SyntaxKind.DefaultKeyword)
)
)
)
)
)
)
.WithModifiers(
TokenList(
Token(SyntaxKind.PrivateKeyword)
)
);
}

return FieldDeclaration(
VariableDeclaration(
IdentifierName(
Expand All @@ -155,16 +197,15 @@ InvocationExpressionSyntax invocationExpressionSyntax
VariableDeclarator(
Identifier(
TriviaList(),
$"_{parameterSymbol.Name}",
symbolName,
TriviaList(
Space
)
)
)
.WithInitializer(
EqualsValueClause(
// TODO: [rlittlesii: February 29, 2024] Replace with FakeItEasy
invocationExpressionSyntax
GetFieldInvocation(compilation, parameterSymbol)
)
.WithEqualsToken(
Token(
Expand Down
73 changes: 52 additions & 21 deletions src/Testing.AutoFixtures/AutoFixtureGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,20 @@ void generateFixtureBuilder(
{
( var syntaxContext, var compilation ) = valueTuple;

var targetSymbol = syntaxContext.TargetSymbol as INamedTypeSymbol;

var substituteMetadata = compilation.GetTypeByMetadataName("NSubstitute.Substitute");
var fakeItEasy = compilation.GetTypeByMetadataName("FakeItEasy.Fake");

if (syntaxContext.Attributes[0].ConstructorArguments[0].Value is not INamedTypeSymbol namedTypeSymbol)
if (syntaxContext.Attributes[0].ConstructorArguments.Length == 0
|| syntaxContext.Attributes[0].ConstructorArguments[0].Value is not INamedTypeSymbol namedTypeSymbol)
{
return;
if (targetSymbol is null)
{
return;
}

namedTypeSymbol = targetSymbol;
}

var parameterSymbols =
Expand Down Expand Up @@ -74,7 +82,7 @@ void generateFixtureBuilder(
.Concat(parameterSymbols.Select(WithPropertyMethod))
.Concat(new[] { BuildBuildMethod(namedTypeSymbol, parameterSymbols), })
.Concat(
parameterSymbols.Select(symbol => BuildFields(symbol, GetFieldInvocation(compilation, symbol)))
parameterSymbols.Select(symbol => BuildFields(symbol, compilation))
);

var classDeclaration = BuildClassDeclaration(namedTypeSymbol)
Expand All @@ -83,30 +91,30 @@ void generateFixtureBuilder(
var namespaceDeclaration = BuildNamespace(syntaxContext.TargetSymbol)
.WithMembers(new(classDeclaration));

var usings =
var usingDirectives = new HashSet<string>(
parameterSymbols
.Select(symbol => symbol.Type.ContainingNamespace?.ToDisplayString() ?? string.Empty)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.OrderBy(x => x)
.Select(x => UsingDirective(ParseName(x)))
.ToArray();

var mockLibrary = UsingDirective(
ParseName(
( fakeItEasy is { }
? fakeItEasy.ContainingNamespace
: substituteMetadata?.ContainingNamespace )
?.ToDisplayString()
?? string.Empty
)
);
) { "System.Collections.ObjectModel", "Rocket.Surgery.Extensions.Testing.AutoFixtures", };

if (fakeItEasy is { })
{
usingDirectives.Add(fakeItEasy.ContainingNamespace.ToDisplayString());
}

if (substituteMetadata is { })
{
usingDirectives.Add(substituteMetadata.ContainingNamespace.ToDisplayString());
}

var usingDirectiveSyntax = usingDirectives
.OrderBy(usingDirective => usingDirective, NamespaceComparer.Default)
.Select(x => UsingDirective(ParseName(x)))
.ToArray();
var unit =
CompilationUnit()
.AddUsings(UsingDirective(ParseName("System.Collections.ObjectModel")))
.AddUsings(usings)
.AddUsings(mockLibrary)
.AddUsings(UsingDirective(ParseName("Rocket.Surgery.Extensions.Testing.AutoFixtures")))
.AddUsings(usingDirectiveSyntax)
.AddMembers(namespaceDeclaration)
.NormalizeWhitespace();

Expand All @@ -128,4 +136,27 @@ public int GetHashCode(IParameterSymbol obj)
return SymbolEqualityComparer.Default.GetHashCode(obj.Type) + obj.Type.GetHashCode() + obj.Name.GetHashCode();
}
}

internal class NamespaceComparer : IComparer<string>
{
public static NamespaceComparer Default { get; } = new();

public int Compare(string x, string y)
{
// Check if both namespaces start with "System"
var xIsSystem = x.StartsWith("System", StringComparison.Ordinal);
var yIsSystem = y.StartsWith("System", StringComparison.Ordinal);

return xIsSystem switch
{
// If only one of them starts with "System", prioritize it
true when !yIsSystem => -1,
false when yIsSystem => 1,
// If both start with "System" or neither does, compare them alphabetically
true when yIsSystem => string.Compare(x, y, StringComparison.Ordinal),
false when !yIsSystem => string.Compare(x, y, StringComparison.Ordinal),
_ => xIsSystem ? -1 : 1,
};
}
}
}
Loading

0 comments on commit 8721019

Please sign in to comment.