From d95d06b426de9e5672139944539d691565afc2fb Mon Sep 17 00:00:00 2001 From: Kevin Bost Date: Fri, 2 Feb 2024 08:36:47 -0800 Subject: [PATCH] Rename the source generators Adding generator for generating extension method when IOptions is referenced Bumping TFM to net8 for the tests --- Directory.Packages.props | 3 +- .../ControllerWithOptionsTests.cs | 19 ++ ...q.AutoMock.Generator.Example.MSTest.csproj | 4 +- .../ControllerWithOptionsTests.cs | 18 ++ ...oq.AutoMock.Generator.Example.NUnit.csproj | 6 +- .../ControllerWithOptionsTests.cs | 18 ++ ...oq.AutoMock.Generator.Example.xUnit.csproj | 10 +- .../ControllerWithOptions.cs | 12 ++ .../Moq.AutoMock.Generator.Example.csproj | 8 +- .../TestsOptions.cs | 6 + Moq.AutoMock.Tests/Moq.AutoMock.Tests.csproj | 2 +- Moq.AutoMock.sln | 25 +-- Moq.AutoMock/ConstructorTestsAttribute.cs | 2 +- Moq.AutoMock/Moq.AutoMock.csproj | 6 +- .../CSharpSourceGeneratorVerifier.cs | 2 +- .../Moq.AutoMocker.Generators.Tests.csproj | 48 ++--- Moq.AutoMocker.Generators.Tests/UnitTests.cs | 191 ++++++++++++++++++ .../AnalyzerReleases.Shipped.md | 0 .../AnalyzerReleases.Unshipped.md | 7 + .../AutoMock.cs | 2 +- .../Diagnostics.cs | 6 +- .../GeneratorTargetClass.cs | 2 +- .../Moq.AutoMocker.Generators.csproj | 34 ++-- .../OptionsExtensionSourceGenerator.cs | 66 ++++++ Moq.AutoMocker.Generators/SyntaxReceiver.cs | 90 +++++++++ .../TargetTestingFramework.cs | 2 +- .../TestNameBuilder.cs | 2 +- .../UnitTestSourceGenerator.cs | 2 +- .../AnalyzerReleases.Unshipped.md | 7 - 29 files changed, 511 insertions(+), 89 deletions(-) create mode 100644 GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/ControllerWithOptionsTests.cs create mode 100644 GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/ControllerWithOptionsTests.cs create mode 100644 GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/ControllerWithOptionsTests.cs create mode 100644 GeneratorTests/Moq.AutoMocker.Generator.Example/ControllerWithOptions.cs create mode 100644 GeneratorTests/Moq.AutoMocker.Generator.Example/TestsOptions.cs rename {Moq.AutoMocker.TestGenerator.Tests => Moq.AutoMocker.Generators.Tests}/CSharpSourceGeneratorVerifier.cs (97%) rename Moq.AutoMocker.TestGenerator.Tests/Moq.AutoMocker.TestGenerator.Tests.csproj => Moq.AutoMocker.Generators.Tests/Moq.AutoMocker.Generators.Tests.csproj (80%) create mode 100644 Moq.AutoMocker.Generators.Tests/UnitTests.cs rename {Moq.AutoMocker.TestGenerator => Moq.AutoMocker.Generators}/AnalyzerReleases.Shipped.md (100%) create mode 100644 Moq.AutoMocker.Generators/AnalyzerReleases.Unshipped.md rename {Moq.AutoMocker.TestGenerator => Moq.AutoMocker.Generators}/AutoMock.cs (89%) rename {Moq.AutoMocker.TestGenerator => Moq.AutoMocker.Generators}/Diagnostics.cs (94%) rename {Moq.AutoMocker.TestGenerator => Moq.AutoMocker.Generators}/GeneratorTargetClass.cs (97%) rename Moq.AutoMocker.TestGenerator/Moq.AutoMocker.TestGenerator.csproj => Moq.AutoMocker.Generators/Moq.AutoMocker.Generators.csproj (96%) create mode 100644 Moq.AutoMocker.Generators/OptionsExtensionSourceGenerator.cs create mode 100644 Moq.AutoMocker.Generators/SyntaxReceiver.cs rename {Moq.AutoMocker.TestGenerator => Moq.AutoMocker.Generators}/TargetTestingFramework.cs (66%) rename {Moq.AutoMocker.TestGenerator => Moq.AutoMocker.Generators}/TestNameBuilder.cs (97%) rename {Moq.AutoMocker.TestGenerator => Moq.AutoMocker.Generators}/UnitTestSourceGenerator.cs (99%) delete mode 100644 Moq.AutoMocker.TestGenerator/AnalyzerReleases.Unshipped.md diff --git a/Directory.Packages.props b/Directory.Packages.props index 54809e1b..c47dfa65 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,10 +3,11 @@ true - + + diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/ControllerWithOptionsTests.cs b/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/ControllerWithOptionsTests.cs new file mode 100644 index 00000000..a60895fe --- /dev/null +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/ControllerWithOptionsTests.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Moq.AutoMock.Generator.Example.MSUnit; +[TestClass] +public class ControllerWithOptionsTests +{ + [TestMethod] + public void CreateInstance_WithOptions_EmbedsOptions() + { + AutoMocker mocker = new(); + + mocker.WithOptions(options => options.Number = 42); + + ControllerWithOptions controller = mocker.CreateInstance(); + + Assert.AreEqual(42, controller.Options.Value.Number); + } +} diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/Moq.AutoMock.Generator.Example.MSTest.csproj b/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/Moq.AutoMock.Generator.Example.MSTest.csproj index 35923b5e..b09bafe0 100644 --- a/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/Moq.AutoMock.Generator.Example.MSTest.csproj +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.MSTest/Moq.AutoMock.Generator.Example.MSTest.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable false @@ -19,7 +19,7 @@ - + Analyzer false diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/ControllerWithOptionsTests.cs b/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/ControllerWithOptionsTests.cs new file mode 100644 index 00000000..c810af87 --- /dev/null +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/ControllerWithOptionsTests.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.Options; +using NUnit.Framework; + +namespace Moq.AutoMock.Generator.Example.NUnit; +public class ControllerWithOptionsTests +{ + [Test] + public void CreateInstance_WithOptions_EmbedsOptions() + { + AutoMocker mocker = new(); + + mocker.WithOptions(options => options.Number = 42); + + ControllerWithOptions controller = mocker.CreateInstance(); + + Assert.That(42 == controller.Options.Value.Number); + } +} diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/Moq.AutoMock.Generator.Example.NUnit.csproj b/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/Moq.AutoMock.Generator.Example.NUnit.csproj index d263f547..93d35cda 100644 --- a/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/Moq.AutoMock.Generator.Example.NUnit.csproj +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.NUnit/Moq.AutoMock.Generator.Example.NUnit.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable false @@ -12,14 +12,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + Analyzer false diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/ControllerWithOptionsTests.cs b/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/ControllerWithOptionsTests.cs new file mode 100644 index 00000000..d80c0318 --- /dev/null +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/ControllerWithOptionsTests.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.Options; +using Xunit; + +namespace Moq.AutoMock.Generator.Example.xUnit; +public class ControllerWithOptionsTests +{ + [Fact] + public void CreateInstance_WithOptions_EmbedsOptions() + { + AutoMocker mocker = new(); + + mocker.WithOptions(options => options.Number = 42); + + ControllerWithOptions controller = mocker.CreateInstance(); + + Assert.Equal(42, controller.Options.Value.Number); + } +} diff --git a/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/Moq.AutoMock.Generator.Example.xUnit.csproj b/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/Moq.AutoMock.Generator.Example.xUnit.csproj index 4c2b4fbc..33465ce4 100644 --- a/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/Moq.AutoMock.Generator.Example.xUnit.csproj +++ b/GeneratorTests/Moq.AutoMock.Generator.Example.xUnit/Moq.AutoMock.Generator.Example.xUnit.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable false @@ -20,13 +20,9 @@ all - - - + - + Analyzer false diff --git a/GeneratorTests/Moq.AutoMocker.Generator.Example/ControllerWithOptions.cs b/GeneratorTests/Moq.AutoMocker.Generator.Example/ControllerWithOptions.cs new file mode 100644 index 00000000..318e788b --- /dev/null +++ b/GeneratorTests/Moq.AutoMocker.Generator.Example/ControllerWithOptions.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Options; + +namespace Moq.AutoMock.Generator.Example; +public class ControllerWithOptions +{ + public IOptions Options { get; } + + public ControllerWithOptions(IOptions options) + { + Options = options; + } +} diff --git a/GeneratorTests/Moq.AutoMocker.Generator.Example/Moq.AutoMock.Generator.Example.csproj b/GeneratorTests/Moq.AutoMocker.Generator.Example/Moq.AutoMock.Generator.Example.csproj index 268cbace..294e2834 100644 --- a/GeneratorTests/Moq.AutoMocker.Generator.Example/Moq.AutoMock.Generator.Example.csproj +++ b/GeneratorTests/Moq.AutoMocker.Generator.Example/Moq.AutoMock.Generator.Example.csproj @@ -1,10 +1,14 @@ - + - net6.0 + net8.0 enable enable false + + + + diff --git a/GeneratorTests/Moq.AutoMocker.Generator.Example/TestsOptions.cs b/GeneratorTests/Moq.AutoMocker.Generator.Example/TestsOptions.cs new file mode 100644 index 00000000..f6d62da7 --- /dev/null +++ b/GeneratorTests/Moq.AutoMocker.Generator.Example/TestsOptions.cs @@ -0,0 +1,6 @@ +namespace Moq.AutoMock.Generator.Example; + +public class TestsOptions +{ + public int Number { get; set; } +} diff --git a/Moq.AutoMock.Tests/Moq.AutoMock.Tests.csproj b/Moq.AutoMock.Tests/Moq.AutoMock.Tests.csproj index 24097082..1d682a8a 100644 --- a/Moq.AutoMock.Tests/Moq.AutoMock.Tests.csproj +++ b/Moq.AutoMock.Tests/Moq.AutoMock.Tests.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Copyright © 2020 false diff --git a/Moq.AutoMock.sln b/Moq.AutoMock.sln index a452e396..14071973 100644 --- a/Moq.AutoMock.sln +++ b/Moq.AutoMock.sln @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Packages.props = Directory.Packages.props global.json = global.json LICENSE.md = LICENSE.md + NuGet.config = NuGet.config README.md = README.md EndProjectSection EndProject @@ -30,16 +31,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.AutoMock.Generator.Exam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.AutoMock.Generator.Example.xUnit", "GeneratorTests\Moq.AutoMock.Generator.Example.xUnit\Moq.AutoMock.Generator.Example.xUnit.csproj", "{586D1D6A-3EE2-4AB8-988A-CEB810EF7787}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.AutoMocker.TestGenerator", "Moq.AutoMocker.TestGenerator\Moq.AutoMocker.TestGenerator.csproj", "{710AF8FE-BB9E-4FE9-ABF7-0CE8BD212BB4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.AutoMocker.TestGenerator.Tests", "Moq.AutoMocker.TestGenerator.Tests\Moq.AutoMocker.TestGenerator.Tests.csproj", "{F3663663-0123-4DA3-9BDD-95C91AFA4596}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{820CF7FE-8E92-4500-BE62-55AF67007838}" ProjectSection(SolutionItems) = preProject .github\dependabot.yml = .github\dependabot.yml .github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.AutoMocker.Generators", "Moq.AutoMocker.Generators\Moq.AutoMocker.Generators.csproj", "{FE1C406B-D16B-4037-9D67-ECE2B6847AEB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.AutoMocker.Generators.Tests", "Moq.AutoMocker.Generators.Tests\Moq.AutoMocker.Generators.Tests.csproj", "{7820E7C8-7169-4A28-9D1B-BB38FC4E2923}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -74,14 +75,14 @@ Global {586D1D6A-3EE2-4AB8-988A-CEB810EF7787}.Debug|Any CPU.Build.0 = Debug|Any CPU {586D1D6A-3EE2-4AB8-988A-CEB810EF7787}.Release|Any CPU.ActiveCfg = Release|Any CPU {586D1D6A-3EE2-4AB8-988A-CEB810EF7787}.Release|Any CPU.Build.0 = Release|Any CPU - {710AF8FE-BB9E-4FE9-ABF7-0CE8BD212BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {710AF8FE-BB9E-4FE9-ABF7-0CE8BD212BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {710AF8FE-BB9E-4FE9-ABF7-0CE8BD212BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {710AF8FE-BB9E-4FE9-ABF7-0CE8BD212BB4}.Release|Any CPU.Build.0 = Release|Any CPU - {F3663663-0123-4DA3-9BDD-95C91AFA4596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3663663-0123-4DA3-9BDD-95C91AFA4596}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3663663-0123-4DA3-9BDD-95C91AFA4596}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3663663-0123-4DA3-9BDD-95C91AFA4596}.Release|Any CPU.Build.0 = Release|Any CPU + {FE1C406B-D16B-4037-9D67-ECE2B6847AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE1C406B-D16B-4037-9D67-ECE2B6847AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE1C406B-D16B-4037-9D67-ECE2B6847AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE1C406B-D16B-4037-9D67-ECE2B6847AEB}.Release|Any CPU.Build.0 = Release|Any CPU + {7820E7C8-7169-4A28-9D1B-BB38FC4E2923}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7820E7C8-7169-4A28-9D1B-BB38FC4E2923}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7820E7C8-7169-4A28-9D1B-BB38FC4E2923}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7820E7C8-7169-4A28-9D1B-BB38FC4E2923}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Moq.AutoMock/ConstructorTestsAttribute.cs b/Moq.AutoMock/ConstructorTestsAttribute.cs index dd0b607f..b7c0e937 100644 --- a/Moq.AutoMock/ConstructorTestsAttribute.cs +++ b/Moq.AutoMock/ConstructorTestsAttribute.cs @@ -1,6 +1,6 @@ namespace Moq.AutoMock; /// -/// An attribute used by Moq.AutoMock.TestGenerator to generate unit tests for null constructor arguments. +/// An attribute used by Moq.AutoMock.Generators to generate unit tests for null constructor arguments. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class ConstructorTestsAttribute : Attribute diff --git a/Moq.AutoMock/Moq.AutoMock.csproj b/Moq.AutoMock/Moq.AutoMock.csproj index dac3ffc8..49342594 100644 --- a/Moq.AutoMock/Moq.AutoMock.csproj +++ b/Moq.AutoMock/Moq.AutoMock.csproj @@ -39,18 +39,18 @@ Analyzer false - + - + - + diff --git a/Moq.AutoMocker.TestGenerator.Tests/CSharpSourceGeneratorVerifier.cs b/Moq.AutoMocker.Generators.Tests/CSharpSourceGeneratorVerifier.cs similarity index 97% rename from Moq.AutoMocker.TestGenerator.Tests/CSharpSourceGeneratorVerifier.cs rename to Moq.AutoMocker.Generators.Tests/CSharpSourceGeneratorVerifier.cs index adbba827..ca5c4797 100644 --- a/Moq.AutoMocker.TestGenerator.Tests/CSharpSourceGeneratorVerifier.cs +++ b/Moq.AutoMocker.Generators.Tests/CSharpSourceGeneratorVerifier.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; -namespace Moq.AutoMocker.TestGenerator.Tests; +namespace Moq.AutoMocker.Generators.Tests; public static class CSharpSourceGeneratorVerifier where TSourceGenerator : ISourceGenerator, new() diff --git a/Moq.AutoMocker.TestGenerator.Tests/Moq.AutoMocker.TestGenerator.Tests.csproj b/Moq.AutoMocker.Generators.Tests/Moq.AutoMocker.Generators.Tests.csproj similarity index 80% rename from Moq.AutoMocker.TestGenerator.Tests/Moq.AutoMocker.TestGenerator.Tests.csproj rename to Moq.AutoMocker.Generators.Tests/Moq.AutoMocker.Generators.Tests.csproj index fb8ad5a4..45bcd806 100644 --- a/Moq.AutoMocker.TestGenerator.Tests/Moq.AutoMocker.TestGenerator.Tests.csproj +++ b/Moq.AutoMocker.Generators.Tests/Moq.AutoMocker.Generators.Tests.csproj @@ -1,24 +1,24 @@ - - - - net6.0 - - false - true - true - - - - - - - - - - - - - - - - + + + + net8.0 + + false + true + true + + + + + + + + + + + + + + + + diff --git a/Moq.AutoMocker.Generators.Tests/UnitTests.cs b/Moq.AutoMocker.Generators.Tests/UnitTests.cs new file mode 100644 index 00000000..cb03d824 --- /dev/null +++ b/Moq.AutoMocker.Generators.Tests/UnitTests.cs @@ -0,0 +1,191 @@ +using System.Text; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using VerifyCS = Moq.AutoMocker.Generators.Tests.CSharpSourceGeneratorVerifier; + +namespace Moq.AutoMocker.Generators.Tests; + +[TestClass] +public class GeneratorsTests +{ + [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.Generators", "Moq.AutoMocker.Generators.UnitTestSourceGenerator", fileName), SourceText.From(content, Encoding.UTF8)); + } +} diff --git a/Moq.AutoMocker.TestGenerator/AnalyzerReleases.Shipped.md b/Moq.AutoMocker.Generators/AnalyzerReleases.Shipped.md similarity index 100% rename from Moq.AutoMocker.TestGenerator/AnalyzerReleases.Shipped.md rename to Moq.AutoMocker.Generators/AnalyzerReleases.Shipped.md diff --git a/Moq.AutoMocker.Generators/AnalyzerReleases.Unshipped.md b/Moq.AutoMocker.Generators/AnalyzerReleases.Unshipped.md new file mode 100644 index 00000000..076ae282 --- /dev/null +++ b/Moq.AutoMocker.Generators/AnalyzerReleases.Unshipped.md @@ -0,0 +1,7 @@ +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +AMG0001 | AutoMocker.Generators | Error | TestClassesMustBePartial +AMG0002 | AutoMocker.Generators | Warning | MustReferenceAutoMock +AMG0003 | AutoMocker.Generators | Error | MustSpecifyTargetType \ No newline at end of file diff --git a/Moq.AutoMocker.TestGenerator/AutoMock.cs b/Moq.AutoMocker.Generators/AutoMock.cs similarity index 89% rename from Moq.AutoMocker.TestGenerator/AutoMock.cs rename to Moq.AutoMocker.Generators/AutoMock.cs index c0cdb386..f1ad53d4 100644 --- a/Moq.AutoMocker.TestGenerator/AutoMock.cs +++ b/Moq.AutoMocker.Generators/AutoMock.cs @@ -1,4 +1,4 @@ -namespace Moq.AutoMocker.TestGenerator; +namespace Moq.AutoMocker.Generators; public static class AutoMock { diff --git a/Moq.AutoMocker.TestGenerator/Diagnostics.cs b/Moq.AutoMocker.Generators/Diagnostics.cs similarity index 94% rename from Moq.AutoMocker.TestGenerator/Diagnostics.cs rename to Moq.AutoMocker.Generators/Diagnostics.cs index 675bf1db..7943157a 100644 --- a/Moq.AutoMocker.TestGenerator/Diagnostics.cs +++ b/Moq.AutoMocker.Generators/Diagnostics.cs @@ -1,10 +1,10 @@ using Microsoft.CodeAnalysis; -namespace Moq.AutoMocker.TestGenerator; +namespace Moq.AutoMocker.Generators; public static class Diagnostics { - private const string Category = "AutoMocker.TestGenerator"; + private const string Category = "AutoMocker.Generators"; public static class TestClassesMustBePartial { public const string DiagnosticId = "AMG0001"; @@ -25,7 +25,7 @@ public static class MustReferenceAutoMock { public const string DiagnosticId = "AMG0002"; private const string Title = $"Test projects must reference {AutoMock.AssemblyName}"; - private const string MessageFormat = $"To use Moq.AutoMocker.TestGenerator, your test project must also reference {AutoMock.AssemblyName}"; + private const string MessageFormat = $"To use Moq.AutoMocker.Generators, your test project must also reference {AutoMock.AssemblyName}"; private const string Description = $"Add a reference to the {AutoMock.AssemblyName} assembly or reference the {AutoMock.NuGetPackageName} NuGet package by running \"Install-Package Moq.AutoMock\"."; //NB: Do not make a property or use target-typed new expression diff --git a/Moq.AutoMocker.TestGenerator/GeneratorTargetClass.cs b/Moq.AutoMocker.Generators/GeneratorTargetClass.cs similarity index 97% rename from Moq.AutoMocker.TestGenerator/GeneratorTargetClass.cs rename to Moq.AutoMocker.Generators/GeneratorTargetClass.cs index 40f2ffa4..007df786 100644 --- a/Moq.AutoMocker.TestGenerator/GeneratorTargetClass.cs +++ b/Moq.AutoMocker.Generators/GeneratorTargetClass.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis; -namespace Moq.AutoMocker.TestGenerator; +namespace Moq.AutoMocker.Generators; public class GeneratorTargetClass { diff --git a/Moq.AutoMocker.TestGenerator/Moq.AutoMocker.TestGenerator.csproj b/Moq.AutoMocker.Generators/Moq.AutoMocker.Generators.csproj similarity index 96% rename from Moq.AutoMocker.TestGenerator/Moq.AutoMocker.TestGenerator.csproj rename to Moq.AutoMocker.Generators/Moq.AutoMocker.Generators.csproj index fe81003d..eb943b0e 100644 --- a/Moq.AutoMocker.TestGenerator/Moq.AutoMocker.TestGenerator.csproj +++ b/Moq.AutoMocker.Generators/Moq.AutoMocker.Generators.csproj @@ -1,17 +1,17 @@ - - - - netstandard2.0 - false - true - - - - - - - - - - - + + + + netstandard2.0 + false + true + + + + + + + + + + + diff --git a/Moq.AutoMocker.Generators/OptionsExtensionSourceGenerator.cs b/Moq.AutoMocker.Generators/OptionsExtensionSourceGenerator.cs new file mode 100644 index 00000000..06e25ac8 --- /dev/null +++ b/Moq.AutoMocker.Generators/OptionsExtensionSourceGenerator.cs @@ -0,0 +1,66 @@ +using Microsoft.CodeAnalysis; + +namespace Moq.AutoMocker.Generators; + +[Generator] +public class OptionsExtensionSourceGenerator : ISourceGenerator +{ + public void Execute(GeneratorExecutionContext context) + { + if (!ReferencesOptions(context.Compilation.ReferencedAssemblyNames)) + { + return; + } + + context.AddSource("AutoMocker.Options.cs", OptionsExtensionContent); + } + + public void Initialize(GeneratorInitializationContext context) + { } + + private static bool ReferencesOptions(IEnumerable assemblies) + { + foreach (AssemblyIdentity assembly in assemblies) + { + if (assembly.Name.StartsWith("Microsoft.Extensions.Options")) + { + return true; + } + } + return false; + } + + private const string OptionsExtensionContent = + """ + namespace Moq.AutoMock + { + using Microsoft.Extensions.Options; + + public static class AutoMockerOptionsExtensions + { + /// + /// This method sets up with various option related services for Microsoft's Option pattern, and allows their interception and manipulation in testing scenarios. + /// + /// The instance + /// A delegate that can be used to configure an option instance of type TClass. + /// The type of Options being configured. + /// The same instance passed as parameter, allowing chained calls. + public static AutoMocker WithOptions(this AutoMocker mocker, Action? configure = null) + where TClass : class, new() + { + if (mocker == null) throw new ArgumentNullException(nameof(mocker)); + + mocker.Use>>(new[] { new ConfigureOptions(configure) }); + mocker.With, OptionsCache>(); + mocker.With, OptionsFactory>(); + mocker.With, OptionsMonitor>(); + mocker.With, OptionsManager>(); + TClass options = mocker.Get>().Create(string.Empty); + mocker.Use(Options.Create(options)); + mocker.Use(options); + return mocker; + } + } + } + """; +} diff --git a/Moq.AutoMocker.Generators/SyntaxReceiver.cs b/Moq.AutoMocker.Generators/SyntaxReceiver.cs new file mode 100644 index 00000000..abae63f0 --- /dev/null +++ b/Moq.AutoMocker.Generators/SyntaxReceiver.cs @@ -0,0 +1,90 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Moq.AutoMocker.Generators; + +public class SyntaxReceiver : ISyntaxContextReceiver +{ + public List TestClasses { get; } = new(); + + public List DiagnosticMessages { get; } = new(); + + private TypeSyntax? GetTargetType(AttributeSyntax attributeSyntax) + { + return attributeSyntax.ArgumentList?.Arguments.Count > 0 && + attributeSyntax.ArgumentList.Arguments + .Select(x => x.Expression) + .OfType() + .FirstOrDefault() is { } typeExpression + ? typeExpression.Type + : null; + } + + 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 + ) + { + if (!classDeclaration.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword))) + { + Diagnostic diagnostic = Diagnostics.TestClassesMustBePartial.Create(classDeclaration.GetLocation(), + symbol.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); + DiagnosticMessages.Add(diagnostic); + return; + } + string testClassName = symbol.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + string namespaceDeclaration = symbol.ContainingNamespace.ToDisplayString(); + + SutClass sut = new() + { + Name = sutType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + FullName = sutType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + }; + + foreach (IMethodSymbol ctor in sutType.Constructors) + { + var parameters = ctor.Parameters.Select(x => new Parameter(x)).ToList(); + int nullIndex = 0; + foreach (IParameterSymbol parameter in ctor.Parameters) + { + if (parameter.Type.IsValueType) continue; + sut.NullConstructorParameterTests.Add(new NullConstructorParameterTest() + { + Parameters = parameters, + NullParameterIndex = nullIndex, + NullTypeName = parameter.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) + }); + nullIndex++; + } + } + + GeneratorTargetClass targetClass = new() + { + Namespace = namespaceDeclaration, + TestClassName = testClassName, + Sut = sut + }; + + TestClasses.Add(targetClass); + } + } +} diff --git a/Moq.AutoMocker.TestGenerator/TargetTestingFramework.cs b/Moq.AutoMocker.Generators/TargetTestingFramework.cs similarity index 66% rename from Moq.AutoMocker.TestGenerator/TargetTestingFramework.cs rename to Moq.AutoMocker.Generators/TargetTestingFramework.cs index bce2ac8b..c8e0a7e9 100644 --- a/Moq.AutoMocker.TestGenerator/TargetTestingFramework.cs +++ b/Moq.AutoMocker.Generators/TargetTestingFramework.cs @@ -1,4 +1,4 @@ -namespace Moq.AutoMocker.TestGenerator; +namespace Moq.AutoMocker.Generators; public enum TargetTestingFramework { diff --git a/Moq.AutoMocker.TestGenerator/TestNameBuilder.cs b/Moq.AutoMocker.Generators/TestNameBuilder.cs similarity index 97% rename from Moq.AutoMocker.TestGenerator/TestNameBuilder.cs rename to Moq.AutoMocker.Generators/TestNameBuilder.cs index 06ffaf21..5bebc44c 100644 --- a/Moq.AutoMocker.TestGenerator/TestNameBuilder.cs +++ b/Moq.AutoMocker.Generators/TestNameBuilder.cs @@ -1,7 +1,7 @@ using System.Text; using Microsoft.CodeAnalysis.CSharp; -namespace Moq.AutoMocker.TestGenerator; +namespace Moq.AutoMocker.Generators; internal static class TestNameBuilder { diff --git a/Moq.AutoMocker.TestGenerator/UnitTestSourceGenerator.cs b/Moq.AutoMocker.Generators/UnitTestSourceGenerator.cs similarity index 99% rename from Moq.AutoMocker.TestGenerator/UnitTestSourceGenerator.cs rename to Moq.AutoMocker.Generators/UnitTestSourceGenerator.cs index 4a46cda1..344c236c 100644 --- a/Moq.AutoMocker.TestGenerator/UnitTestSourceGenerator.cs +++ b/Moq.AutoMocker.Generators/UnitTestSourceGenerator.cs @@ -1,7 +1,7 @@ using System.Text; using Microsoft.CodeAnalysis; -namespace Moq.AutoMocker.TestGenerator; +namespace Moq.AutoMocker.Generators; [Generator] public class UnitTestSourceGenerator : ISourceGenerator diff --git a/Moq.AutoMocker.TestGenerator/AnalyzerReleases.Unshipped.md b/Moq.AutoMocker.TestGenerator/AnalyzerReleases.Unshipped.md deleted file mode 100644 index f7f5d6ce..00000000 --- a/Moq.AutoMocker.TestGenerator/AnalyzerReleases.Unshipped.md +++ /dev/null @@ -1,7 +0,0 @@ -### New Rules - -Rule ID | Category | Severity | Notes ---------|----------|----------|------- -AMG0001 | AutoMocker.TestGenerator | Error | TestClassesMustBePartial -AMG0002 | AutoMocker.TestGenerator | Warning | MustReferenceAutoMock -AMG0003 | AutoMocker.TestGenerator | Error | MustSpecifyTargetType \ No newline at end of file