From acf24e4e7387bb2eb6372c74b3820399d4548851 Mon Sep 17 00:00:00 2001 From: Laszlo Deak Date: Mon, 1 Mar 2021 22:49:59 +0100 Subject: [PATCH 1/3] Dev multi library code generation Renaming S namespace to Safe --- JsonMergePatch.sln | 7 ++ .../Controllers/SampleController.cs | 16 ++++ sample/ConsoleApp/ConsoleApp.csproj | 6 +- sample/ConsoleApp/Program.cs | 7 +- .../ConsoleAppLibrary.csproj | 12 +++ sample/ConsoleAppLibrary/Sample.cs | 26 ++++++ .../ITypeRepository.cs | 12 +++ .../ITypeRepositoryExntesions.cs | 13 +++ .../JsonMergePatchSourceGenerator.cs | 2 +- .../NameBuilder.cs | 19 ++++ .../TypeBuilder.cs | 22 ++--- .../TypeRepositoryGenerator.cs | 17 +++- .../BasicSerializationTests.cs | 2 +- .../DictionarySerializationTests.cs | 2 +- .../GeneratedWrapperTypeTests.cs | 24 ++--- .../SourceBuilder.cs | 2 +- .../TypeBuilderTests.cs | 34 +++---- .../TypeRepositoryGeneratorTests.cs | 88 +++++++++++++++---- .../TypesWithCtorSerializationTests.cs | 2 +- .../ModelBuilderExtensionGeneratorTests.cs | 6 ++ 20 files changed, 244 insertions(+), 75 deletions(-) create mode 100644 sample/ConsoleAppLibrary/ConsoleAppLibrary.csproj create mode 100644 sample/ConsoleAppLibrary/Sample.cs create mode 100644 src/JsonMergePatch.Abstractions/ITypeRepositoryExntesions.cs create mode 100644 src/JsonMergePatch.SourceGenerator/NameBuilder.cs diff --git a/JsonMergePatch.sln b/JsonMergePatch.sln index a2a0656..adb9c96 100644 --- a/JsonMergePatch.sln +++ b/JsonMergePatch.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonMergePatch.SourceGenera EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonMergePatch.SourceGenerator", "src\JsonMergePatch.SourceGenerator\JsonMergePatch.SourceGenerator.csproj", "{CEB6F7B7-15A4-485E-8372-7687852EB929}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppLibrary", "sample\ConsoleAppLibrary\ConsoleAppLibrary.csproj", "{3592FF83-D9D6-473A-97CE-94FF0D7FE5F1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -77,6 +79,10 @@ Global {CEB6F7B7-15A4-485E-8372-7687852EB929}.Debug|Any CPU.Build.0 = Debug|Any CPU {CEB6F7B7-15A4-485E-8372-7687852EB929}.Release|Any CPU.ActiveCfg = Release|Any CPU {CEB6F7B7-15A4-485E-8372-7687852EB929}.Release|Any CPU.Build.0 = Release|Any CPU + {3592FF83-D9D6-473A-97CE-94FF0D7FE5F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3592FF83-D9D6-473A-97CE-94FF0D7FE5F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3592FF83-D9D6-473A-97CE-94FF0D7FE5F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3592FF83-D9D6-473A-97CE-94FF0D7FE5F1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -84,6 +90,7 @@ Global GlobalSection(NestedProjects) = preSolution {910E8728-872B-43EF-994F-B67953874BCB} = {0E750D74-69C7-433C-92C7-3F026083C255} {1FE80A1A-9402-4953-9A7F-F01B1D0539B5} = {0E750D74-69C7-433C-92C7-3F026083C255} + {3592FF83-D9D6-473A-97CE-94FF0D7FE5F1} = {0E750D74-69C7-433C-92C7-3F026083C255} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55007954-1D8A-4553-A15C-FE4AE17C0A4B} diff --git a/sample/AspNetCoreWebApi/Controllers/SampleController.cs b/sample/AspNetCoreWebApi/Controllers/SampleController.cs index ce7650c..742d92b 100644 --- a/sample/AspNetCoreWebApi/Controllers/SampleController.cs +++ b/sample/AspNetCoreWebApi/Controllers/SampleController.cs @@ -27,6 +27,12 @@ public class WeatherForecast public string Summary { get; set; } } + public class DeviceData + { + public double Watts { get; set; } + public string Name { get; set; } + } + [ApiController] [Route("[controller]")] public class SampleController : ControllerBase @@ -54,6 +60,16 @@ public WeatherForecast GetWeather() }; } + [HttpGet("DeviceData")] + public DeviceData GetDeviceData() + { + return new DeviceData + { + Name = "test device1", + Watts = 12 + }; + } + [HttpPatch("PatchWeather")] public WeatherForecast PatchForecast(Patch input) { diff --git a/sample/ConsoleApp/ConsoleApp.csproj b/sample/ConsoleApp/ConsoleApp.csproj index c4c50a5..868b110 100644 --- a/sample/ConsoleApp/ConsoleApp.csproj +++ b/sample/ConsoleApp/ConsoleApp.csproj @@ -6,14 +6,10 @@ false - - - - + diff --git a/sample/ConsoleApp/Program.cs b/sample/ConsoleApp/Program.cs index 67b5217..9a075cb 100644 --- a/sample/ConsoleApp/Program.cs +++ b/sample/ConsoleApp/Program.cs @@ -1,6 +1,8 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using ConsoleAppLibrary; +using LaDeak.JsonMergePatch.Abstractions; using LaDeak.JsonMergePatch.Http; namespace ReadJsonPatchAsync @@ -16,7 +18,7 @@ public class WeatherForecast public static async Task Main(string[] args) { - LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository = LaDeak.JsonMergePatch.Generated.TypeRepository.Instance; + LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository = LaDeak.JsonMergePatch.Generated.SafeConsoleApp.TypeRepository.Instance.Extend(LaDeak.JsonMergePatch.Generated.SafeConsoleAppLibrary.TypeRepository.Instance); await ReadAsJsonMergePatchAsync(); } @@ -28,6 +30,9 @@ public static async Task ReadAsJsonMergePatchAsync() var original = new WeatherForecast() { Date = DateTime.UtcNow, Summary = "Sample weather forecast", Temp = 24 }; var result = responseData.ApplyPatch(original); Console.WriteLine($"Patched: Date={result.Date}, Summary={result.Summary}, Temp={result.Temp}"); + + var client = new Client(); + await client.ReadAsJsonMergePatchAsync(); } } } diff --git a/sample/ConsoleAppLibrary/ConsoleAppLibrary.csproj b/sample/ConsoleAppLibrary/ConsoleAppLibrary.csproj new file mode 100644 index 0000000..bb0fc26 --- /dev/null +++ b/sample/ConsoleAppLibrary/ConsoleAppLibrary.csproj @@ -0,0 +1,12 @@ + + + + net5.0 + + + + + + + + diff --git a/sample/ConsoleAppLibrary/Sample.cs b/sample/ConsoleAppLibrary/Sample.cs new file mode 100644 index 0000000..bbdccc6 --- /dev/null +++ b/sample/ConsoleAppLibrary/Sample.cs @@ -0,0 +1,26 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using LaDeak.JsonMergePatch.Http; + +namespace ConsoleAppLibrary +{ + public class DeviceData + { + public double Watts { get; set; } + public string Name { get; set; } + } + + public class Client + { + public async Task ReadAsJsonMergePatchAsync() + { + var httpClient = new HttpClient(); + var response = await httpClient.GetAsync("https://localhost:5001/Sample/DeviceData", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + var responseData = await response.Content.ReadJsonPatchAsync().ConfigureAwait(false); + var original = new DeviceData() { Watts = 5, Name = "phone" }; + var result = responseData.ApplyPatch(original); + Console.WriteLine($"Patched: Name={result.Name}, Watts={result.Watts}"); + } + } +} diff --git a/src/JsonMergePatch.Abstractions/ITypeRepository.cs b/src/JsonMergePatch.Abstractions/ITypeRepository.cs index 4d922f9..13d05cd 100644 --- a/src/JsonMergePatch.Abstractions/ITypeRepository.cs +++ b/src/JsonMergePatch.Abstractions/ITypeRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace LaDeak.JsonMergePatch.Abstractions @@ -19,5 +20,16 @@ public interface ITypeRepository /// Type that wraps the user type. /// True if the user type had a registration, otherwise false. bool TryGet(Type source, [NotNullWhen(true)] out Type wrapper); + + /// + /// Adds a type and corresponding wrapper to the type repository. + /// + void Add(Type source, Type wrapper); + + /// + /// Returns all registrations. + /// + /// Az enumeration of registered types and corresponding wrapper types. + public IEnumerable> GetAll(); } } \ No newline at end of file diff --git a/src/JsonMergePatch.Abstractions/ITypeRepositoryExntesions.cs b/src/JsonMergePatch.Abstractions/ITypeRepositoryExntesions.cs new file mode 100644 index 0000000..8061fc7 --- /dev/null +++ b/src/JsonMergePatch.Abstractions/ITypeRepositoryExntesions.cs @@ -0,0 +1,13 @@ +namespace LaDeak.JsonMergePatch.Abstractions +{ + public static class ITypeRepositoryExntesions + { + public static ITypeRepository Extend(this ITypeRepository repository, ITypeRepository other) + { + foreach (var item in other.GetAll()) + if (!repository.TryGet(item.Key, out _)) + repository.Add(item.Key, item.Value); + return repository; + } + } +} \ No newline at end of file diff --git a/src/JsonMergePatch.SourceGenerator/JsonMergePatchSourceGenerator.cs b/src/JsonMergePatch.SourceGenerator/JsonMergePatchSourceGenerator.cs index e54163f..61f3372 100644 --- a/src/JsonMergePatch.SourceGenerator/JsonMergePatchSourceGenerator.cs +++ b/src/JsonMergePatch.SourceGenerator/JsonMergePatchSourceGenerator.cs @@ -19,7 +19,7 @@ protected virtual IEnumerable ExecuteImpl(GeneratorExecutionCo context.AddSource(generatedType.FileName, SourceText.From(generatedType.SourceCode, Encoding.UTF8)); var tyeRepoGenerator = new TypeRepositoryGenerator(); - var typeRepoClass = tyeRepoGenerator.CreateTypeRepository(types.Select(x => (x.SourceTypeFullName, x.TargetTypeFullName))); + var typeRepoClass = tyeRepoGenerator.CreateTypeRepository(types.Select(x => (x.SourceTypeFullName, x.TargetTypeFullName)), context.Compilation.Assembly.Name); context.AddSource("LaDeakJsonMergePatchTypeRepo", SourceText.From(typeRepoClass, Encoding.UTF8)); return types; } diff --git a/src/JsonMergePatch.SourceGenerator/NameBuilder.cs b/src/JsonMergePatch.SourceGenerator/NameBuilder.cs new file mode 100644 index 0000000..17c66ae --- /dev/null +++ b/src/JsonMergePatch.SourceGenerator/NameBuilder.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace LaDeak.JsonMergePatch.SourceGenerator +{ + public static class NameBuilder + { + internal const string Namespace = "LaDeak.JsonMergePatch.Generated"; + + public static string GetName(ITypeSymbol typeInfo) => $"{typeInfo.Name}Wrapped"; + + public static string GetNamespaceExtension(ITypeSymbol typeInfo) => $"Safe{typeInfo.ContainingNamespace.ToDisplayString()}"; + + public static string GetFullTypeName(ITypeSymbol typeInfo) => $"{Namespace}.{GetNamespaceExtension(typeInfo)}.{GetName(typeInfo)}"; + + public static string GetNamespace(ITypeSymbol typeInfo) => $"{Namespace}.{GetNamespaceExtension(typeInfo)}"; + + public static string GetNamespaceExtension(string extension) => $"{Namespace}.Safe{extension}"; + } +} diff --git a/src/JsonMergePatch.SourceGenerator/TypeBuilder.cs b/src/JsonMergePatch.SourceGenerator/TypeBuilder.cs index c6c8ee1..939204d 100644 --- a/src/JsonMergePatch.SourceGenerator/TypeBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/TypeBuilder.cs @@ -7,30 +7,22 @@ namespace LaDeak.JsonMergePatch.SourceGenerator { public class TypeBuilder : ITypeBuilder { - private const string Namespace = "LaDeak.JsonMergePatch.Generated"; - public GeneratedWrapper BuildWrapperType(ITypeSymbol typeInfo, string sourceTypeName) { - var name = GetName(typeInfo); + var name = NameBuilder.GetName(typeInfo); var state = InitializeState(typeInfo, name, sourceTypeName); BuildFile(state); return new GeneratedWrapper() { - FileName = $"LaDeakJsonMergePatch{GetNamespaceExtension(typeInfo)}{name}", + FileName = $"LaDeakJsonMergePatch{NameBuilder.GetNamespaceExtension(typeInfo)}{name}", SourceCode = state.Builder.ToString(), SourceTypeFullName = sourceTypeName, - TargetTypeFullName = GetFullName(typeInfo), + TargetTypeFullName = NameBuilder.GetFullTypeName(typeInfo), ToProcessTypes = state.ToProcessTypeSymbols }; } - private string GetName(ITypeSymbol typeInfo) => $"{typeInfo.Name}Wrapped"; - - private string GetNamespaceExtension(ITypeSymbol typeInfo) => $"S{typeInfo.ContainingNamespace.ToDisplayString()}"; - - private string GetFullName(ITypeSymbol typeInfo) => $"{Namespace}.{GetNamespaceExtension(typeInfo)}.{GetName(typeInfo)}"; - - private void BuildFile(BuilderState state) => BuildNameSpace(state, BuildClass); + private void BuildFile(BuilderState state) => BuildNamespace(state, BuildClass); private void BuildClass(BuilderState state) => BuildClassDeclaration(state, s => BuildClassBody(s)); @@ -49,9 +41,9 @@ private BuilderState InitializeState(ITypeSymbol typeInfo, string name, string s return new BuilderState(typeInformation); } - private void BuildNameSpace(BuilderState state, Action? addBody = null) + private void BuildNamespace(BuilderState state, Action? addBody = null) { - state.AppendLine($"namespace {Namespace}.{GetNamespaceExtension(state.TypeInfo.TypeSymbol)}"); + state.AppendLine($"namespace {NameBuilder.GetNamespace(state.TypeInfo.TypeSymbol)}"); state.AppendLine("{"); addBody?.Invoke(state.IncrementIdentation()); state.AppendLine("}"); @@ -142,7 +134,7 @@ private string ConvertToNullableIfRequired(PropertyInformation propertyInfo, ITy { if (GeneratedTypeFilter.IsGeneratableType(propertyTypeSymbol)) { - return (true, GetFullName(propertyTypeSymbol)); + return (true, NameBuilder.GetFullTypeName(propertyTypeSymbol)); } return (false, propertyTypeSymbol.ToDisplayString(GeneratedTypeFilter.SymbolFormat)); } diff --git a/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs b/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs index c68285e..1eee4c2 100644 --- a/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs +++ b/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs @@ -6,10 +6,11 @@ namespace LaDeak.JsonMergePatch.SourceGenerator { public class TypeRepositoryGenerator { - public string CreateTypeRepository(IEnumerable<(string, string)> typeRegistrations) + public string CreateTypeRepository(IEnumerable<(string, string)> typeRegistrations, string assemblyName) { - StringBuilder sb = new StringBuilder(@" -namespace LaDeak.JsonMergePatch.Generated + StringBuilder sb = new StringBuilder(@$" +namespace {NameBuilder.GetNamespaceExtension(assemblyName.Replace("-", ""))}"); + sb.Append(@" { public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository { @@ -24,17 +25,25 @@ private TypeRepository() sb.Append(@" } + public static LaDeak.JsonMergePatch.Abstractions.ITypeRepository Instance { get; } = new TypeRepository(); + public void Add() where TWrapper : LaDeak.JsonMergePatch.Abstractions.Patch { _repository.Add(typeof(TSource), typeof(TWrapper)); } + public void Add(System.Type source, System.Type wrapper) + { + if (wrapper.IsSubclassOf(typeof(LaDeak.JsonMergePatch.Abstractions.Patch<>).MakeGenericType(source))) + _repository.Add(source, wrapper); + } + public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Type wrapper) { return _repository.TryGetValue(source, out wrapper); } - public static LaDeak.JsonMergePatch.Abstractions.ITypeRepository Instance { get; } = new TypeRepository(); + public System.Collections.Generic.IEnumerable> GetAll() => _repository; } }"); return sb.ToString(); diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BasicSerializationTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BasicSerializationTests.cs index d60238e..dbe3bdc 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BasicSerializationTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BasicSerializationTests.cs @@ -30,7 +30,7 @@ public void ApplyPatch_PropertyToDelete_SetsNullOnTarget( var compilation = CreateInputOutputCompilation(sourceCode); var outputAssembly = SourceBuilder.EmitToAssembly(compilation); - var parentDtoWrappedMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.ParentDtoWrapped"); + var parentDtoWrappedMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.ParentDtoWrapped"); var targetParentMetadata = outputAssembly.GetType("TestCode.ParentDto"); var targetSubMetadata = outputAssembly.GetType("TestCode.SubDto"); diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/DictionarySerializationTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/DictionarySerializationTests.cs index 33fef69..6b40746 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/DictionarySerializationTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/DictionarySerializationTests.cs @@ -20,7 +20,7 @@ public void ApplyPatch_PropertyToDelete_SetsNullOnTarget( var compilation = CreateWrappedTypeCompilation(source); var outputAssembly = SourceBuilder.EmitToAssembly(compilation); - var dtoWrappedMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.DtoWrapped"); + var dtoWrappedMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.DtoWrapped"); var targetMetadata = outputAssembly.GetType("TestCode.Dto"); var target = targetMetadata.GetConstructor(new Type[0]).Invoke(null); targetMetadata.GetProperty("Values").SetValue(target, input); diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/GeneratedWrapperTypeTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/GeneratedWrapperTypeTests.cs index 7460cd9..d2b2a0e 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/GeneratedWrapperTypeTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/GeneratedWrapperTypeTests.cs @@ -13,7 +13,7 @@ public void WrappedDtoType_Ctr_CreatesObject() Compilation outputCompilation = CreateWrappedTypeCompilation(); var assembly = SourceBuilder.EmitToAssembly(outputCompilation); - var sut = assembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto2Wrapped").GetConstructor(new Type[0]).Invoke(null); + var sut = assembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto2Wrapped").GetConstructor(new Type[0]).Invoke(null); Assert.NotNull(sut); } @@ -24,7 +24,7 @@ public void WrappedDtoType_Sets_GeneratedProperties() Compilation outputCompilation = CreateWrappedTypeCompilation(); var assembly = SourceBuilder.EmitToAssembly(outputCompilation); - var wrappedTypeMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto2Wrapped"); + var wrappedTypeMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto2Wrapped"); var sut = wrappedTypeMetadata.GetConstructor(new Type[0]).Invoke(null); wrappedTypeMetadata.GetProperty("Property").SetValue(sut, "test"); @@ -37,7 +37,7 @@ public void WrappedSubDtoType_Sets_GeneratedProperties() Compilation outputCompilation = CreateWrappedTypeCompilation(); var assembly = SourceBuilder.EmitToAssembly(outputCompilation); - var wrappedTypeMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto1Wrapped"); + var wrappedTypeMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto1Wrapped"); var sut = wrappedTypeMetadata.GetConstructor(new Type[0]).Invoke(null); wrappedTypeMetadata.GetProperty("NumberProp").SetValue(sut, 100); @@ -50,8 +50,8 @@ public void WrappedDtoType_Sets_SubDtoProperty() Compilation outputCompilation = CreateWrappedTypeCompilation(); var assembly = SourceBuilder.EmitToAssembly(outputCompilation); - var wrappedDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto2Wrapped"); - var wrappedSubDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto1Wrapped"); + var wrappedDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto2Wrapped"); + var wrappedSubDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto1Wrapped"); var sut = wrappedDtoMetadata.GetConstructor(new Type[0]).Invoke(null); var subDto = wrappedSubDtoMetadata.GetConstructor(new Type[0]).Invoke(null); wrappedDtoMetadata.GetProperty("OtherDto").SetValue(sut, subDto); @@ -65,7 +65,7 @@ public void NullArgument_ApplyPatch_ReturnsObject() Compilation outputCompilation = CreateWrappedTypeCompilation(); var assembly = SourceBuilder.EmitToAssembly(outputCompilation); - var wrappedSubDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto1Wrapped"); + var wrappedSubDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto1Wrapped"); var sut = wrappedSubDtoMetadata.GetConstructor(new Type[0]).Invoke(null); var result = wrappedSubDtoMetadata.GetMethod("ApplyPatch").Invoke(sut, new object[] { null }); @@ -78,8 +78,8 @@ public void ApplyPatch_CallsApplyPath_OnSubDto() Compilation outputCompilation = CreateWrappedTypeCompilation(); var assembly = SourceBuilder.EmitToAssembly(outputCompilation); - var wrappedDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto2Wrapped"); - var wrappedSubDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto1Wrapped"); + var wrappedDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto2Wrapped"); + var wrappedSubDtoMetadata = assembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto1Wrapped"); var sut = wrappedDtoMetadata.GetConstructor(new Type[0]).Invoke(null); var subDto = wrappedSubDtoMetadata.GetConstructor(new Type[0]).Invoke(null); wrappedDtoMetadata.GetProperty("OtherDto").SetValue(sut, subDto); @@ -99,7 +99,7 @@ public void ApplyPatch_SetsPropertiesWithValues_ToTargetObject() var compilation = CreateWrappedTypeCompilation(); var outputAssembly = SourceBuilder.EmitToAssembly(compilation); - var wrappedTypeMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto1Wrapped"); + var wrappedTypeMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto1Wrapped"); var sut = wrappedTypeMetadata.GetConstructor(new Type[0]).Invoke(null); wrappedTypeMetadata.GetProperty("NumberProp").SetValue(sut, 100); @@ -117,7 +117,7 @@ public void ApplyPatch_DoesNotSet_ValuePropertyWithoutValue() var compilation = CreateWrappedTypeCompilation(); var outputAssembly = SourceBuilder.EmitToAssembly(compilation); - var wrappedTypeMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto1Wrapped"); + var wrappedTypeMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto1Wrapped"); var sut = wrappedTypeMetadata.GetConstructor(new Type[0]).Invoke(null); var targetTypeMetadata = outputAssembly.GetType("TestCode.Dto1"); @@ -135,7 +135,7 @@ public void ApplyPatch_DoesNotSet_ReferencePropertyWithoutValue() var compilation = CreateWrappedTypeCompilation(); var outputAssembly = SourceBuilder.EmitToAssembly(compilation); - var wrappedTypeMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto2Wrapped"); + var wrappedTypeMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto2Wrapped"); var sut = wrappedTypeMetadata.GetConstructor(new Type[0]).Invoke(null); var targetTypeMetadata = outputAssembly.GetType("TestCode.Dto2"); @@ -153,7 +153,7 @@ public void ApplyPatch_PropertyToDelete_SetsNullOnTarget() var compilation = CreateWrappedTypeCompilation(); var outputAssembly = SourceBuilder.EmitToAssembly(compilation); - var wrappedTypeMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.Dto2Wrapped"); + var wrappedTypeMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.Dto2Wrapped"); var sut = wrappedTypeMetadata.GetConstructor(new Type[0]).Invoke(null); wrappedTypeMetadata.GetProperty("Property").SetValue(sut, null); diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/SourceBuilder.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/SourceBuilder.cs index e1aaad9..1cd0ffd 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/SourceBuilder.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/SourceBuilder.cs @@ -42,7 +42,7 @@ public static (CSharpCompilation Compilation, SyntaxTree Tree) Compile(string co references.Add(MetadataReference.CreateFromFile(typeof(Patch<>).Assembly.Location)); references.Add(MetadataReference.CreateFromFile(typeof(JsonPropertyNameAttribute).Assembly.Location)); references.AddRange(metadataReferences ?? Enumerable.Empty()); - var compilation = CSharpCompilation.Create($"{Guid.NewGuid()}.dll", new[] { syntaxTree }, references, + var compilation = CSharpCompilation.Create($"Test-{Guid.NewGuid()}", new[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); return (compilation, syntaxTree); } diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeBuilderTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeBuilderTests.cs index 10f2d66..940f312 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeBuilderTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeBuilderTests.cs @@ -32,7 +32,7 @@ public void BuildWrapperType_TargetTypeName_ReturnedTypeNameAndWrapped() var attributes = ImmutableArray.Create(); typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal("LaDeak.JsonMergePatch.Generated.STestTypeNamespace.TestTypeWrapped", result.TargetTypeFullName); + Assert.Equal("LaDeak.JsonMergePatch.Generated.SafeTestTypeNamespace.TestTypeWrapped", result.TargetTypeFullName); } [Fact] @@ -69,7 +69,7 @@ public void BuildWrapperType_FileName_UsesTypeName() var attributes = ImmutableArray.Create(); typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal("LaDeakJsonMergePatchSTestTypeWrapped", result.FileName); + Assert.Equal("LaDeakJsonMergePatchSafeTestTypeWrapped", result.FileName); } [Fact] @@ -83,7 +83,7 @@ public void EmptyType_Returns_EmptyTypeAndNamespace() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -125,7 +125,7 @@ public void TypeWithField_Returns_EmptyTypeAndNamespace() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -167,7 +167,7 @@ public void ReadonlyWriteonlyStaticIndexerAbstract_Property_Ignored() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -197,7 +197,7 @@ public void ClassAttribute_Added_ToGeneratedClass() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { [TestAttribute] [Hello] @@ -232,7 +232,7 @@ public void ReadWriteProperty_Added_BackingFieldAndProperty() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -279,7 +279,7 @@ public void MultipleReadWriteProperty_Added_BackingFieldsAndProperties() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( - @"namespace LaDeak.JsonMergePatch.Generated.S + @"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -338,7 +338,7 @@ public void MultipleReadWriteProperty_AddsTypeDataToProcessSymbols() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( - @"namespace LaDeak.JsonMergePatch.Generated.S + @"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -347,8 +347,8 @@ public TestTypeWrapped() Properties = new bool[2]; } - private LaDeak.JsonMergePatch.Generated.STest.DtoWrapped _testProp0; - public LaDeak.JsonMergePatch.Generated.STest.DtoWrapped TestProp0 + private LaDeak.JsonMergePatch.Generated.SafeTest.DtoWrapped _testProp0; + public LaDeak.JsonMergePatch.Generated.SafeTest.DtoWrapped TestProp0 { get { return _testProp0; } init @@ -398,7 +398,7 @@ public void PropertyWitAttribute_Adds_AttributeOnProperty() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -443,7 +443,7 @@ public void DictionaryWithValueProperty_CreatesPatchToPatchEachValue() typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -495,7 +495,7 @@ public void MultipleDictionaryProperty_CreatesPatchToPatchEachValue() typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -566,7 +566,7 @@ public void DictionaryWithReferenceProperty_CreatesPatchToPatchEachValue() typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -617,7 +617,7 @@ public void DictionaryWithNullableValueProperty_CreatesPatchToPatchEachValue() typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { @@ -675,7 +675,7 @@ public void PropertiesInBaseTypeIncluded() typeSymbol.GetAttributes().Returns(attributes); var result = sut.BuildWrapperType(typeSymbol, "SourceName"); Assert.Equal( -@"namespace LaDeak.JsonMergePatch.Generated.S +@"namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch { diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeRepositoryGeneratorTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeRepositoryGeneratorTests.cs index 2cc8fce..66b2a8f 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeRepositoryGeneratorTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeRepositoryGeneratorTests.cs @@ -14,9 +14,9 @@ public class TypeRepositoryGeneratorTests public void EmptyInput_CreateRepository_GeneratesEmptyConstructor() { var sut = new TypeRepositoryGenerator(); - var result = sut.CreateTypeRepository(null); + var result = sut.CreateTypeRepository(null, string.Empty); Assert.Equal(@" -namespace LaDeak.JsonMergePatch.Generated +namespace LaDeak.JsonMergePatch.Generated.Safe { public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository { @@ -27,17 +27,65 @@ private TypeRepository() } + public static LaDeak.JsonMergePatch.Abstractions.ITypeRepository Instance { get; } = new TypeRepository(); + public void Add() where TWrapper : LaDeak.JsonMergePatch.Abstractions.Patch { _repository.Add(typeof(TSource), typeof(TWrapper)); } + public void Add(System.Type source, System.Type wrapper) + { + if (wrapper.IsSubclassOf(typeof(LaDeak.JsonMergePatch.Abstractions.Patch<>).MakeGenericType(source))) + _repository.Add(source, wrapper); + } + public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Type wrapper) { return _repository.TryGetValue(source, out wrapper); } + public System.Collections.Generic.IEnumerable> GetAll() => _repository; + } +}", result); + } + + [Fact] + public void CustomNamespace_CreateRepository_AppendsSAvoidCollision() + { + var sut = new TypeRepositoryGenerator(); + var result = sut.CreateTypeRepository(null, "CustomNamespace"); + Assert.Equal(@" +namespace LaDeak.JsonMergePatch.Generated.SafeCustomNamespace +{ + public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository + { + private System.Collections.Generic.Dictionary _repository = new System.Collections.Generic.Dictionary(); + + private TypeRepository() + { + + } + public static LaDeak.JsonMergePatch.Abstractions.ITypeRepository Instance { get; } = new TypeRepository(); + + public void Add() where TWrapper : LaDeak.JsonMergePatch.Abstractions.Patch + { + _repository.Add(typeof(TSource), typeof(TWrapper)); + } + + public void Add(System.Type source, System.Type wrapper) + { + if (wrapper.IsSubclassOf(typeof(LaDeak.JsonMergePatch.Abstractions.Patch<>).MakeGenericType(source))) + _repository.Add(source, wrapper); + } + + public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Type wrapper) + { + return _repository.TryGetValue(source, out wrapper); + } + + public System.Collections.Generic.IEnumerable> GetAll() => _repository; } }", result); } @@ -46,9 +94,9 @@ public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullW public void WithTypes_CreateRepository_GeneratesEmptyConstructor() { var sut = new TypeRepositoryGenerator(); - var result = sut.CreateTypeRepository(new[] { ("TestDto0", "TestDto0Wrapped"), ("TestDto1", "TestDto1Wrapped") }); + var result = sut.CreateTypeRepository(new[] { ("TestDto0", "TestDto0Wrapped"), ("TestDto1", "TestDto1Wrapped") }, string.Empty); Assert.Equal(@" -namespace LaDeak.JsonMergePatch.Generated +namespace LaDeak.JsonMergePatch.Generated.Safe { public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository { @@ -61,17 +109,25 @@ private TypeRepository() } + public static LaDeak.JsonMergePatch.Abstractions.ITypeRepository Instance { get; } = new TypeRepository(); + public void Add() where TWrapper : LaDeak.JsonMergePatch.Abstractions.Patch { _repository.Add(typeof(TSource), typeof(TWrapper)); } + public void Add(System.Type source, System.Type wrapper) + { + if (wrapper.IsSubclassOf(typeof(LaDeak.JsonMergePatch.Abstractions.Patch<>).MakeGenericType(source))) + _repository.Add(source, wrapper); + } + public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Type wrapper) { return _repository.TryGetValue(source, out wrapper); } - public static LaDeak.JsonMergePatch.Abstractions.ITypeRepository Instance { get; } = new TypeRepository(); + public System.Collections.Generic.IEnumerable> GetAll() => _repository; } }", result); } @@ -80,14 +136,14 @@ public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullW public void EmptyInput_CreateRepository_Compiles() { var sut = new TypeRepositoryGenerator(); - _ = Compile(sut.CreateTypeRepository(null)); + _ = Compile(sut.CreateTypeRepository(null, string.Empty)); } [Fact] public void Compiled_Repository_ReturnsInstanceSingleton() { var sut = new TypeRepositoryGenerator(); - var typeRepository = GetTypeRepository(Compile(sut.CreateTypeRepository(null))); + var typeRepository = GetTypeRepository(Compile(sut.CreateTypeRepository(null, string.Empty))); Assert.NotNull(typeRepository); } @@ -95,7 +151,7 @@ public void Compiled_Repository_ReturnsInstanceSingleton() public void EmptyRepository_Add_DoesNotThrow() { var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null))); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); sut.Add(); } @@ -103,7 +159,7 @@ public void EmptyRepository_Add_DoesNotThrow() public void EmptyRepository_AddTwiceSameType_ThrowsException() { var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null))); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); sut.Add(); Assert.Throws(() => sut.Add()); } @@ -112,7 +168,7 @@ public void EmptyRepository_AddTwiceSameType_ThrowsException() public void EmptyRepository_TryGet_ReturnsFalse() { var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null))); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); Assert.False(sut.TryGet(typeof(TestDto), out _)); } @@ -120,7 +176,7 @@ public void EmptyRepository_TryGet_ReturnsFalse() public void RepositoryWitTestDto_TryGet_ReturnsTrue() { var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null))); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); sut.Add(); Assert.True(sut.TryGet(typeof(TestDto), out _)); } @@ -129,7 +185,7 @@ public void RepositoryWitTestDto_TryGet_ReturnsTrue() public void RepositoryWitTestDto_TryGet_ReturnsRegisteredType() { var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null))); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); sut.Add(); sut.TryGet(typeof(TestDto), out var result); Assert.Equal(typeof(TestDtoWrapped), result); @@ -139,7 +195,7 @@ public void RepositoryWitTestDto_TryGet_ReturnsRegisteredType() public void RepositoryWitTestDto_TryGet_ReturnsPatchOfUserType() { var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null))); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); sut.Add(); sut.TryGet(typeof(TestDto), out var result); Assert.True(typeof(Patch).IsAssignableFrom(result)); @@ -149,7 +205,7 @@ public void RepositoryWitTestDto_TryGet_ReturnsPatchOfUserType() public void GeneratedTypesInRepository_TryGet_ReturnsRegisteredType() { var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(new[] { (typeof(TestDto).FullName, typeof(TestDtoWrapped).FullName) }))); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(new[] { (typeof(TestDto).FullName, typeof(TestDtoWrapped).FullName) }, string.Empty))); sut.TryGet(typeof(TestDto), out var result); Assert.Equal(typeof(TestDtoWrapped), result); } @@ -159,7 +215,7 @@ public void SameTypeRegisteredTwice_InstanceProperty_Throws() { var generator = new TypeRepositoryGenerator(); var input = new[] { (typeof(TestDto).FullName, typeof(TestDtoWrapped).FullName), (typeof(TestDto).FullName, typeof(TestDtoWrapped).FullName) }; - Assert.Throws(() => GetTypeRepository(Compile(generator.CreateTypeRepository(input)))); + Assert.Throws(() => GetTypeRepository(Compile(generator.CreateTypeRepository(input, string.Empty)))); } private Assembly Compile(string code) @@ -169,7 +225,7 @@ private Assembly Compile(string code) private ITypeRepository GetTypeRepository(Assembly assembly) { - return assembly.GetType("LaDeak.JsonMergePatch.Generated.TypeRepository").GetProperty("Instance").GetValue(null) as ITypeRepository; + return assembly.GetType("LaDeak.JsonMergePatch.Generated.Safe.TypeRepository").GetProperty("Instance").GetValue(null) as ITypeRepository; } } } diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypesWithCtorSerializationTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypesWithCtorSerializationTests.cs index cc741f2..78890eb 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypesWithCtorSerializationTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypesWithCtorSerializationTests.cs @@ -31,7 +31,7 @@ public void ApplyPatch_PropertyToDelete_SetsNullOnTarget( var compilation = CreateInputOutputCompilation(sourceCode); var outputAssembly = SourceBuilder.EmitToAssembly(compilation); - var parentDtoWrappedMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.STestCode.ParentDtoWrapped"); + var parentDtoWrappedMetadata = outputAssembly.GetType("LaDeak.JsonMergePatch.Generated.SafeTestCode.ParentDtoWrapped"); var targetParentMetadata = outputAssembly.GetType("TestCode.ParentDto"); var targetSubMetadata = outputAssembly.GetType("TestCode.SubDto"); diff --git a/test/JsonMergePatch.SourceGenerator.Tests/ModelBuilderExtensionGeneratorTests.cs b/test/JsonMergePatch.SourceGenerator.Tests/ModelBuilderExtensionGeneratorTests.cs index 262d06d..ba445a3 100644 --- a/test/JsonMergePatch.SourceGenerator.Tests/ModelBuilderExtensionGeneratorTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Tests/ModelBuilderExtensionGeneratorTests.cs @@ -102,6 +102,12 @@ public void Add() where TWrapper : Patch { } + public void Add(Type source, Type wrapper) + { + } + + public IEnumerable> GetAll() => Enumerable.Empty>(); + public bool TryGet(Type source, [NotNullWhen(true)] out Type wrapper) { wrapper = null; From 6db85a827aa9b4696305a85362bd45daa8457a80 Mon Sep 17 00:00:00 2001 From: Laszlo Deak Date: Tue, 2 Mar 2021 20:08:35 +0100 Subject: [PATCH 2/3] asp.net core extension supporting extended type repostiories --- .../AspNetJsonMergePatchSourceGenerator.cs | 4 ++- .../ModelBuilderExtensionGenerator.cs | 6 ++-- .../NameBuilder.cs | 2 +- .../TypeRepositoryGenerator.cs | 2 +- .../SourceBuilder.cs | 2 +- .../AspNetCoreSourceBuilder.cs | 4 +-- .../ModelBuilderExtensionGeneratorTests.cs | 28 ++++++++++++++++--- 7 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/JsonMergePatch.SourceGenerator.AspNetCore/AspNetJsonMergePatchSourceGenerator.cs b/src/JsonMergePatch.SourceGenerator.AspNetCore/AspNetJsonMergePatchSourceGenerator.cs index f4e23ed..b2fa997 100644 --- a/src/JsonMergePatch.SourceGenerator.AspNetCore/AspNetJsonMergePatchSourceGenerator.cs +++ b/src/JsonMergePatch.SourceGenerator.AspNetCore/AspNetJsonMergePatchSourceGenerator.cs @@ -9,8 +9,10 @@ public class AspNetJsonMergePatchSourceGenerator : ISourceGenerator { public void Execute(GeneratorExecutionContext context) { + var currentAssemblyName = context.Compilation.Assembly.Name; + var namespaceName = NameBuilder.GetNamespace(currentAssemblyName); var modelBuilderGenerator = new ModelBuilderExtensionGenerator(); - var mvcExtension = modelBuilderGenerator.CreateModelBuilder("LaDeak.JsonMergePatch.Generated.TypeRepository.Instance"); + var mvcExtension = modelBuilderGenerator.CreateModelBuilder($"{namespaceName}.TypeRepository.Instance"); context.AddSource("LaDeakJsonMergePatchModelBuilderExtension", SourceText.From(mvcExtension, Encoding.UTF8)); } diff --git a/src/JsonMergePatch.SourceGenerator.AspNetCore/ModelBuilderExtensionGenerator.cs b/src/JsonMergePatch.SourceGenerator.AspNetCore/ModelBuilderExtensionGenerator.cs index d5df005..1cf33bd 100644 --- a/src/JsonMergePatch.SourceGenerator.AspNetCore/ModelBuilderExtensionGenerator.cs +++ b/src/JsonMergePatch.SourceGenerator.AspNetCore/ModelBuilderExtensionGenerator.cs @@ -14,12 +14,12 @@ namespace LaDeak.JsonMergePatch.Generated { public static class MvcBuilderExtensions { - public static IMvcBuilder AddJsonMergePatch(this IMvcBuilder builder, JsonOptions jsonOptions = null) + public static IMvcBuilder AddJsonMergePatch(this IMvcBuilder builder, JsonOptions jsonOptions = null, LaDeak.JsonMergePatch.Abstractions.ITypeRepository typeRepository = null) {"); if (!string.IsNullOrWhiteSpace(typeRepositoryAccessor)) { - sb.AppendLine($" builder.Services.AddSingleton({typeRepositoryAccessor});"); - sb.AppendLine($" LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository = {typeRepositoryAccessor};"); + sb.AppendLine($" builder.Services.AddSingleton(typeRepository ?? {typeRepositoryAccessor});"); + sb.AppendLine($" LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository = typeRepository ?? {typeRepositoryAccessor};"); } sb.Append(@" jsonOptions ??= new JsonOptions(); return builder.AddMvcOptions(options => options.InputFormatters.Insert(0, new LaDeak.JsonMergePatch.AspNetCore.JsonMergePatchInputReader(jsonOptions))); diff --git a/src/JsonMergePatch.SourceGenerator/NameBuilder.cs b/src/JsonMergePatch.SourceGenerator/NameBuilder.cs index 17c66ae..32042b2 100644 --- a/src/JsonMergePatch.SourceGenerator/NameBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/NameBuilder.cs @@ -14,6 +14,6 @@ public static class NameBuilder public static string GetNamespace(ITypeSymbol typeInfo) => $"{Namespace}.{GetNamespaceExtension(typeInfo)}"; - public static string GetNamespaceExtension(string extension) => $"{Namespace}.Safe{extension}"; + public static string GetNamespace(string extension) => $"{Namespace}.Safe{extension}"; } } diff --git a/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs b/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs index 1eee4c2..07966d2 100644 --- a/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs +++ b/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs @@ -9,7 +9,7 @@ public class TypeRepositoryGenerator public string CreateTypeRepository(IEnumerable<(string, string)> typeRegistrations, string assemblyName) { StringBuilder sb = new StringBuilder(@$" -namespace {NameBuilder.GetNamespaceExtension(assemblyName.Replace("-", ""))}"); +namespace {NameBuilder.GetNamespace(assemblyName)}"); sb.Append(@" { public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/SourceBuilder.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/SourceBuilder.cs index 1cd0ffd..c65513e 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/SourceBuilder.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/SourceBuilder.cs @@ -42,7 +42,7 @@ public static (CSharpCompilation Compilation, SyntaxTree Tree) Compile(string co references.Add(MetadataReference.CreateFromFile(typeof(Patch<>).Assembly.Location)); references.Add(MetadataReference.CreateFromFile(typeof(JsonPropertyNameAttribute).Assembly.Location)); references.AddRange(metadataReferences ?? Enumerable.Empty()); - var compilation = CSharpCompilation.Create($"Test-{Guid.NewGuid()}", new[] { syntaxTree }, references, + var compilation = CSharpCompilation.Create($"Test{Guid.NewGuid().ToString().Replace("-", "")}", new[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); return (compilation, syntaxTree); } diff --git a/test/JsonMergePatch.SourceGenerator.Tests/AspNetCoreSourceBuilder.cs b/test/JsonMergePatch.SourceGenerator.Tests/AspNetCoreSourceBuilder.cs index d428462..9bc5ad3 100644 --- a/test/JsonMergePatch.SourceGenerator.Tests/AspNetCoreSourceBuilder.cs +++ b/test/JsonMergePatch.SourceGenerator.Tests/AspNetCoreSourceBuilder.cs @@ -40,10 +40,10 @@ private static List GetMvcMetadataReferences() return references; } - public static Func GetMethod(Assembly assembly, string type, string methodName) + public static Func GetMethod(Assembly assembly, string type, string methodName) { var method = assembly.GetType(type).GetMethod(methodName); - Func result = method.CreateDelegate>(); + Func result = method.CreateDelegate>(); return result; } diff --git a/test/JsonMergePatch.SourceGenerator.Tests/ModelBuilderExtensionGeneratorTests.cs b/test/JsonMergePatch.SourceGenerator.Tests/ModelBuilderExtensionGeneratorTests.cs index ba445a3..ed5f8dd 100644 --- a/test/JsonMergePatch.SourceGenerator.Tests/ModelBuilderExtensionGeneratorTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Tests/ModelBuilderExtensionGeneratorTests.cs @@ -42,7 +42,7 @@ public void Adds_ITypeRepository_MvcOptions_ServiceRegistrations() var services = Substitute.For(); mvcBuilder.Services.Returns(services); - GetMethodDelegate(code)(mvcBuilder, null); + GetMethodDelegate(code)(mvcBuilder, null, null); services.Received().Add(Arg.Is(x => x.ServiceType == typeof(ITypeRepository))); services.Received().Add(Arg.Is(x => x.ServiceType == typeof(IConfigureOptions))); @@ -62,7 +62,7 @@ public void AddsJsonMergePatchInputReader_ToMvcOptions() .Do(callInfo => optionsSetup = (callInfo[0] as ServiceDescriptor).ImplementationInstance as IConfigureOptions); var options = new Microsoft.AspNetCore.Mvc.MvcOptions(); - GetMethodDelegate(code)(mvcBuilder, null); + GetMethodDelegate(code)(mvcBuilder, null, null); optionsSetup.Configure(options); Assert.Contains(options.InputFormatters, x => x is JsonMergePatchInputReader); @@ -83,13 +83,33 @@ public void PassesCustom_JsonOptions_JsonMergePatchInputReader() var options = new Microsoft.AspNetCore.Mvc.MvcOptions(); var jsonOption = new JsonOptions(); - GetMethodDelegate(code)(mvcBuilder, jsonOption); + GetMethodDelegate(code)(mvcBuilder, jsonOption, null); optionsSetup.Configure(options); Assert.Same(jsonOption.SerializerOptions, options.InputFormatters.OfType().First().SerializerOptions); } - private Func GetMethodDelegate(string code) + [Fact] + public void PassesCustom_TypeRepository_RegistersToServices() + { + var sut = new ModelBuilderExtensionGenerator(); + var code = sut.CreateModelBuilder("new LaDeak.JsonMergePatch.SourceGenerator.AspNetCore.Tests.TypeRepositoryHook()"); + var mvcBuilder = Substitute.For(); + var services = Substitute.For(); + mvcBuilder.Services.Returns(services); + + ITypeRepository registeredTypeRepository = null; + services.When(y => y.Add(Arg.Is(x => x.ServiceType == typeof(ITypeRepository)))) + .Do(callInfo => registeredTypeRepository = (callInfo[0] as ServiceDescriptor).ImplementationInstance as ITypeRepository); + var options = new Microsoft.AspNetCore.Mvc.MvcOptions(); + + var typeRepository = new TypeRepositoryHook(); + GetMethodDelegate(code)(mvcBuilder, null, typeRepository); + + Assert.Same(typeRepository, registeredTypeRepository); + } + + private Func GetMethodDelegate(string code) { IEnumerable metadataReferences = new[] { MetadataReference.CreateFromFile(typeof(TypeRepositoryHook).Assembly.Location) }; return AspNetCoreSourceBuilder.GetMethod(AspNetCoreSourceBuilder.CompileMvcToAssembly(code, metadataReferences), "LaDeak.JsonMergePatch.Generated.MvcBuilderExtensions", "AddJsonMergePatch"); From 72e0a48c9798f81771d333e8ed60c7843bedb3c9 Mon Sep 17 00:00:00 2001 From: Laszlo Deak Date: Tue, 2 Mar 2021 20:44:30 +0100 Subject: [PATCH 3/3] Fixing typos adding unit tests --- .../ITypeRepositoryExntesions.cs | 13 ---- .../ITypeRepositoryExtensions.cs | 20 ++++++ .../NameBuilder.cs | 2 +- .../ITypeRepositoryExtensionTests.cs | 68 +++++++++++++++++++ .../NameBuilderTests.cs | 59 ++++++++++++++++ 5 files changed, 148 insertions(+), 14 deletions(-) delete mode 100644 src/JsonMergePatch.Abstractions/ITypeRepositoryExntesions.cs create mode 100644 src/JsonMergePatch.Abstractions/ITypeRepositoryExtensions.cs create mode 100644 test/JsonMergePatch.SourceGenerator.Abstractions.Tests/ITypeRepositoryExtensionTests.cs create mode 100644 test/JsonMergePatch.SourceGenerator.Abstractions.Tests/NameBuilderTests.cs diff --git a/src/JsonMergePatch.Abstractions/ITypeRepositoryExntesions.cs b/src/JsonMergePatch.Abstractions/ITypeRepositoryExntesions.cs deleted file mode 100644 index 8061fc7..0000000 --- a/src/JsonMergePatch.Abstractions/ITypeRepositoryExntesions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace LaDeak.JsonMergePatch.Abstractions -{ - public static class ITypeRepositoryExntesions - { - public static ITypeRepository Extend(this ITypeRepository repository, ITypeRepository other) - { - foreach (var item in other.GetAll()) - if (!repository.TryGet(item.Key, out _)) - repository.Add(item.Key, item.Value); - return repository; - } - } -} \ No newline at end of file diff --git a/src/JsonMergePatch.Abstractions/ITypeRepositoryExtensions.cs b/src/JsonMergePatch.Abstractions/ITypeRepositoryExtensions.cs new file mode 100644 index 0000000..ba70e9f --- /dev/null +++ b/src/JsonMergePatch.Abstractions/ITypeRepositoryExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LaDeak.JsonMergePatch.Abstractions +{ + public static class ITypeRepositoryExtensions + { + public static ITypeRepository Extend(this ITypeRepository target, ITypeRepository source) + { + _ = target ?? throw new ArgumentNullException(nameof(target)); + _ = source ?? throw new ArgumentNullException(nameof(source)); + + foreach (var item in source.GetAll() ?? Enumerable.Empty>()) + if (!target.TryGet(item.Key, out _)) + target.Add(item.Key, item.Value); + return target; + } + } +} \ No newline at end of file diff --git a/src/JsonMergePatch.SourceGenerator/NameBuilder.cs b/src/JsonMergePatch.SourceGenerator/NameBuilder.cs index 32042b2..fbff545 100644 --- a/src/JsonMergePatch.SourceGenerator/NameBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/NameBuilder.cs @@ -4,7 +4,7 @@ namespace LaDeak.JsonMergePatch.SourceGenerator { public static class NameBuilder { - internal const string Namespace = "LaDeak.JsonMergePatch.Generated"; + private const string Namespace = "LaDeak.JsonMergePatch.Generated"; public static string GetName(ITypeSymbol typeInfo) => $"{typeInfo.Name}Wrapped"; diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/ITypeRepositoryExtensionTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/ITypeRepositoryExtensionTests.cs new file mode 100644 index 0000000..83f057c --- /dev/null +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/ITypeRepositoryExtensionTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LaDeak.JsonMergePatch.Abstractions; +using NSubstitute; +using Xunit; + +namespace LaDeak.JsonMergePatch.SourceGenerator +{ + public class ITypeRepositoryExtensionTests + { + [Fact] + public void WithTypesInOther_ExtendAddsAllTo_TargetTypeRepository() + { + var testData = new[] { new KeyValuePair(typeof(object), typeof(string)), new KeyValuePair(typeof(object), typeof(int)) }; + var targetRespository = Substitute.For(); + var sourceRepository = Substitute.For(); + sourceRepository.GetAll().Returns(testData); + + var result = targetRespository.Extend(sourceRepository); + + Assert.Same(targetRespository, result); + targetRespository.Received().Add(testData[0].Key, testData[0].Value); + targetRespository.Received().Add(testData[1].Key, testData[1].Value); + } + + [Fact] + public void WithNoTypesInOther_ExtendAddsAllTo_TargetTypeRepository() + { + var testData = Enumerable.Empty>(); + var targetRespository = Substitute.For(); + var sourceRepository = Substitute.For(); + sourceRepository.GetAll().Returns(testData); + + var result = targetRespository.Extend(sourceRepository); + + Assert.Same(targetRespository, result); + targetRespository.DidNotReceiveWithAnyArgs().Add(null, null); + } + + [Fact] + public void WithNullTypesInOther_ExtendAddsAllTo_TargetTypeRepository() + { + var targetRespository = Substitute.For(); + var sourceRepository = Substitute.For(); + sourceRepository.GetAll().Returns((IEnumerable>)null); + + var result = targetRespository.Extend(sourceRepository); + + Assert.Same(targetRespository, result); + targetRespository.DidNotReceiveWithAnyArgs().Add(null, null); + } + + [Fact] + public void NullTarget_ThrowsArgumetNullExcepion() + { + ITypeRepository targetRespository = null; + Assert.Throws(() => targetRespository.Extend(Substitute.For())); + } + + [Fact] + public void NullSource_ThrowsArgumetNullExcepion() + { + ITypeRepository targetRespository = Substitute.For(); + Assert.Throws(() => targetRespository.Extend(null)); + } + } +} diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/NameBuilderTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/NameBuilderTests.cs new file mode 100644 index 0000000..59ec057 --- /dev/null +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/NameBuilderTests.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using NSubstitute; +using Xunit; + +namespace LaDeak.JsonMergePatch.SourceGenerator.Tests +{ + public class NameBuilderTests + { + [Fact] + public void GetName_ReturnsTypeName_WithWrappedSuffix() + { + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestName"); + var result = NameBuilder.GetName(typeSymbol); + Assert.Equal("TestNameWrapped", result); + } + + [Fact] + public void GetNamespaceExtension_Returns_SafeExtension() + { + var typeSymbol = Substitute.For(); + typeSymbol.ContainingNamespace.ToDisplayString().Returns("Test.Namespace"); + var result = NameBuilder.GetNamespaceExtension(typeSymbol); + Assert.Equal("SafeTest.Namespace", result); + } + + [Fact] + public void GetNamespace_Returns_Namespace_SafeExtension() + { + var typeSymbol = Substitute.For(); + typeSymbol.ContainingNamespace.ToDisplayString().Returns("Test.Namespace"); + var result = NameBuilder.GetNamespace(typeSymbol); + Assert.Equal("LaDeak.JsonMergePatch.Generated.SafeTest.Namespace", result); + } + + + [Fact] + public void GetNamespaceOnString_Returns_Namespace_SafeExtension() + { + var result = NameBuilder.GetNamespace("Test.Namespace"); + Assert.Equal("LaDeak.JsonMergePatch.Generated.SafeTest.Namespace", result); + } + + [Fact] + public void GetGetFullTypeName_Returns_Namespace_SafeExtension_TypeNameWrapped() + { + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestName"); + typeSymbol.ContainingNamespace.ToDisplayString().Returns("Test.Namespace"); + var result = NameBuilder.GetFullTypeName(typeSymbol); + Assert.Equal("LaDeak.JsonMergePatch.Generated.SafeTest.Namespace.TestNameWrapped", result); + } + } +}