diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/ControllerNullableParametersTests.cs b/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/ControllerNullableParametersTests.cs new file mode 100644 index 00000000..e13ce855 --- /dev/null +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/ControllerNullableParametersTests.cs @@ -0,0 +1,15 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Moq.AutoMock.Generator.Example.MSTest; + +[TestClass] +[ConstructorTests(typeof(ControllerWithSomeNullableParameters), Behavior = TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerNullableParametersTests +{ + partial void ControllerWithSomeNullableParametersConstructor_WithNullstring_ThrowsArgumentNullExceptionSetup(AutoMocker mocker) + { + mocker.Use(""); + mocker.Use(42); + mocker.Use(24); + } +} diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/ControllerNullableParametersTests.cs b/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/ControllerNullableParametersTests.cs new file mode 100644 index 00000000..6a7a8759 --- /dev/null +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/ControllerNullableParametersTests.cs @@ -0,0 +1,12 @@ +namespace Moq.AutoMock.Generator.Example.NUnit; + +[ConstructorTests(typeof(ControllerWithSomeNullableParameters), Behavior = TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerNullableParametersTests +{ + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName) + { + mocker.Use(""); + mocker.Use(42); + mocker.Use(24); + } +} diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/ControllerNullableParametersTests.cs b/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/ControllerNullableParametersTests.cs new file mode 100644 index 00000000..70622634 --- /dev/null +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/ControllerNullableParametersTests.cs @@ -0,0 +1,12 @@ +namespace Moq.AutoMock.Generator.Example.xUnit; + +[ConstructorTests(typeof(ControllerWithSomeNullableParameters), Behavior = TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerNullableParametersTests +{ + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName) + { + mocker.Use(""); + mocker.Use(42); + mocker.Use(24); + } +} diff --git a/GeneratorTests/Moq.AutoMocker.Generator.Example/ControllerWithSomeNullableParameters.cs b/GeneratorTests/Moq.AutoMocker.Generator.Example/ControllerWithSomeNullableParameters.cs new file mode 100644 index 00000000..0d8ed9a4 --- /dev/null +++ b/GeneratorTests/Moq.AutoMocker.Generator.Example/ControllerWithSomeNullableParameters.cs @@ -0,0 +1,15 @@ +namespace Moq.AutoMock.Generator.Example; + +public class ControllerWithSomeNullableParameters +{ + public ControllerWithSomeNullableParameters( + string name, // Test generated + int years, // No test generated + string? nullableName, // No test generated + string? testName = null, // No test generated + string foo = null!, // No test generated + int? age = null) // No test generated + { + ArgumentNullException.ThrowIfNull(name); + } +} diff --git a/Moq.AutoMock/ConstructorTestsAttribute.cs b/Moq.AutoMock/ConstructorTestsAttribute.cs index bc4fa595..dd0b607f 100644 --- a/Moq.AutoMock/ConstructorTestsAttribute.cs +++ b/Moq.AutoMock/ConstructorTestsAttribute.cs @@ -1,5 +1,4 @@ namespace Moq.AutoMock; - /// /// An attribute used by Moq.AutoMock.TestGenerator to generate unit tests for null constructor arguments. /// @@ -11,6 +10,10 @@ public class ConstructorTestsAttribute : Attribute /// public Type? TargetType { get; set; } /// + /// Controls whether to generate tests for nullable reference types. + /// + public TestGenerationBehavior Behavior { get; set; } + /// /// Create a new instance of the ConstructorTestsAttribute /// public ConstructorTestsAttribute() @@ -18,9 +21,20 @@ public ConstructorTestsAttribute() /// /// Create a new instance of the ConstructorTestsAttribute specifying the targetType /// - /// + /// The name for which the generator should generate tests for. public ConstructorTestsAttribute(Type targetType) { TargetType = targetType; } + + /// + /// Create a new instance of the ConstructorTestsAttribute specifying the targetType + /// + /// The name for which the generator should generate tests for. + /// Sets the behavior for the test generator to operate with. + public ConstructorTestsAttribute(Type targetType, TestGenerationBehavior behavior) + { + TargetType = targetType; + Behavior = behavior; + } } diff --git a/Moq.AutoMock/TestGenerationBehavior.cs b/Moq.AutoMock/TestGenerationBehavior.cs new file mode 100644 index 00000000..06f91e70 --- /dev/null +++ b/Moq.AutoMock/TestGenerationBehavior.cs @@ -0,0 +1,22 @@ +namespace Moq.AutoMock; + +/// +/// Control the behavior of the test generator, such as whether to skip nullable parameters. +/// +public enum TestGenerationBehavior +{ + /// + /// The default behavior, which is to generate tests for all parameters. + /// + Default, + + /// + /// Skip generating tests for parameters that meet one of the following criteria: + /// + /// Nullable reference types are enabled and the parameter type is a nullable reference type. + /// The parameter is a nullable value type. + /// The parameter has a default value of null. + /// + /// + IgnoreNullableParameters +} diff --git a/Moq.AutoMocker.TestGenerator.Tests/TestGeneratorTests.cs b/Moq.AutoMocker.TestGenerator.Tests/TestGeneratorTests.cs new file mode 100644 index 00000000..fe30f37d --- /dev/null +++ b/Moq.AutoMocker.TestGenerator.Tests/TestGeneratorTests.cs @@ -0,0 +1,527 @@ +using System.Text; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using VerifyCS = Moq.AutoMocker.TestGenerator.Tests.CSharpSourceGeneratorVerifier; + +namespace Moq.AutoMocker.TestGenerator.Tests; + +[TestClass] +public class TestGeneratorTests +{ + [TestMethod] + public async Task Generation_WithProjectThatDoesNotReferenceAutoMocker_ProducesDiagnosticWarning() + { + var expectedResult = + DiagnosticResult.CompilerWarning(Diagnostics.MustReferenceAutoMock.DiagnosticId); + await new VerifyCS.Test + { + ReferenceAutoMocker = false, + ExpectedDiagnostics = + { + expectedResult + } + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_WithDecoratedNonPartialClass_ProducesDiagnosticError() + { + var code = @" +using Moq.AutoMock; + +namespace TestNamespace; + +[ConstructorTests(TargetType = typeof(Controller))] +public class ControllerTests +{ + +} + +public class Controller { } +"; + var expectedResult = + DiagnosticResult.CompilerError(Diagnostics.TestClassesMustBePartial.DiagnosticId) + .WithSpan(6, 1, 10, 2) + .WithArguments("TestNamespace.ControllerTests"); + await new VerifyCS.Test + { + TestCode = code, + ExpectedDiagnostics = + { + expectedResult + } + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_WithNoTargetTypeSpecified_ProducesDiagnosticError() + { + var code = @" +using Moq.AutoMock; + +namespace TestNamespace; + +[ConstructorTests] +public class ControllerTests +{ + +} + +public class Controller { } +"; + var expectedResult = + DiagnosticResult.CompilerError(Diagnostics.MustSpecifyTargetType.DiagnosticId) + .WithSpan(6, 2, 6, 18) + .WithArguments("TestNamespace.ControllerTests"); + await new VerifyCS.Test + { + TestCode = code, + ExpectedDiagnostics = + { + expectedResult + } + }.RunAsync(); + } + + [TestMethod] + [Description("Issue 142")] + public async Task Generation_WithGenericParameter_RemovesInvalidCharactersFromTestsName() + { + var code = @" +using Moq.AutoMock; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller))] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller(ILogger logger) { } +} + +public interface ILogger { } +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + partial void ControllerConstructor_WithNullILoggerController_ThrowsArgumentNullExceptionSetup(Moq.AutoMock.AutoMocker mocker); + + public void ControllerConstructor_WithNullILoggerController_ThrowsArgumentNullException() + { + Moq.AutoMock.AutoMocker mocker = new Moq.AutoMock.AutoMocker(); + AutoMockerTestSetup(mocker, ""ControllerConstructor_WithNullILoggerController_ThrowsArgumentNullException""); + ControllerConstructor_WithNullILoggerController_ThrowsArgumentNullExceptionSetup(mocker); + } + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_WithValueTypeParameter_DoesNotGenerateTest() + { + var code = @" +using Moq.AutoMock; +using System.Threading; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller))] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller(CancellationToken token) { } +} +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_ParameterWithDefaultValue_DoesNotGenerateTest() + { + var code = @" +using Moq.AutoMock; +using System.Threading; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller), Behavior = TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller(string name = null) { } +} +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_ParameterWithEmptyString_DoesGenerateTest() + { + var code = @" +using Moq.AutoMock; +using System.Threading; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller), Behavior = TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller(string name = """") { } +} +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + partial void ControllerConstructor_WithNullstring_ThrowsArgumentNullExceptionSetup(Moq.AutoMock.AutoMocker mocker); + + public void ControllerConstructor_WithNullstring_ThrowsArgumentNullException() + { + Moq.AutoMock.AutoMocker mocker = new Moq.AutoMock.AutoMocker(); + AutoMockerTestSetup(mocker, ""ControllerConstructor_WithNullstring_ThrowsArgumentNullException""); + ControllerConstructor_WithNullstring_ThrowsArgumentNullExceptionSetup(mocker); + } + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_ParameterWithNullableString_DoesNotGenerateTest() + { + var code = @" +#nullable enable +using Moq.AutoMock; +using System.Threading; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller), TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller(string? name) { } +} +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_ParameterWithNullableStringAndDefaultValue_DoesNotGenerateTest() + { + var code = @" +#nullable enable +using Moq.AutoMock; +using System.Threading; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller), TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller(string? name = null) { } +} +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_ParameterWithNullableValueType_DoesNotGenerateTest() + { + var code = @" +using Moq.AutoMock; +using System.Threading; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller), TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller(int? age) { } +} +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_ParameterWithValueType_DoesNotGenerateTest() + { + var code = @" +using Moq.AutoMock; +using System.Threading; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller), TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller(int years) + { } +} +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + [TestMethod] + public async Task Generation_ParametersTypesWithValueTypeBetweenReferenceTypes_OnlyGeneratesTestsForReferenceType() + { + var code = @" +#nullable enable +using Moq.AutoMock; +using System.Threading; + +namespace TestNamespace; + +[ConstructorTests(typeof(Controller), TestGenerationBehavior.IgnoreNullableParameters)] +public partial class ControllerTests +{ + +} + +public class Controller +{ + public Controller( + string name, + int years, + string? nullableName) + { } +} +"; + string expected = @"namespace TestNamespace +{ + partial class ControllerTests + { + partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); + + partial void ControllerConstructor_WithNullstring_ThrowsArgumentNullExceptionSetup(Moq.AutoMock.AutoMocker mocker); + + public void ControllerConstructor_WithNullstring_ThrowsArgumentNullException() + { + Moq.AutoMock.AutoMocker mocker = new Moq.AutoMock.AutoMocker(); + AutoMockerTestSetup(mocker, ""ControllerConstructor_WithNullstring_ThrowsArgumentNullException""); + ControllerConstructor_WithNullstring_ThrowsArgumentNullExceptionSetup(mocker); + var years = mocker.Get(); + var nullableName = mocker.Get(); + } + + } +} +"; + + await new VerifyCS.Test + { + TestCode = code, + TestState = + { + GeneratedSources = + { + GetSourceFile(expected, "ControllerTests.g.cs") + } + } + + }.RunAsync(); + } + + private static (string FileName, SourceText SourceText) GetSourceFile(string content, string fileName) + { + return (Path.Combine("Moq.AutoMocker.TestGenerator", "Moq.AutoMocker.TestGenerator.UnitTestSourceGenerator", fileName), SourceText.From(content, Encoding.UTF8)); + } +} diff --git a/Moq.AutoMocker.TestGenerator.Tests/UnitTests.cs b/Moq.AutoMocker.TestGenerator.Tests/UnitTests.cs deleted file mode 100644 index 89442907..00000000 --- a/Moq.AutoMocker.TestGenerator.Tests/UnitTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System.Text; -using Microsoft.CodeAnalysis.Testing; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using VerifyCS = Moq.AutoMocker.TestGenerator.Tests.CSharpSourceGeneratorVerifier; - -namespace Moq.AutoMocker.TestGenerator.Tests; - -[TestClass] -public class TestGeneratorTests -{ - [TestMethod] - public async Task Generation_WithProjectThatDoesNotReferenceAutoMocker_ProducesDiagnosticWarning() - { - var expectedResult = - DiagnosticResult.CompilerWarning(Diagnostics.MustReferenceAutoMock.DiagnosticId); - await new VerifyCS.Test - { - ReferenceAutoMocker = false, - ExpectedDiagnostics = - { - expectedResult - } - }.RunAsync(); - } - - [TestMethod] - public async Task Generation_WithDecoratedNonPartialClass_ProducesDiagnosticError() - { - var code = @" -using Moq.AutoMock; - -namespace TestNamespace; - -[ConstructorTests(TargetType = typeof(Controller))] -public class ControllerTests -{ - -} - -public class Controller { } -"; - var expectedResult = - DiagnosticResult.CompilerError(Diagnostics.TestClassesMustBePartial.DiagnosticId) - .WithSpan(6, 1, 10, 2) - .WithArguments("TestNamespace.ControllerTests"); - await new VerifyCS.Test - { - TestCode = code, - ExpectedDiagnostics = - { - expectedResult - } - }.RunAsync(); - } - - [TestMethod] - public async Task Generation_WithNoTargetTypeSpecified_ProducesDiagnosticError() - { - var code = @" -using Moq.AutoMock; - -namespace TestNamespace; - -[ConstructorTests] -public class ControllerTests -{ - -} - -public class Controller { } -"; - var expectedResult = - DiagnosticResult.CompilerError(Diagnostics.MustSpecifyTargetType.DiagnosticId) - .WithSpan(6, 2, 6, 18) - .WithArguments("TestNamespace.ControllerTests"); - await new VerifyCS.Test - { - TestCode = code, - ExpectedDiagnostics = - { - expectedResult - } - }.RunAsync(); - } - - [TestMethod] - [Description("Issue 142")] - public async Task Generation_WithGenericParameter_RemovesInvalidCharactersFromTestsName() - { - var code = @" -using Moq.AutoMock; - -namespace TestNamespace; - -[ConstructorTests(typeof(Controller))] -public partial class ControllerTests -{ - -} - -public class Controller -{ - public Controller(ILogger logger) { } -} - -public interface ILogger { } -"; - string expected = @"namespace TestNamespace -{ - partial class ControllerTests - { - partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); - - partial void ControllerConstructor_WithNullILoggerController_ThrowsArgumentNullExceptionSetup(Moq.AutoMock.AutoMocker mocker); - - public void ControllerConstructor_WithNullILoggerController_ThrowsArgumentNullException() - { - Moq.AutoMock.AutoMocker mocker = new Moq.AutoMock.AutoMocker(); - AutoMockerTestSetup(mocker, ""ControllerConstructor_WithNullILoggerController_ThrowsArgumentNullException""); - ControllerConstructor_WithNullILoggerController_ThrowsArgumentNullExceptionSetup(mocker); - } - - } -} -"; - - await new VerifyCS.Test - { - TestCode = code, - TestState = - { - GeneratedSources = - { - GetSourceFile(expected, "ControllerTests.g.cs") - } - } - - }.RunAsync(); - } - - [TestMethod] - public async Task Generation_WithValueTypeParameter_DoesNotGenerateTest() - { - var code = @" -using Moq.AutoMock; -using System.Threading; - -namespace TestNamespace; - -[ConstructorTests(typeof(Controller))] -public partial class ControllerTests -{ - -} - -public class Controller -{ - public Controller(CancellationToken token) { } -} -"; - string expected = @"namespace TestNamespace -{ - partial class ControllerTests - { - partial void AutoMockerTestSetup(Moq.AutoMock.AutoMocker mocker, string testName); - - } -} -"; - - await new VerifyCS.Test - { - TestCode = code, - TestState = - { - GeneratedSources = - { - GetSourceFile(expected, "ControllerTests.g.cs") - } - } - - }.RunAsync(); - } - - private static (string FileName, SourceText SourceText) GetSourceFile(string content, string fileName) - { - return (Path.Combine("Moq.AutoMocker.TestGenerator", "Moq.AutoMocker.TestGenerator.UnitTestSourceGenerator", fileName), SourceText.From(content, Encoding.UTF8)); - } -} diff --git a/Moq.AutoMocker.TestGenerator/AutoMock.cs b/Moq.AutoMocker.TestGenerator/AutoMock.cs index c3170c81..c0cdb386 100644 --- a/Moq.AutoMocker.TestGenerator/AutoMock.cs +++ b/Moq.AutoMocker.TestGenerator/AutoMock.cs @@ -6,9 +6,9 @@ public static class AutoMock public const string TargetTypePropertyName = "TargetType"; - public const string AssemblyName = "Moq.AutoMock"; public const string NuGetPackageName = "Moq.AutoMock"; + public const string IgnoreNullableParametersEnumValue = "IgnoreNullableParameters"; } diff --git a/Moq.AutoMocker.TestGenerator/GeneratorTargetClass.cs b/Moq.AutoMocker.TestGenerator/GeneratorTargetClass.cs index e73a09c1..40f2ffa4 100644 --- a/Moq.AutoMocker.TestGenerator/GeneratorTargetClass.cs +++ b/Moq.AutoMocker.TestGenerator/GeneratorTargetClass.cs @@ -8,6 +8,8 @@ public class GeneratorTargetClass public string? TestClassName { get; set; } public SutClass? Sut { get; set; } + + public bool SkipNullableParameters { get; set; } } @@ -31,10 +33,22 @@ public class Parameter { public Parameter(IParameterSymbol symbol) { - Symbol = symbol; + Name = symbol.Name; + ParameterType = symbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + IsValueType = symbol.Type.IsValueType; + + if (symbol.HasExplicitDefaultValue) + { + IsNullable = symbol.ExplicitDefaultValue is null; + } + else if (symbol.NullableAnnotation is NullableAnnotation.Annotated) + { + IsNullable = true; + } } - private IParameterSymbol Symbol { get; } - public string Name => Symbol.Name; - public string ParameterType => Symbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + public bool IsValueType { get; } + public bool IsNullable { get; } + public string Name { get; } + public string ParameterType { get; } } diff --git a/Moq.AutoMocker.TestGenerator/SyntaxReceiver.cs b/Moq.AutoMocker.TestGenerator/SyntaxReceiver.cs index c32d4b8a..9776b1f4 100644 --- a/Moq.AutoMocker.TestGenerator/SyntaxReceiver.cs +++ b/Moq.AutoMocker.TestGenerator/SyntaxReceiver.cs @@ -2,6 +2,8 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Moq.AutoMocker.TestGenerator.AutoMock; + namespace Moq.AutoMocker.TestGenerator; public class SyntaxReceiver : ISyntaxContextReceiver @@ -21,29 +23,34 @@ public class SyntaxReceiver : ISyntaxContextReceiver : null; } + private bool GetSkipNullableParameters(AttributeSyntax attributeSyntax) + { + return attributeSyntax.ArgumentList?.Arguments.Count > 0 && + attributeSyntax.ArgumentList!.Arguments + .Select(x => x.Expression) + .OfType() + .FirstOrDefault() is { Name.Identifier.ValueText: IgnoreNullableParametersEnumValue }; + } + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { if (context.Node is ClassDeclarationSyntax classDeclaration && context.SemanticModel.GetDeclaredSymbol(classDeclaration) is INamedTypeSymbol symbol && classDeclaration.AttributeLists.SelectMany(x => x.Attributes) - .Select(a => - { - if (context.SemanticModel.GetTypeInfo(a).Type?.Name == AutoMock.ConstructorTestsAttribute) - { - if (GetTargetType(a) is { } targetType && - context.SemanticModel.GetTypeInfo(targetType).Type is INamedTypeSymbol sutType) - { - return sutType; - } - Diagnostic diagnostic = Diagnostics.MustSpecifyTargetType.Create(a.GetLocation(), - symbol.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); - DiagnosticMessages.Add(diagnostic); - } - return null; - }) - .FirstOrDefault(a => a is not null) is { } sutType - ) + .Select(a => context.SemanticModel.GetTypeInfo(a).Type?.Name == ConstructorTestsAttribute ? a : null) + .FirstOrDefault(a => a is not null) is { } attribute) { + if (!(GetTargetType(attribute) is { } targetType) || + context.SemanticModel.GetTypeInfo(targetType).Type is not INamedTypeSymbol sutType) + { + Diagnostic diagnostic = Diagnostics.MustSpecifyTargetType.Create(attribute.GetLocation(), + symbol.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + DiagnosticMessages.Add(diagnostic); + return; + } + + bool skipNullableParameters = GetSkipNullableParameters(attribute); + if (!classDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword))) { Diagnostic diagnostic = Diagnostics.TestClassesMustBePartial.Create(classDeclaration.GetLocation(), @@ -66,7 +73,6 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) int nullIndex = 0; foreach (IParameterSymbol parameter in ctor.Parameters) { - if (parameter.Type.IsValueType) continue; sut.NullConstructorParameterTests.Add(new NullConstructorParameterTest() { Parameters = parameters, @@ -81,7 +87,8 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { Namespace = namespaceDeclaration, TestClassName = testClassName, - Sut = sut + Sut = sut, + SkipNullableParameters = skipNullableParameters }; TestClasses.Add(targetClass); diff --git a/Moq.AutoMocker.TestGenerator/UnitTestSourceGenerator.cs b/Moq.AutoMocker.TestGenerator/UnitTestSourceGenerator.cs index 7b2a9abf..4a46cda1 100644 --- a/Moq.AutoMocker.TestGenerator/UnitTestSourceGenerator.cs +++ b/Moq.AutoMocker.TestGenerator/UnitTestSourceGenerator.cs @@ -1,5 +1,4 @@ using System.Text; -using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; namespace Moq.AutoMocker.TestGenerator; @@ -40,10 +39,18 @@ public void Execute(GeneratorExecutionContext context) HashSet testNames = new(); - foreach (var test in testClass.Sut?.NullConstructorParameterTests ?? Enumerable.Empty()) + foreach (NullConstructorParameterTest test in testClass.Sut?.NullConstructorParameterTests ?? Enumerable.Empty()) { + if (test.Parameters?[test.NullParameterIndex].IsValueType == true) + { + continue; + } + if (testClass.SkipNullableParameters && test.Parameters?[test.NullParameterIndex].IsNullable == true) + { + continue; + } string testName = ""; - foreach(var name in TestNameBuilder.CreateTestName(testClass, test)) + foreach (var name in TestNameBuilder.CreateTestName(testClass, test)) { if (testNames.Add(name)) { @@ -115,7 +122,7 @@ static IEnumerable GetParameterNames(NullConstructorParameterTest test) { for (int i = 0; i < test.Parameters?.Count; i++) { - yield return i == test.NullParameterIndex + yield return i == test.NullParameterIndex ? $"default({test.Parameters[i].ParameterType})" : test.Parameters[i].Name; }