diff --git a/.github/codecoverage.runsettings b/.github/codecoverage.runsettings deleted file mode 100644 index e53648e..0000000 --- a/.github/codecoverage.runsettings +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - cobertura - **/test/**/*.cs,**/sample/**/*.cs, - - - - - diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 87a913b..0a55285 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -1,5 +1,4 @@ name: CD - on: release: types: [created] @@ -12,14 +11,12 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' + dotnet-version: '9.0.x' include-prerelease: true - name: Set VERSION variable from tag run: echo "VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV - - name: Restore dependencies - run: dotnet restore - name: Build - run: dotnet build --configuration Release --no-restore -p:Version=${VERSION} + run: dotnet build --configuration Release -p:Version=${VERSION} - name: Test run: dotnet test - name: Create package diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 31622d7..0aa03c5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,23 +4,15 @@ on: [push, workflow_dispatch] jobs: build: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' + dotnet-version: '9.0.x' include-prerelease: true - - name: Install dependencies - run: dotnet restore - name: Build - run: dotnet build --configuration Release --no-restore + run: dotnet build --configuration Release - name: Test - run: dotnet test --collect:"XPlat Code Coverage" --settings ./.github/codecoverage.runsettings - - name: Upload Test Coverage - uses: codecov/codecov-action@v4 - with: - flags: dotnet + run: dotnet test \ No newline at end of file diff --git a/Readme.md b/Readme.md index 3d59880..64c2c53 100644 --- a/Readme.md +++ b/Readme.md @@ -2,7 +2,7 @@ JsonMergePatch library provides an implementation for json merge patch operations, detailed in RFC7396. This library uses C# source generators to generate the types required for serialization. The Http package provides extension methods for HTTP requests and responses, while the AspNetCore package provides an InputReader implementation. -[![CI](https://github.com/ladeak/JsonMergePatch/workflows/CI/badge.svg)](https://github.com/ladeak/JsonMergePatch/actions) [![CodeCoverage](https://codecov.io/gh/ladeak/JsonMergePatch/branch/master/graph/badge.svg)](https://app.codecov.io/gh/ladeak/JsonMergePatch) [![NuGet](https://img.shields.io/nuget/v/LaDeak.JsonMergePatch.AspNetCore.svg)](https://www.nuget.org/packages/LaDeak.JsonMergePatch.AspNetCore/) +[![CI](https://github.com/ladeak/JsonMergePatch/workflows/CI/badge.svg)](https://github.com/ladeak/JsonMergePatch/actions) [![NuGet](https://img.shields.io/nuget/v/LaDeak.JsonMergePatch.AspNetCore.svg)](https://www.nuget.org/packages/LaDeak.JsonMergePatch.AspNetCore/) ## Getting Started @@ -10,7 +10,7 @@ JsonMergePatch library helps to deserialize http requests' and responses' json b JsonMergePatch library is based on C# source generators. For the http body content to be deserialized into a type, the SourceGenerator library generates helper classes. Helper classes are called Wrappers, capturing all the features of the type intended to be used for the deserialization. Once the request is deserialized into a Wrapper object, the object can be used to apply the patch on the user defined target object. The JsonMergePatch library is designed to be used with POCO classes and record types. -Source Generations requires Visual Studio 16.9 or later. +Source Generations requires Visual Studio 17.12 or later. Based on the given application type different packages may be installed from NuGet by running one or more of the following commands: @@ -24,7 +24,7 @@ dotnet add package LaDeak.JsonMergePatch.AspNetCore 1. Install AspNetCore package via NuGet 1. Add the required usings -1. Add a new controller with a parameter types ```Patch``` where ```T``` is a custom target type chosen by the user +1. Add a new controller with a parameter types ```Patch``` where ```T``` is a custom target type chosen by the user. Make sure that `[Patchable]` is applied on the `T` target type. 1. Extend application startup ### Install AspNetCore packages via NuGet @@ -58,7 +58,7 @@ public WeatherForecast PatchForecast(Patch input) } ``` -During build, the source generator scans for methods with type parameters of ```Patch```. When such a parameter is found a Wrapper type is generated for ```T```. The base class of the generated type provides the necessary operations to work with the type. +During build, the source generator scans types has `[Patchable]` attribute applied. When such a type is found a Wrapper type is generated for it. ### Extend application startup @@ -86,9 +86,7 @@ The AspNetCore input reader supports requests with ```application/merge-patch+js ### Patchable -Certain use-cases require to generate wrapper types with the source generation for assemblies that do not directly use `Patch` (where T is the wrapped source type). This could be a reason for having separate assemblies for entity types, or because of the need of stacking multiple source generators on top of each other. -In thie case types may be attributed with `[Patchable]` attribute: - +To generate wrapper types with the source generation add the `[Patchable]` attribute: ```csharp [Patchable] public class WeatherForecast @@ -97,8 +95,6 @@ public class WeatherForecast } ``` -`[Patchable]` makes sure to generate wrapper types for source types not used in HTTP requests or method arguments of `Patch`. - ### Using it with System.Text.Json source generation In order to use multiple source generators, we need to *stack* them. Today the only way to do it is by enforcing a build order between two projects, while adding the first source generator to the first project built, and the second one to the second project built. To make sure JsonMergePatch source generator works with System.Text.Json's source generator create two projects: diff --git a/sample/AspNetCoreMinimal.Entities/AspNetCoreMinimal.Entities.csproj b/sample/AspNetCoreMinimal.Entities/AspNetCoreMinimal.Entities.csproj index 7a30cb4..1001cd2 100644 --- a/sample/AspNetCoreMinimal.Entities/AspNetCoreMinimal.Entities.csproj +++ b/sample/AspNetCoreMinimal.Entities/AspNetCoreMinimal.Entities.csproj @@ -1,9 +1,10 @@  - net8.0 + net9.0 enable false + true diff --git a/sample/AspNetCoreMinimal.Entities/Entities.cs b/sample/AspNetCoreMinimal.Entities/Entities.cs index 7bd1c40..7eff814 100644 --- a/sample/AspNetCoreMinimal.Entities/Entities.cs +++ b/sample/AspNetCoreMinimal.Entities/Entities.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using LaDeak.JsonMergePatch.Abstractions; namespace AspNetCoreMinimal.Entities; diff --git a/sample/AspNetCoreMinimal/AspNetCoreMinimal.csproj b/sample/AspNetCoreMinimal/AspNetCoreMinimal.csproj index 9c386ca..b6b4cfd 100644 --- a/sample/AspNetCoreMinimal/AspNetCoreMinimal.csproj +++ b/sample/AspNetCoreMinimal/AspNetCoreMinimal.csproj @@ -1,9 +1,10 @@  - net8.0 + net9.0 enable false + true diff --git a/sample/AspNetCoreMinimal/Controllers/SampleController.cs b/sample/AspNetCoreMinimal/Controllers/SampleController.cs index 247039a..df20908 100644 --- a/sample/AspNetCoreMinimal/Controllers/SampleController.cs +++ b/sample/AspNetCoreMinimal/Controllers/SampleController.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; +using System.Text.Json; using AspNetCoreMinimal.Entities; using LaDeak.JsonMergePatch.Abstractions; using LaDeak.JsonMergePatch.Http; @@ -43,7 +39,7 @@ public CitiesData PatchCities(Patch input) [HttpGet("ReadJsonPatchAsync")] public async Task GetReadJsonPatchAsync() { - var target = new WeatherForecast() { Date = DateTime.UtcNow, Summary = "Sample weather forecast", TemperatureC = 24 }; + var target = new WeatherForecast() { Date = DateTime.UtcNow, Summary = "Sample weather forecast", TemperatureC = 22 }; var httpClient = _clientFactory.CreateClient(); var response = await httpClient.GetAsync("https://localhost:5001/Sample/Weather", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); var responseData = await response.Content.ReadJsonPatchAsync(new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }).ConfigureAwait(false); diff --git a/sample/AspNetCoreMinimal/Program.cs b/sample/AspNetCoreMinimal/Program.cs index 6903614..ef756aa 100644 --- a/sample/AspNetCoreMinimal/Program.cs +++ b/sample/AspNetCoreMinimal/Program.cs @@ -1,8 +1,5 @@ using System.Text.Json.Serialization; using LaDeak.JsonMergePatch.AspNetCore; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; var builder = WebApplication.CreateBuilder(args); @@ -10,23 +7,16 @@ { LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository = LaDeak.JsonMergePatch.Generated.SafeAspNetCoreMinimal.Entities.TypeRepository.Instance; var jsonOptions = new Microsoft.AspNetCore.Http.Json.JsonOptions(); - jsonOptions.SerializerOptions.AddContext(); + jsonOptions.SerializerOptions.TypeInfoResolver = SampleJsonContext.Default; options.InputFormatters.Insert(0, new JsonMergePatchInputReader(jsonOptions)); }); builder.Services.AddHttpClient(); - - var app = builder.Build(); - app.UseHttpsRedirection(); - app.UseAuthorization(); - app.MapControllers(); - app.Run(); - [JsonSerializable(typeof(LaDeak.JsonMergePatch.Generated.SafeAspNetCoreMinimal.Entities.WeatherForecastWrapped))] [JsonSerializable(typeof(LaDeak.JsonMergePatch.Generated.SafeAspNetCoreMinimal.Entities.CitiesDataWrapped))] public partial class SampleJsonContext : JsonSerializerContext diff --git a/sample/AspNetCoreMinimal/Properties/launchSettings.json b/sample/AspNetCoreMinimal/Properties/launchSettings.json index 09bc039..2cd653f 100644 --- a/sample/AspNetCoreMinimal/Properties/launchSettings.json +++ b/sample/AspNetCoreMinimal/Properties/launchSettings.json @@ -1,31 +1,13 @@ { "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:42816", - "sslPort": 44364 - } - }, "profiles": { "AspNetCoreMinimal": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } } } } diff --git a/sample/AspNetCoreMinimal/sample.http b/sample/AspNetCoreMinimal/sample.http new file mode 100644 index 0000000..630952d --- /dev/null +++ b/sample/AspNetCoreMinimal/sample.http @@ -0,0 +1,29 @@ +### + +GET https://localhost:5001/Sample/Weather + +### + +PATCH https://localhost:5001/Sample/PatchWeather +Content-Type: application/merge-patch+json + +{ + "temp":23, + "summary":null +} +### + +PATCH https://localhost:5001/Sample/PatchCities +Content-Type: application/merge-patch+json + +{ + "cities": + { + "Dublin":"Ireland", + "London":"GB", + "New York":null + } +} +### + +GET https://localhost:5001/Sample/ReadJsonPatchAsync \ No newline at end of file diff --git a/sample/ConsoleApp/ConsoleApp.csproj b/sample/ConsoleApp/ConsoleApp.csproj index 154843d..84f4e11 100644 --- a/sample/ConsoleApp/ConsoleApp.csproj +++ b/sample/ConsoleApp/ConsoleApp.csproj @@ -2,8 +2,9 @@ Exe - net8.0 + net9.0 false + true diff --git a/sample/ConsoleApp/Program.cs b/sample/ConsoleApp/Program.cs index e3a8226..094d226 100644 --- a/sample/ConsoleApp/Program.cs +++ b/sample/ConsoleApp/Program.cs @@ -1,38 +1,31 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; -using ConsoleAppLibrary; -using LaDeak.JsonMergePatch.Abstractions; +using LaDeak.JsonMergePatch.Abstractions; using LaDeak.JsonMergePatch.Http; -namespace ReadJsonPatchAsync +namespace ReadJsonPatchAsync; + +public class Program { - public class Program + [Patchable] + public class WeatherForecast { - public class WeatherForecast - { - public DateTime Date { get; set; } - public int Temp { get; set; } - public string Summary { get; set; } - } - - public static async Task Main(string[] args) - { - LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository = LaDeak.JsonMergePatch.Generated.SafeConsoleApp.TypeRepository.Instance.Extend(LaDeak.JsonMergePatch.Generated.SafeConsoleAppLibrary.TypeRepository.Instance); - await ReadAsJsonMergePatchAsync(); - } + public DateTime Date { get; set; } + public int Temp { get; set; } + public string Summary { get; set; } + } - public static async Task ReadAsJsonMergePatchAsync() - { - var httpClient = new HttpClient(); - var response = await httpClient.GetAsync("https://localhost:5001/Sample/Weather", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); - var responseData = await response.Content.ReadJsonPatchAsync().ConfigureAwait(false); - var target = new WeatherForecast() { Date = DateTime.UtcNow, Summary = "Sample weather forecast", Temp = 24 }; - var result = responseData.ApplyPatch(target); - Console.WriteLine($"Patched: Date={result.Date}, Summary={result.Summary}, Temp={result.Temp}"); + public static async Task Main(string[] args) + { + LaDeak.JsonMergePatch.Abstractions.JsonMergePatchOptions.Repository = LaDeak.JsonMergePatch.Generated.SafeConsoleApp.TypeRepository.Instance.Extend(LaDeak.JsonMergePatch.Generated.SafeConsoleAppLibrary.TypeRepository.Instance); + await ReadAsJsonMergePatchAsync(); + } - var client = new Client(); - await client.ReadAsJsonMergePatchAsync(); - } + public static async Task ReadAsJsonMergePatchAsync() + { + var httpClient = new HttpClient(); + var response = await httpClient.GetAsync("https://localhost:5001/Sample/Weather", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); + var responseData = await response.Content.ReadJsonPatchAsync().ConfigureAwait(false); + var target = new WeatherForecast() { Date = DateTime.UtcNow, Summary = "Sample weather forecast", Temp = 22 }; + var result = responseData.ApplyPatch(target); + Console.WriteLine($"Patched: Date={result.Date}, Summary={result.Summary}, Temp={result.Temp}"); } } diff --git a/sample/ConsoleAppLibrary/ConsoleAppLibrary.csproj b/sample/ConsoleAppLibrary/ConsoleAppLibrary.csproj index 8303415..6e0c6f7 100644 --- a/sample/ConsoleAppLibrary/ConsoleAppLibrary.csproj +++ b/sample/ConsoleAppLibrary/ConsoleAppLibrary.csproj @@ -1,8 +1,9 @@ - net8.0 + net9.0 false + true diff --git a/sample/ConsoleAppLibrary/Sample.cs b/sample/ConsoleAppLibrary/Sample.cs index 14b3b93..5050fc3 100644 --- a/sample/ConsoleAppLibrary/Sample.cs +++ b/sample/ConsoleAppLibrary/Sample.cs @@ -1,26 +1,24 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; +using LaDeak.JsonMergePatch.Abstractions; using LaDeak.JsonMergePatch.Http; -namespace ConsoleAppLibrary +namespace ConsoleAppLibrary; + +[Patchable] +public class DeviceData { - public class DeviceData - { - public double Watts { get; set; } - public string Name { get; set; } - } + public double Watts { get; set; } + public string Name { get; set; } +} - public class Client +public class Client +{ + public async Task ReadAsJsonMergePatchAsync() { - 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 target = new DeviceData() { Watts = 5, Name = "phone" }; - var result = responseData.ApplyPatch(target); - Console.WriteLine($"Patched: Name={result.Name}, Watts={result.Watts}"); - } + 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 target = new DeviceData() { Watts = 5, Name = "phone" }; + var result = responseData.ApplyPatch(target); + Console.WriteLine($"Patched: Name={result.Name}, Watts={result.Watts}"); } } diff --git a/src/JsonMergePatch.Abstractions/ITypeRepository.cs b/src/JsonMergePatch.Abstractions/ITypeRepository.cs index 9fd0d4b..36e95f8 100644 --- a/src/JsonMergePatch.Abstractions/ITypeRepository.cs +++ b/src/JsonMergePatch.Abstractions/ITypeRepository.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace LaDeak.JsonMergePatch.Abstractions; diff --git a/src/JsonMergePatch.Abstractions/ITypeRepositoryExtensions.cs b/src/JsonMergePatch.Abstractions/ITypeRepositoryExtensions.cs index 049ff89..5830d0f 100644 --- a/src/JsonMergePatch.Abstractions/ITypeRepositoryExtensions.cs +++ b/src/JsonMergePatch.Abstractions/ITypeRepositoryExtensions.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace LaDeak.JsonMergePatch.Abstractions; +namespace LaDeak.JsonMergePatch.Abstractions; public static class ITypeRepositoryExtensions { diff --git a/src/JsonMergePatch.Abstractions/JsonMergePatch.Abstractions.csproj b/src/JsonMergePatch.Abstractions/JsonMergePatch.Abstractions.csproj index efc1c31..d66a216 100644 --- a/src/JsonMergePatch.Abstractions/JsonMergePatch.Abstractions.csproj +++ b/src/JsonMergePatch.Abstractions/JsonMergePatch.Abstractions.csproj @@ -4,6 +4,7 @@ netstandard2.1 LaDeak.JsonMergePatch.Abstractions enable + true diff --git a/src/JsonMergePatch.Abstractions/Patch.cs b/src/JsonMergePatch.Abstractions/Patch.cs index e310d4d..9a8ff68 100644 --- a/src/JsonMergePatch.Abstractions/Patch.cs +++ b/src/JsonMergePatch.Abstractions/Patch.cs @@ -7,7 +7,7 @@ public abstract class Patch /// /// List of properties used by the request. /// - protected bool[] Properties; + protected bool[] Properties = []; /// /// Applies updates on input type using Json Merge Patch rules. diff --git a/src/JsonMergePatch.Abstractions/PatchableAttribute.cs b/src/JsonMergePatch.Abstractions/PatchableAttribute.cs index 247589f..5768232 100644 --- a/src/JsonMergePatch.Abstractions/PatchableAttribute.cs +++ b/src/JsonMergePatch.Abstractions/PatchableAttribute.cs @@ -1,6 +1,4 @@ -using System; - -namespace LaDeak.JsonMergePatch.Abstractions; +namespace LaDeak.JsonMergePatch.Abstractions; [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class)] public class PatchableAttribute : Attribute diff --git a/src/JsonMergePatch.AspNetCore/JsonMergePatch.AspNetCore.csproj b/src/JsonMergePatch.AspNetCore/JsonMergePatch.AspNetCore.csproj index a472b33..8fb008e 100644 --- a/src/JsonMergePatch.AspNetCore/JsonMergePatch.AspNetCore.csproj +++ b/src/JsonMergePatch.AspNetCore/JsonMergePatch.AspNetCore.csproj @@ -1,9 +1,10 @@  - net8.0 + net9.0 LaDeak.JsonMergePatch.AspNetCore enable + true diff --git a/src/JsonMergePatch.AspNetCore/JsonMergePatchInputReader.cs b/src/JsonMergePatch.AspNetCore/JsonMergePatchInputReader.cs index 6ea96c0..e3feb04 100644 --- a/src/JsonMergePatch.AspNetCore/JsonMergePatchInputReader.cs +++ b/src/JsonMergePatch.AspNetCore/JsonMergePatchInputReader.cs @@ -1,9 +1,5 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; +using System.Text; using System.Text.Json; -using System.Threading.Tasks; using LaDeak.JsonMergePatch.Abstractions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Json; diff --git a/src/JsonMergePatch.Http/HttpContentExtensions.cs b/src/JsonMergePatch.Http/HttpContentExtensions.cs index cf825c7..d8f8384 100644 --- a/src/JsonMergePatch.Http/HttpContentExtensions.cs +++ b/src/JsonMergePatch.Http/HttpContentExtensions.cs @@ -1,9 +1,5 @@ -using System; -using System.Net.Http; -using System.Text; +using System.Text; using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; using LaDeak.JsonMergePatch.Abstractions; namespace LaDeak.JsonMergePatch.Http; @@ -16,21 +12,18 @@ public static class HttpContentExtensions public static async Task?> ReadJsonPatchAsync(this HttpContent content, ITypeRepository typeRepository, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { - if (content == null) - throw new ArgumentNullException(nameof(content)); - if (typeRepository == null) - throw new ArgumentNullException(nameof(typeRepository)); + ArgumentNullException.ThrowIfNull(content); + ArgumentNullException.ThrowIfNull(typeRepository); if (!typeRepository.TryGet(typeof(TResult), out var wrapperType)) throw new ArgumentException($"{typeof(TResult)} is missing generated wrapper type. Check if all members of the type definition is supported."); if (content.Headers.ContentType?.MediaType != "application/merge-patch+json" && content.Headers.ContentType?.MediaType != "application/json" && !string.IsNullOrWhiteSpace(content.Headers.ContentType?.MediaType)) return null; var contentStream = await content.ReadAsStreamAsync().ConfigureAwait(false); -#if NET5_0_OR_GREATER Encoding? encoding = content.Headers.ContentType?.CharSet != null ? GetEncoding(content.Headers.ContentType.CharSet) : null; if (encoding != null && encoding != Encoding.UTF8) contentStream = Encoding.CreateTranscodingStream(contentStream, encoding, Encoding.UTF8); -#endif + await using (contentStream.ConfigureAwait(false)) { var contentData = await JsonSerializer.DeserializeAsync(contentStream, wrapperType, options ?? _serializerOptions, cancellationToken).ConfigureAwait(false); diff --git a/src/JsonMergePatch.Http/JsonMergePatch.Http.csproj b/src/JsonMergePatch.Http/JsonMergePatch.Http.csproj index 88f0806..9ccbf93 100644 --- a/src/JsonMergePatch.Http/JsonMergePatch.Http.csproj +++ b/src/JsonMergePatch.Http/JsonMergePatch.Http.csproj @@ -1,9 +1,10 @@  - net8.0 + net9.0 LaDeak.JsonMergePatch.Http enable + true diff --git a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/ApplyPatchBuilder.cs b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/ApplyPatchBuilder.cs index 51673ac..a9f91c2 100644 --- a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/ApplyPatchBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/ApplyPatchBuilder.cs @@ -3,7 +3,7 @@ public abstract class ApplyPatchBuilder { /// - /// Generates logic to instantiate object for init property. + /// Generates logic to instantiate object with init property. /// /// The state representing the ApplyPatch method. /// The index of the property in the 'Properties' collection. @@ -11,7 +11,7 @@ public abstract class ApplyPatchBuilder public abstract BuilderState BuildInitOnly(BuilderState state, int i); /// - /// Generates logic to instantiate object for non-init property. + /// Generates logic to instantiate object with non-init property. /// /// The state representing the ApplyPatch method. /// The index of the property in the 'Properties' collection. diff --git a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/GeneratableListBuilder.cs b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/GeneratableListBuilder.cs index 3bc6894..b20211b 100644 --- a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/GeneratableListBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/GeneratableListBuilder.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; namespace LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; @@ -10,9 +8,9 @@ namespace LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; public class GeneratableListBuilder : ApplyPatchBuilder { private readonly ITypeSymbol _firstGenericType; - private readonly IPropertySymbol _property; + private readonly string _propertyName; - public GeneratableListBuilder(INamedTypeSymbol namedType, IPropertySymbol property) + public GeneratableListBuilder(INamedTypeSymbol namedType, string propertyName) { if (!namedType.Name.Contains("List") || namedType.ContainingNamespace.ToDisplayString() != "System.Collections.Generic" @@ -21,32 +19,31 @@ public GeneratableListBuilder(INamedTypeSymbol namedType, IPropertySymbol proper throw new ArgumentException("Input argument type is not a generic list with generatable type."); } _firstGenericType = namedType.TypeArguments.First(); - _property = property; + _propertyName = propertyName; } public override BuilderState BuildInitOnly(BuilderState state, int i) { - state.AppendLine($"{_property.Name} = Properties[{i}] && input.{_property.Name} == null ? new() : input.{_property.Name},"); + state.AppendLine($"{_propertyName} = Properties[{i}] && input.{_propertyName} == null ? new() : input.{_propertyName},"); return state; } public override BuilderState BuildInstantiation(BuilderState state, int i) { state.AppendLine($"if (Properties[{i}])"); - state.IncrementIdentation().AppendLine($"input.{_property.Name} = new();"); + state.IncrementIdentation().AppendLine($"input.{_propertyName} = new();"); return state; } public override BuilderState BuildPatch(BuilderState state) { if (_firstGenericType != null && GeneratedTypeFilter.IsGeneratableType(_firstGenericType)) - PopulateGeneratableListProperties(state, _property); + PopulateGeneratableListProperties(state, _propertyName); return state; } - private void PopulateGeneratableListProperties(BuilderState state, IPropertySymbol property) + private void PopulateGeneratableListProperties(BuilderState state, string propertyName) { - var propertyName = property.Name; state.AppendLine($"if({propertyName} != null)"); state.AppendLine("{"); var ifBody = state.IncrementIdentation(); diff --git a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/NonGeneratableDictionaryPatchBuilder.cs b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/NonGeneratableDictionaryPatchBuilder.cs index b73e96a..539d85a 100644 --- a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/NonGeneratableDictionaryPatchBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/NonGeneratableDictionaryPatchBuilder.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; namespace LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; @@ -9,41 +8,42 @@ namespace LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; public class NonGeneratableDictionaryPatchBuilder : ApplyPatchBuilder { private readonly INamedTypeSymbol _namedType; - private readonly IPropertySymbol _property; + private readonly string _propertyName; + private readonly bool _hasGeneratableType; private readonly bool _isConvertedToNullableType; - public NonGeneratableDictionaryPatchBuilder(INamedTypeSymbol namedType, IPropertySymbol property, bool isConvertedToNullableType) + public NonGeneratableDictionaryPatchBuilder(INamedTypeSymbol namedType, string propertyName, bool hasGeneratableType, bool isConvertedToNullableType) { if (!namedType.Name.Contains("Dictionary") || namedType.ContainingNamespace.ToDisplayString() != "System.Collections.Generic") throw new ArgumentException($"Input argument type is not a valid for {nameof(NonGeneratableDictionaryPatchBuilder)}"); _namedType = namedType; - _property = property; + _propertyName = propertyName; + _hasGeneratableType = hasGeneratableType; _isConvertedToNullableType = isConvertedToNullableType; } public override BuilderState BuildInitOnly(BuilderState state, int i) { - state.AppendLine($"{_property.Name} = Properties[{i}] && input.{_property.Name} == null ? new() : input.Values,"); + state.AppendLine($"{_propertyName} = Properties[{i}] && input.{_propertyName} == null ? new() : input.Values,"); return state; } public override BuilderState BuildInstantiation(BuilderState state, int i) { state.AppendLine($"if (Properties[{i}])"); - state.IncrementIdentation().AppendLine($"input.{_property.Name} ??= new();"); + state.IncrementIdentation().AppendLine($"input.{_propertyName} ??= new();"); return state; } public override BuilderState BuildPatch(BuilderState state) { - if (!GeneratedTypeFilter.IsGeneratableType(_property.Type)) - PopulateDictionary(state, _property); + if (!_hasGeneratableType) + PopulateDictionary(state, _propertyName); return state; } - private void PopulateDictionary(BuilderState state, IPropertySymbol property) + private void PopulateDictionary(BuilderState state, string propertyName) { - var propertyName = property.Name; state.AppendLine($"if({propertyName} != null)"); state.AppendLine("{"); var ifBody = state.IncrementIdentation(); diff --git a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/SimpleGeneratableBuilder.cs b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/SimpleGeneratableBuilder.cs index f3c8626..99c5676 100644 --- a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/SimpleGeneratableBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/SimpleGeneratableBuilder.cs @@ -1,32 +1,20 @@ -using System; -using Microsoft.CodeAnalysis; - -namespace LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; +namespace LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; /// /// Builds ApplyPatch method for user types where a Wrapper DTO is generated. /// -public class SimpleGeneratableBuilder : ApplyPatchBuilder +public class SimpleGeneratableBuilder(string name) : ApplyPatchBuilder { - private readonly IPropertySymbol _propertySymbol; - - public SimpleGeneratableBuilder(IPropertySymbol propertySymbol) - { - if (!GeneratedTypeFilter.IsGeneratableType(propertySymbol.Type)) - throw new ArgumentException("The given property is not simple generatable"); - _propertySymbol = propertySymbol; - } - public override BuilderState BuildInitOnly(BuilderState state, int i) { - state.AppendLine($"{_propertySymbol.Name} = Properties[{i}] ? this.{_propertySymbol.Name}?.ApplyPatch(input.{_propertySymbol.Name}) : input.{_propertySymbol.Name},"); + state.AppendLine($"{name} = Properties[{i}] ? this.{name}?.ApplyPatch(input.{name}) : input.{name},"); return state; } public override BuilderState BuildInstantiation(BuilderState state, int i) { state.AppendLine($"if (Properties[{i}])"); - state.IncrementIdentation().AppendLine($"input.{_propertySymbol.Name} = {_propertySymbol.Name}?.ApplyPatch(input.{_propertySymbol.Name});"); + state.IncrementIdentation().AppendLine($"input.{name} = {name}?.ApplyPatch(input.{name});"); return state; } diff --git a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/SimpleNonGeneratableBuilder.cs b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/SimpleNonGeneratableBuilder.cs index dfcb346..9829c09 100644 --- a/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/SimpleNonGeneratableBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/ApplyPatchBuilders/SimpleNonGeneratableBuilder.cs @@ -1,40 +1,26 @@ -using System; -using Microsoft.CodeAnalysis; - -namespace LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; +namespace LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; /// /// Builds ApplyPatch method for types where no wrapper is generated, or the type is nullable. /// -public class SimpleNonGeneratableBuilder : ApplyPatchBuilder +public class SimpleNonGeneratableBuilder(string name, bool isConvertedToNullable) : ApplyPatchBuilder { - private readonly IPropertySymbol _propertySymbol; - private readonly bool _isConvertedToNullable; - - public SimpleNonGeneratableBuilder(IPropertySymbol propertySymbol, bool isConvertedToNullable) - { - if (GeneratedTypeFilter.IsGeneratableType(propertySymbol.Type)) - throw new ArgumentException("The given property is not simple generatable"); - _propertySymbol = propertySymbol; - _isConvertedToNullable = isConvertedToNullable; - } - public override BuilderState BuildInitOnly(BuilderState state, int i) { - if (_isConvertedToNullable) - state.AppendLine($"{_propertySymbol.Name} = Properties[{i}] ? ({_propertySymbol.Name}.HasValue ? this.{_propertySymbol.Name}.Value : default) : input.{_propertySymbol.Name},"); + if (isConvertedToNullable) + state.AppendLine($"{name} = Properties[{i}] ? ({name}.HasValue ? this.{name}.Value : default) : input.{name},"); else - state.AppendLine($"{_propertySymbol.Name} = Properties[{i}] ? this.{_propertySymbol.Name} : input.{_propertySymbol.Name},"); + state.AppendLine($"{name} = Properties[{i}] ? this.{name} : input.{name},"); return state; } public override BuilderState BuildInstantiation(BuilderState state, int i) { state.AppendLine($"if (Properties[{i}])"); - if (_isConvertedToNullable) - state.IncrementIdentation().AppendLine($"input.{_propertySymbol.Name} = {_propertySymbol.Name}.HasValue ? {_propertySymbol.Name}.Value : default;"); + if (isConvertedToNullable) + state.IncrementIdentation().AppendLine($"input.{name} = {name}.HasValue ? {name}.Value : default;"); else - state.IncrementIdentation().AppendLine($"input.{_propertySymbol.Name} = {_propertySymbol.Name};"); + state.IncrementIdentation().AppendLine($"input.{name} = {name};"); return state; } diff --git a/src/JsonMergePatch.SourceGenerator/BuilderState.cs b/src/JsonMergePatch.SourceGenerator/BuilderState.cs index 252d77f..e7f70c3 100644 --- a/src/JsonMergePatch.SourceGenerator/BuilderState.cs +++ b/src/JsonMergePatch.SourceGenerator/BuilderState.cs @@ -1,33 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.CodeAnalysis; +using System.Text; -namespace LaDeak.JsonMergePatch.SourceGenerator +namespace LaDeak.JsonMergePatch.SourceGenerator; + +public class BuilderState { - public class BuilderState - { - public StringBuilder Builder { get; } - public int Identation { get; } - public TypeInformation TypeInfo { get; } - public List ToProcessTypeSymbols { get; } + public StringBuilder Builder { get; } + public int Identation { get; } + public GeneratorClassInfo TypeInfo { get; } - public BuilderState(TypeInformation typeInfo) : this(new StringBuilder(), 0, typeInfo, new List()) - { - } + public BuilderState(GeneratorClassInfo typeInfo) : this(new StringBuilder(), 0, typeInfo) + { + } - private BuilderState(StringBuilder builder, int identation, TypeInformation typeInfo, List toProcessTypeSymbols) - { - Builder = builder ?? throw new ArgumentNullException(nameof(builder)); - Identation = identation; - TypeInfo = typeInfo ?? throw new ArgumentNullException(nameof(typeInfo)); - ToProcessTypeSymbols = toProcessTypeSymbols; - } + private BuilderState(StringBuilder builder, int identation, GeneratorClassInfo typeInfo) + { + Builder = builder ?? throw new ArgumentNullException(nameof(builder)); + Identation = identation; + TypeInfo = typeInfo ?? throw new ArgumentNullException(nameof(typeInfo)); + } - public BuilderState IncrementIdentation() => new BuilderState(Builder, Identation + 4, TypeInfo, ToProcessTypeSymbols); + public BuilderState IncrementIdentation() => new BuilderState(Builder, Identation + 4, TypeInfo); - public void AppendLine(string line) => Builder.AppendLineWithIdentation(Identation, line); + public void AppendLine(string line) => Builder.AppendLineWithIdentation(Identation, line); - public void AppendLine() => Builder.AppendLine(); - } + public void AppendLine() => Builder.AppendLine(); } diff --git a/src/JsonMergePatch.SourceGenerator/Casing.cs b/src/JsonMergePatch.SourceGenerator/Casing.cs index 9363a29..2d85d55 100644 --- a/src/JsonMergePatch.SourceGenerator/Casing.cs +++ b/src/JsonMergePatch.SourceGenerator/Casing.cs @@ -1,22 +1,20 @@ -using System; -using System.Text; +using System.Text; -namespace LaDeak.JsonMergePatch.SourceGenerator +namespace LaDeak.JsonMergePatch.SourceGenerator; + +public class Casing { - public class Casing - { - [ThreadStatic] - public static StringBuilder? _builder; + [ThreadStatic] + public static StringBuilder? _builder; - public static string PrefixUnderscoreCamelCase(string name) - { - _builder ??= new StringBuilder(); - _builder.Clear(); - _builder.Append('_'); - _builder.Append(char.ToLowerInvariant(name[0])); - for (int i = 1; i < name.Length; i++) - _builder.Append(name[i]); - return _builder.ToString(); - } + public static string PrefixUnderscoreCamelCase(string name) + { + _builder ??= new StringBuilder(); + _builder.Clear(); + _builder.Append('_'); + _builder.Append(char.ToLowerInvariant(name[0])); + for (int i = 1; i < name.Length; i++) + _builder.Append(name[i]); + return _builder.ToString(); } } diff --git a/src/JsonMergePatch.SourceGenerator/GeneratedTypeFilter.cs b/src/JsonMergePatch.SourceGenerator/GeneratedTypeFilter.cs index 662b827..c195d42 100644 --- a/src/JsonMergePatch.SourceGenerator/GeneratedTypeFilter.cs +++ b/src/JsonMergePatch.SourceGenerator/GeneratedTypeFilter.cs @@ -1,61 +1,58 @@ -using System.Linq; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; -namespace LaDeak.JsonMergePatch.SourceGenerator -{ - public static class GeneratedTypeFilter - { - public static SymbolDisplayFormat SymbolFormat = new SymbolDisplayFormat(SymbolDisplayGlobalNamespaceStyle.Omitted, SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, SymbolDisplayGenericsOptions.IncludeTypeParameters, SymbolDisplayMemberOptions.None, SymbolDisplayDelegateStyle.NameOnly, SymbolDisplayExtensionMethodStyle.Default, SymbolDisplayParameterOptions.IncludeType, SymbolDisplayPropertyStyle.NameOnly, SymbolDisplayLocalOptions.None, SymbolDisplayKindOptions.None, SymbolDisplayMiscellaneousOptions.None); +namespace LaDeak.JsonMergePatch.SourceGenerator; - public static bool IsGeneratableType(ITypeSymbol typeInfo) - { - bool generic = false; - if (typeInfo is INamedTypeSymbol namedTypeInfo) - generic = namedTypeInfo.IsGenericType; - - // Check for System types that have SpecialType.None. - string typeName = typeInfo.ToDisplayString(); - bool isNonSpecialSystemType = typeName == "System.Guid" || typeName == "System.DateOnly" || typeName == "System.TimeOnly"; - - return typeInfo.SpecialType == SpecialType.None - && !isNonSpecialSystemType - && !typeInfo.IsAnonymousType - && !typeInfo.IsAbstract - && !generic && !typeInfo.IsStatic - && typeInfo.TypeKind != TypeKind.Enum; - } +public static class GeneratedTypeFilter +{ + public static SymbolDisplayFormat SymbolFormat = new SymbolDisplayFormat(SymbolDisplayGlobalNamespaceStyle.Omitted, SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, SymbolDisplayGenericsOptions.IncludeTypeParameters, SymbolDisplayMemberOptions.None, SymbolDisplayDelegateStyle.NameOnly, SymbolDisplayExtensionMethodStyle.Default, SymbolDisplayParameterOptions.IncludeType, SymbolDisplayPropertyStyle.NameOnly, SymbolDisplayLocalOptions.None, SymbolDisplayKindOptions.None, SymbolDisplayMiscellaneousOptions.None); + public static bool IsGeneratableType(ITypeSymbol typeInfo) + { + bool generic = false; + if (typeInfo is INamedTypeSymbol namedTypeInfo) + generic = namedTypeInfo.IsGenericType; + + // Check for System types that have SpecialType.None. + string typeName = typeInfo.ToDisplayString(); + bool isNonSpecialSystemType = typeName == "System.Guid" || typeName == "System.DateOnly" || typeName == "System.TimeOnly"; + + return typeInfo.SpecialType == SpecialType.None + && !isNonSpecialSystemType + && !typeInfo.IsAnonymousType + && !typeInfo.IsAbstract + && !generic && !typeInfo.IsStatic + && typeInfo.TypeKind != TypeKind.Enum; + } - public static bool TryGetGeneratableType(ITypeSymbol typeInfo, out ITypeSymbol generatableType) + public static bool TryGetGeneratableType(ITypeSymbol typeInfo, out ITypeSymbol generatableType) + { + if (typeInfo is INamedTypeSymbol namedTypeInfo) { - if (typeInfo is INamedTypeSymbol namedTypeInfo) + if (namedTypeInfo.IsGenericType && !namedTypeInfo.IsUnboundGenericType) { - if (namedTypeInfo.IsGenericType && !namedTypeInfo.IsUnboundGenericType) + ITypeSymbol? genericTypeArgument = null; + if (namedTypeInfo.TypeArguments.Length == 1 && namedTypeInfo.SpecialType != SpecialType.None) + genericTypeArgument = namedTypeInfo.TypeArguments.First(); + else if (namedTypeInfo.TypeArguments.Length == 2 && namedTypeInfo.Name == "Dictionary" && namedTypeInfo.ContainingNamespace.ToDisplayString() == "System.Collections.Generic") { - ITypeSymbol? genericTypeArgument = null; - if (namedTypeInfo.TypeArguments.Length == 1 && namedTypeInfo.SpecialType != SpecialType.None) - genericTypeArgument = namedTypeInfo.TypeArguments.First(); - else if (namedTypeInfo.TypeArguments.Length == 2 && namedTypeInfo.Name == "Dictionary" && namedTypeInfo.ContainingNamespace.ToDisplayString() == "System.Collections.Generic") - { - genericTypeArgument = namedTypeInfo.TypeArguments.Last(); - if (genericTypeArgument is INamedTypeSymbol dictionaryValueType && dictionaryValueType.IsGenericType && dictionaryValueType.SpecialType == SpecialType.System_Nullable_T) - genericTypeArgument = dictionaryValueType.TypeArguments.First(); - } - if (genericTypeArgument != null && IsGeneratableType(genericTypeArgument)) - { - generatableType = genericTypeArgument; - return true; - } + genericTypeArgument = namedTypeInfo.TypeArguments.Last(); + if (genericTypeArgument is INamedTypeSymbol dictionaryValueType && dictionaryValueType.IsGenericType && dictionaryValueType.SpecialType == SpecialType.System_Nullable_T) + genericTypeArgument = dictionaryValueType.TypeArguments.First(); + } + if (genericTypeArgument != null && IsGeneratableType(genericTypeArgument)) + { + generatableType = genericTypeArgument; + return true; } } - - generatableType = typeInfo; - return IsGeneratableType(typeInfo); } - public static string SourceTypeName(ITypeSymbol typeInfo) - { - return typeInfo.ToDisplayString(SymbolFormat); - } + generatableType = typeInfo; + return IsGeneratableType(typeInfo); + } + + public static string SourceTypeName(ITypeSymbol typeInfo) + { + return typeInfo.ToDisplayString(SymbolFormat); } } diff --git a/src/JsonMergePatch.SourceGenerator/GeneratedWrapper.cs b/src/JsonMergePatch.SourceGenerator/GeneratedWrapper.cs index 0f67fd1..36e1d1b 100644 --- a/src/JsonMergePatch.SourceGenerator/GeneratedWrapper.cs +++ b/src/JsonMergePatch.SourceGenerator/GeneratedWrapper.cs @@ -1,14 +1,9 @@ -using System.Collections.Generic; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; -namespace LaDeak.JsonMergePatch.SourceGenerator +namespace LaDeak.JsonMergePatch.SourceGenerator; + +public class GeneratedWrapper { - public class GeneratedWrapper - { - public string? FileName { get; set; } - public string? SourceCode { get; set; } - public string? SourceTypeFullName { get; set; } - public string? TargetTypeFullName { get; set; } - public List? ToProcessTypes { get; set; } - } + public string? FileName { get; set; } + public string? SourceCode { get; set; } } diff --git a/src/JsonMergePatch.SourceGenerator/IEnumerableExtensions.cs b/src/JsonMergePatch.SourceGenerator/IEnumerableExtensions.cs index 5b6354f..94b1c73 100644 --- a/src/JsonMergePatch.SourceGenerator/IEnumerableExtensions.cs +++ b/src/JsonMergePatch.SourceGenerator/IEnumerableExtensions.cs @@ -1,17 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; -namespace LaDeak.JsonMergePatch.SourceGenerator +namespace LaDeak.JsonMergePatch.SourceGenerator; + +public static class IEnumerableExtensions { - public static class IEnumerableExtensions + public static bool AnyOrNull(this IEnumerable source, Func predicate) { - public static bool AnyOrNull(this IEnumerable source, Func predicate) - { - if (source is null || !source.GetEnumerator().MoveNext()) - return true; - return source.Any(predicate); - } + if (source is null || !source.GetEnumerator().MoveNext()) + return true; + return source.Any(predicate); } } diff --git a/src/JsonMergePatch.SourceGenerator/IPatchParametersWalker.cs b/src/JsonMergePatch.SourceGenerator/IPatchParametersWalker.cs index 3caaf4c..af44f1b 100644 --- a/src/JsonMergePatch.SourceGenerator/IPatchParametersWalker.cs +++ b/src/JsonMergePatch.SourceGenerator/IPatchParametersWalker.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; namespace LaDeak.JsonMergePatch.SourceGenerator { diff --git a/src/JsonMergePatch.SourceGenerator/ITypeBuilder.cs b/src/JsonMergePatch.SourceGenerator/ITypeBuilder.cs index 881790b..636a260 100644 --- a/src/JsonMergePatch.SourceGenerator/ITypeBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/ITypeBuilder.cs @@ -1,9 +1,6 @@ -using Microsoft.CodeAnalysis; +namespace LaDeak.JsonMergePatch.SourceGenerator; -namespace LaDeak.JsonMergePatch.SourceGenerator +public interface ITypeBuilder { - public interface ITypeBuilder - { - GeneratedWrapper BuildWrapperType(ITypeSymbol typeInfo, string sourceTypeName); - } + GeneratedWrapper BuildWrapperType(GeneratorClassInfo typeInfo); } \ No newline at end of file diff --git a/src/JsonMergePatch.SourceGenerator/ITypeBuilderGenerator.cs b/src/JsonMergePatch.SourceGenerator/ITypeBuilderGenerator.cs index 5632ce4..7a3587d 100644 --- a/src/JsonMergePatch.SourceGenerator/ITypeBuilderGenerator.cs +++ b/src/JsonMergePatch.SourceGenerator/ITypeBuilderGenerator.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; +namespace LaDeak.JsonMergePatch.SourceGenerator; -namespace LaDeak.JsonMergePatch.SourceGenerator +public interface ITypeBuilderGenerator { - public interface ITypeBuilderGenerator - { - IEnumerable Generate(); - } + IEnumerable Generate(); } \ No newline at end of file diff --git a/src/JsonMergePatch.SourceGenerator/JsonMergePatch.SourceGenerator.csproj b/src/JsonMergePatch.SourceGenerator/JsonMergePatch.SourceGenerator.csproj index a3fd4f6..f35de82 100644 --- a/src/JsonMergePatch.SourceGenerator/JsonMergePatch.SourceGenerator.csproj +++ b/src/JsonMergePatch.SourceGenerator/JsonMergePatch.SourceGenerator.csproj @@ -1,15 +1,17 @@ - + netstandard2.0 LaDeak.JsonMergePatch.SourceGenerator enable false + true + true - - + + diff --git a/src/JsonMergePatch.SourceGenerator/JsonMergePatchSourceGenerator.cs b/src/JsonMergePatch.SourceGenerator/JsonMergePatchSourceGenerator.cs index c44d95e..f3a6cab 100644 --- a/src/JsonMergePatch.SourceGenerator/JsonMergePatchSourceGenerator.cs +++ b/src/JsonMergePatch.SourceGenerator/JsonMergePatchSourceGenerator.cs @@ -1,31 +1,37 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; namespace LaDeak.JsonMergePatch.SourceGenerator; [Generator] -public class JsonMergePatchSourceGenerator : ISourceGenerator +public class JsonMergePatchSourceGenerator : IIncrementalGenerator { - public void Execute(GeneratorExecutionContext context) => ExecuteImpl(context); - - protected virtual IEnumerable ExecuteImpl(GeneratorExecutionContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - var typeBuilder = new MultiTypeBuilder(context.Compilation.SyntaxTrees, context.Compilation, new TypeBuilder(), new PatchParametersWalker()); - var types = typeBuilder.Generate(); - foreach (var generatedType in types) - context.AddSource(generatedType.FileName, SourceText.From(generatedType.SourceCode, Encoding.UTF8)); + var provider = context.SyntaxProvider.ForAttributeWithMetadataName("LaDeak.JsonMergePatch.Abstractions.PatchableAttribute", (x, token) => true, + static (GeneratorAttributeSyntaxContext context, CancellationToken token) => + { + var typeSymbol = context.TargetSymbol as INamedTypeSymbol; + if (!GeneratedTypeFilter.TryGetGeneratableType(typeSymbol, out var _)) + return null; + var assemblyName = context.SemanticModel.Compilation.Assembly.Name; + var sourceTypeName = GeneratedTypeFilter.SourceTypeName(typeSymbol); + return new GeneratorClassInfo(typeSymbol, sourceTypeName, assemblyName); + }).Where(x => x != null); - var tyeRepoGenerator = new TypeRepositoryGenerator(); - 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; - } + context.RegisterImplementationSourceOutput(provider, (spc, classInfo) => + { + var builder = new TypeBuilder(); + var generatedType = builder.BuildWrapperType(classInfo); + spc.AddSource(generatedType.FileName, SourceText.From(generatedType.SourceCode, Encoding.UTF8)); + }); - public void Initialize(GeneratorInitializationContext context) - { + context.RegisterImplementationSourceOutput(provider.Collect(), (spc, classInfos) => + { + var tyeRepoGenerator = new TypeRepositoryGenerator(); + var typeRepoClass = tyeRepoGenerator.CreateTypeRepository(classInfos.Select(x => (x.SourceTypeName, x.FullTypeName)), classInfos.First().AssemblyName); + spc.AddSource("LaDeakJsonMergePatchTypeRepo", SourceText.From(typeRepoClass, Encoding.UTF8)); + }); } } - diff --git a/src/JsonMergePatch.SourceGenerator/MultiTypeBuilder.cs b/src/JsonMergePatch.SourceGenerator/MultiTypeBuilder.cs deleted file mode 100644 index 99401a0..0000000 --- a/src/JsonMergePatch.SourceGenerator/MultiTypeBuilder.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace LaDeak.JsonMergePatch.SourceGenerator; - -public class MultiTypeBuilder -{ - private readonly IEnumerable _syntaxTrees; - private readonly Compilation _compilation; - private readonly ITypeBuilder _typeBuilder; - private readonly IPatchParametersWalker _walker; - private readonly Stack _typesToWrap; - - public MultiTypeBuilder(IEnumerable syntaxTrees, Compilation compilation, ITypeBuilder typeBuilder, IPatchParametersWalker walker) - { - _typesToWrap = new Stack(); - _syntaxTrees = syntaxTrees ?? throw new ArgumentNullException(nameof(syntaxTrees)); - _compilation = compilation ?? throw new ArgumentNullException(nameof(compilation)); - _typeBuilder = typeBuilder ?? throw new ArgumentNullException(nameof(typeBuilder)); - _walker = walker ?? throw new ArgumentNullException(nameof(walker)); - } - - public IEnumerable Generate() - { - _typesToWrap.Clear(); - var result = new List(); - foreach (SyntaxTree tree in _syntaxTrees) - WalkTree(result, tree); - - return result; - } - - private void WalkTree(List result, SyntaxTree tree) - { - var typesToWrap = _walker.Process(tree.GetRoot(), _compilation.GetSemanticModel(tree)); - PushToWrap(typesToWrap); - while (_typesToWrap.Count > 0) - { - var typeInfo = _typesToWrap.Pop(); - var sourceTypeName = GeneratedTypeFilter.SourceTypeName(typeInfo); - if (!result.Any(x => x.SourceTypeFullName == sourceTypeName) - && GeneratedTypeFilter.TryGetGeneratableType(typeInfo, out var typeInfoToGenerate)) - { - var generatedTypeResult = _typeBuilder.BuildWrapperType(typeInfoToGenerate, sourceTypeName); - PushToWrap(generatedTypeResult.ToProcessTypes); - result.Add(generatedTypeResult); - } - } - } - - private void PushToWrap(IEnumerable typesToWrap) - { - foreach (var typeToWrap in typesToWrap) - _typesToWrap.Push(typeToWrap); - } -} - \ No newline at end of file diff --git a/src/JsonMergePatch.SourceGenerator/PatchParametersWalker.cs b/src/JsonMergePatch.SourceGenerator/PatchParametersWalker.cs index 33cb415..2775b5b 100644 --- a/src/JsonMergePatch.SourceGenerator/PatchParametersWalker.cs +++ b/src/JsonMergePatch.SourceGenerator/PatchParametersWalker.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/src/JsonMergePatch.SourceGenerator/StringBuilderExtensions.cs b/src/JsonMergePatch.SourceGenerator/StringBuilderExtensions.cs index 54f6402..d4e92fb 100644 --- a/src/JsonMergePatch.SourceGenerator/StringBuilderExtensions.cs +++ b/src/JsonMergePatch.SourceGenerator/StringBuilderExtensions.cs @@ -1,14 +1,13 @@ using System.Text; -namespace LaDeak.JsonMergePatch.SourceGenerator +namespace LaDeak.JsonMergePatch.SourceGenerator; + +public static class StringBuilderExtensions { - public static class StringBuilderExtensions + public static StringBuilder AppendLineWithIdentation(this StringBuilder sb, int identation, string line) { - public static StringBuilder AppendLineWithIdentation(this StringBuilder sb, int identation, string line) - { - sb.Append(' ', identation); - sb.AppendLine(line); - return sb; - } + sb.Append(' ', identation); + sb.AppendLine(line); + return sb; } } diff --git a/src/JsonMergePatch.SourceGenerator/TypeBuilder.cs b/src/JsonMergePatch.SourceGenerator/TypeBuilder.cs index 845d856..7e395f5 100644 --- a/src/JsonMergePatch.SourceGenerator/TypeBuilder.cs +++ b/src/JsonMergePatch.SourceGenerator/TypeBuilder.cs @@ -1,118 +1,68 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Reflection; using LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; using Microsoft.CodeAnalysis; namespace LaDeak.JsonMergePatch.SourceGenerator; -public class TypeBuilder : ITypeBuilder +public record PropertyInfo { - public GeneratedWrapper BuildWrapperType(ITypeSymbol typeInfo, string sourceTypeName) + public PropertyInfo(IPropertySymbol symbol) { - var name = NameBuilder.GetName(typeInfo); - var state = InitializeState(typeInfo, name, sourceTypeName); - BuildFile(state); - return new GeneratedWrapper() - { - FileName = $"LaDeakJsonMergePatch{NameBuilder.GetNamespaceExtension(typeInfo)}{name}", - SourceCode = state.Builder.ToString(), - SourceTypeFullName = sourceTypeName, - TargetTypeFullName = NameBuilder.GetFullTypeName(typeInfo), - ToProcessTypes = state.ToProcessTypeSymbols - }; + Name = symbol.Name; + Attributes = ReadAttributes(symbol).ToList(); + IsInitOnly = GetIsInitOnly(symbol); + HasGeneratableType = GeneratedTypeFilter.IsGeneratableType(symbol.Type); + PropertyTypeName = GetPropertyTypeName(symbol); } - private void BuildFile(BuilderState state) => BuildNamespace(state, BuildClass); - - private void BuildClass(BuilderState state) => BuildClassDeclaration(state, s => BuildClassBody(s)); - - private BuilderState InitializeState(ITypeSymbol typeInfo, string name, string sourceTypeName) + private static bool GetIsInitOnly(IPropertySymbol symbol) { - var typeInformation = new TypeInformation() { Name = name, SourceTypeName = sourceTypeName, TypeSymbol = typeInfo }; + return symbol.SetMethod?.OriginalDefinition.IsInitOnly ?? false; + } - var currentType = typeInfo; - while (currentType != null && currentType.Name != "Object") - { - typeInformation.Properties.AddRange(currentType.GetMembers().OfType() - .Where(x => !x.IsReadOnly && !x.IsWriteOnly && !x.IsIndexer && !x.IsStatic && !x.IsAbstract && !x.IsVirtual).Select(x => new PropertyInformation() { Property = x, IsConvertedToNullableType = false })); - currentType = currentType.BaseType; - } + public string Name { get; } - return new BuilderState(typeInformation); - } + public List Attributes { get; } - private void BuildNamespace(BuilderState state, Action? addBody = null) - { - state.AppendLine($"#nullable enable"); - state.AppendLine($"namespace {NameBuilder.GetNamespace(state.TypeInfo.TypeSymbol)}"); - state.AppendLine("{"); - addBody?.Invoke(state.IncrementIdentation()); - state.AppendLine("}"); - state.AppendLine($"#nullable disable"); - } + public bool HasGeneratableType { get; } - private void BuildClassDeclaration(BuilderState state, Action? addBody = null) - { - BuildAttributes(state, state.TypeInfo.TypeSymbol.GetAttributes()); - state.AppendLine($"public class {state.TypeInfo.Name} : LaDeak.JsonMergePatch.Abstractions.Patch<{state.TypeInfo.SourceTypeName}>"); - state.AppendLine("{"); - addBody?.Invoke(state.IncrementIdentation()); - state.AppendLine("}"); - } + public bool IsInitOnly { get; } - private void BuildConstructor(BuilderState state, Action? addBody = null) - { - state.AppendLine($"public {state.TypeInfo.Name}()"); - state.AppendLine("{"); - var innerState = state.IncrementIdentation(); - innerState.AppendLine($"Properties = new bool[{state.TypeInfo.Properties.Count}];"); - addBody?.Invoke(innerState); - state.AppendLine("}"); - } + public string PropertyTypeName { get; } + + public ApplyPatchBuilder? Builder { get; private set; } - private void BuildPropery(BuilderState state, PropertyInformation propertyInfo, int propertyId) + private IEnumerable ReadAttributes(IPropertySymbol symbol) { - state.ToProcessTypeSymbols.Add(propertyInfo.Property.Type); - string fieldName = Casing.PrefixUnderscoreCamelCase(propertyInfo.Property.Name); - string propertyTypeName = GetPropertyTypeName(propertyInfo); - state.AppendLine($"private {propertyTypeName} {fieldName};"); - BuildAttributes(state, propertyInfo.Property.GetAttributes()); - state.AppendLine($"public {propertyTypeName} {propertyInfo.Property.Name}"); - state.AppendLine("{"); - var getterSetter = state.IncrementIdentation(); - getterSetter.AppendLine($"get {{ return {fieldName}; }}"); - getterSetter.AppendLine("set"); - getterSetter.AppendLine("{"); - var setterBody = getterSetter.IncrementIdentation(); - setterBody.AppendLine($"Properties[{propertyId}] = true;"); - setterBody.AppendLine($"{fieldName} = value;"); - getterSetter.AppendLine("}"); - state.AppendLine("}"); + return symbol.GetAttributes().Where(attribute => + attribute.AttributeClass?.ToDisplayString() != "System.Runtime.CompilerServices.NullableContextAttribute" + && attribute.AttributeClass?.ToDisplayString() != "System.Runtime.CompilerServices.NullableAttribute" + && attribute.AttributeClass?.ToDisplayString() != "LaDeak.JsonMergePatch.Abstractions.PatchableAttribute"); } - private string GetPropertyTypeName(PropertyInformation propertyInfo) + private string GetPropertyTypeName(IPropertySymbol symbol) { - var propertyTypeSymbol = propertyInfo.Property.Type; - if (propertyTypeSymbol is INamedTypeSymbol namedType && namedType.IsGenericType) - return CreateGenericTypeWithParameters(propertyInfo); + if (symbol.Type is INamedTypeSymbol namedType && namedType.IsGenericType) + return CreateGenericTypeWithParameters(symbol); - return ConvertToNullableIfRequired(propertyInfo, propertyTypeSymbol); + return ConvertToNullableIfRequired(symbol.Type).Item1; } - private string CreateGenericTypeWithParameters(PropertyInformation propertyInfo) + private string CreateGenericTypeWithParameters(IPropertySymbol propertySymbol) { - if (propertyInfo?.Property?.Type is not INamedTypeSymbol namedType || !namedType.IsGenericType) + if (propertySymbol?.Type is not INamedTypeSymbol namedType || !namedType.IsGenericType) throw new InvalidOperationException("Parameter is not generic type parameter."); - propertyInfo.Builder = new SimpleNonGeneratableBuilder(propertyInfo.Property, false); + Builder = new SimpleNonGeneratableBuilder(Name, false); var firstUnderlyingType = GetPropertyTypeName(namedType.TypeArguments.First()).TypeName; var withoutUnderlyingType = namedType.ToDisplayString(new SymbolDisplayFormat(SymbolDisplayGlobalNamespaceStyle.Omitted, SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, SymbolDisplayGenericsOptions.None, SymbolDisplayMemberOptions.None, SymbolDisplayDelegateStyle.NameOnly, SymbolDisplayExtensionMethodStyle.Default, SymbolDisplayParameterOptions.IncludeType, SymbolDisplayPropertyStyle.NameOnly, SymbolDisplayLocalOptions.IncludeType, SymbolDisplayKindOptions.None, SymbolDisplayMiscellaneousOptions.ExpandNullable)); var genericResult = $"{withoutUnderlyingType}<{firstUnderlyingType}"; + bool isConvertedToNullableType = false; foreach (var underlyingType in namedType.TypeArguments.Skip(1).OfType()) { - string genericTypeParam = ConvertToNullableIfRequired(propertyInfo, underlyingType); + (string genericTypeParam, bool isConvertedToNullable) = ConvertToNullableIfRequired(underlyingType); + isConvertedToNullableType |= isConvertedToNullable; genericResult += $", {genericTypeParam}"; } genericResult += ">"; @@ -121,23 +71,24 @@ private string CreateGenericTypeWithParameters(PropertyInformation propertyInfo) genericResult += "?"; } if (namedType.Name.Contains("Dictionary") && namedType.ContainingNamespace.ToDisplayString() == "System.Collections.Generic") - propertyInfo.Builder = new NonGeneratableDictionaryPatchBuilder(namedType, propertyInfo.Property, propertyInfo.IsConvertedToNullableType); + Builder = new NonGeneratableDictionaryPatchBuilder(namedType, Name, HasGeneratableType, isConvertedToNullableType); if (namedType.Name.Contains("List") && namedType.ContainingNamespace.ToDisplayString() == "System.Collections.Generic" - && !IsInitOnlyProperty(propertyInfo.Property) + && !GetIsInitOnly(propertySymbol) && GeneratedTypeFilter.IsGeneratableType(namedType.TypeArguments.First())) - propertyInfo.Builder = new GeneratableListBuilder(namedType, propertyInfo.Property); + Builder = new GeneratableListBuilder(namedType, Name); return genericResult; } - private string ConvertToNullableIfRequired(PropertyInformation propertyInfo, ITypeSymbol typeSymbol) + private (string, bool) ConvertToNullableIfRequired(ITypeSymbol typeSymbol) { (bool isGeneratable, string genericTypeParam) = GetPropertyTypeName(typeSymbol); + bool isConvertedToNullableType = false; if (typeSymbol.IsValueType && typeSymbol.SpecialType != SpecialType.System_Nullable_T && typeSymbol.NullableAnnotation != NullableAnnotation.Annotated) { - propertyInfo.IsConvertedToNullableType = true; + isConvertedToNullableType = true; genericTypeParam = $"System.Nullable<{genericTypeParam}>"; } if (!typeSymbol.IsValueType && typeSymbol.SpecialType != SpecialType.System_Nullable_T) @@ -146,11 +97,11 @@ private string ConvertToNullableIfRequired(PropertyInformation propertyInfo, ITy } if (isGeneratable) - propertyInfo.Builder = new SimpleGeneratableBuilder(propertyInfo.Property); + Builder = new SimpleGeneratableBuilder(Name); else - propertyInfo.Builder = new SimpleNonGeneratableBuilder(propertyInfo.Property, propertyInfo.IsConvertedToNullableType); + Builder = new SimpleNonGeneratableBuilder(Name, isConvertedToNullableType); - return genericTypeParam; + return (genericTypeParam, isConvertedToNullableType); } private (bool IsGeneratedType, string TypeName) GetPropertyTypeName(ITypeSymbol propertyTypeSymbol) @@ -161,6 +112,150 @@ private string ConvertToNullableIfRequired(PropertyInformation propertyInfo, ITy } return (false, propertyTypeSymbol.ToDisplayString(GeneratedTypeFilter.SymbolFormat)); } +} + +public record GeneratorClassInfo +{ + public static GeneratorClassInfo Default => new GeneratorClassInfo(); + + private GeneratorClassInfo() + { + Name = string.Empty; + FullTypeName = string.Empty; + NamespaceExtension = string.Empty; + SourceTypeName = string.Empty; + Namespace = string.Empty; + AssemblyName = string.Empty; + Properties = []; + Attributes = []; + } + + public GeneratorClassInfo(ITypeSymbol typeSymbol, string sourceTypeName, string assemblyName = "") + { + Name = NameBuilder.GetName(typeSymbol); + FullTypeName = NameBuilder.GetFullTypeName(typeSymbol); + NamespaceExtension = NameBuilder.GetNamespaceExtension(typeSymbol); + HasDefaultCtor = HasDefaultConstructor(typeSymbol); + SourceTypeName = sourceTypeName; + AssemblyName = assemblyName; + Properties = ReadProperties(typeSymbol); + Attributes = ReadAttributes(typeSymbol).ToList(); + Namespace = NameBuilder.GetNamespace(typeSymbol); + CtorParameterLength = typeSymbol.GetMembers() + .OfType() + .Where(x => x.MethodKind == MethodKind.Constructor) + .OrderByDescending(x => x.Parameters.Length).FirstOrDefault()?.Parameters.Count() ?? 0; + } + + public string Name { get; } + + public string FullTypeName { get; } + + public string NamespaceExtension { get; } + + public string Namespace { get; } + + public bool HasDefaultCtor { get; } + + public string SourceTypeName { get; } + public string AssemblyName { get; } + public int CtorParameterLength { get; } + + public List Properties { get; } + + public List Attributes { get; } + + private bool HasDefaultConstructor(ITypeSymbol typeSymbol) => + typeSymbol.GetMembers().OfType().Where(x => x.MethodKind == MethodKind.Constructor).AnyOrNull(x => x.Parameters.IsEmpty); + + private List ReadProperties(ITypeSymbol typeSymbol) + { + var properties = new List(); + var currentType = typeSymbol; + while (currentType != null && currentType.Name != "Object") + { + properties.AddRange(currentType.GetMembers().OfType() + .Where(x => !x.IsReadOnly && !x.IsWriteOnly && !x.IsIndexer && !x.IsStatic && !x.IsAbstract && !x.IsVirtual) + .Select(x => new PropertyInfo(x))); + currentType = currentType.BaseType; + } + return properties; + } + + private IEnumerable ReadAttributes(ITypeSymbol typeSymbol) + { + return typeSymbol.GetAttributes().Where(attribute => + attribute.AttributeClass?.ToDisplayString() != "System.Runtime.CompilerServices.NullableContextAttribute" + && attribute.AttributeClass?.ToDisplayString() != "System.Runtime.CompilerServices.NullableAttribute" + && attribute.AttributeClass?.ToDisplayString() != "LaDeak.JsonMergePatch.Abstractions.PatchableAttribute"); + } +} + +public class TypeBuilder : ITypeBuilder +{ + public GeneratedWrapper BuildWrapperType(GeneratorClassInfo typeInfo) + { + var name = typeInfo.Name; + var state = new BuilderState(typeInfo); + BuildFile(state); + return new GeneratedWrapper() + { + FileName = $"LaDeakJsonMergePatch{typeInfo.NamespaceExtension}{name}", + SourceCode = state.Builder.ToString(), + }; + } + + private void BuildFile(BuilderState state) => BuildNamespace(state, BuildClass); + + private void BuildClass(BuilderState state) => BuildClassDeclaration(state, s => BuildClassBody(s)); + + private void BuildNamespace(BuilderState state, Action? addBody = null) + { + state.AppendLine($"#nullable enable"); + state.AppendLine($"namespace {state.TypeInfo.Namespace}"); + state.AppendLine("{"); + addBody?.Invoke(state.IncrementIdentation()); + state.AppendLine("}"); + state.AppendLine($"#nullable disable"); + } + + private void BuildClassDeclaration(BuilderState state, Action? addBody = null) + { + BuildAttributes(state, state.TypeInfo.Attributes); + state.AppendLine($"public class {state.TypeInfo.Name} : LaDeak.JsonMergePatch.Abstractions.Patch<{state.TypeInfo.SourceTypeName}>"); + state.AppendLine("{"); + addBody?.Invoke(state.IncrementIdentation()); + state.AppendLine("}"); + } + + private void BuildConstructor(BuilderState state, Action? addBody = null) + { + state.AppendLine($"public {state.TypeInfo.Name}()"); + state.AppendLine("{"); + var innerState = state.IncrementIdentation(); + innerState.AppendLine($"Properties = new bool[{state.TypeInfo.Properties.Count}];"); + addBody?.Invoke(innerState); + state.AppendLine("}"); + } + + private void BuildPropery(BuilderState state, PropertyInfo propertyInfo, int propertyId) + { + string fieldName = Casing.PrefixUnderscoreCamelCase(propertyInfo.Name); + string propertyTypeName = propertyInfo.PropertyTypeName; + state.AppendLine($"private {propertyTypeName} {fieldName};"); + BuildAttributes(state, propertyInfo.Attributes); + state.AppendLine($"public {propertyTypeName} {propertyInfo.Name}"); + state.AppendLine("{"); + var getterSetter = state.IncrementIdentation(); + getterSetter.AppendLine($"get {{ return {fieldName}; }}"); + getterSetter.AppendLine("set"); + getterSetter.AppendLine("{"); + var setterBody = getterSetter.IncrementIdentation(); + setterBody.AppendLine($"Properties[{propertyId}] = true;"); + setterBody.AppendLine($"{fieldName} = value;"); + getterSetter.AppendLine("}"); + state.AppendLine("}"); + } private void BuildAttributes(BuilderState state, IEnumerable attributes) { @@ -205,7 +300,7 @@ private void BuildApplyPath(BuilderState state) private void SetInitOnlyProperties(BuilderState state) { - if (!state.TypeInfo.Properties.Any(x => IsInitOnlyProperty(x.Property))) + if (!state.TypeInfo.Properties.Any(x => x.IsInitOnly)) return; CallConstructIfEmpty(state, "var tmp =", leaveOpen: true); state.AppendLine("{"); @@ -213,14 +308,14 @@ private void SetInitOnlyProperties(BuilderState state) for (int i = 0; i < state.TypeInfo.Properties.Count; i++) { var currentProperty = state.TypeInfo.Properties[i]; - if (IsInitOnlyProperty(currentProperty.Property)) + if (currentProperty.IsInitOnly) { currentProperty.Builder?.BuildInitOnly(state, i); } else { // Copy old property values onto the new object - initializerState.AppendLine($"{currentProperty.Property.Name} = input.{currentProperty.Property.Name},"); + initializerState.AppendLine($"{currentProperty.Name} = input.{currentProperty.Name},"); } } state.AppendLine("};"); @@ -232,7 +327,7 @@ private void SetReadWriteProperties(BuilderState state) for (int i = 0; i < state.TypeInfo.Properties.Count; i++) { var currentProperty = state.TypeInfo.Properties[i]; - if (!IsInitOnlyProperty(currentProperty.Property)) + if (!currentProperty.IsInitOnly) currentProperty.Builder?.BuildInstantiation(state, i); } } @@ -246,25 +341,12 @@ private static void BuildApplyPatchEnumerations(BuilderState state) } } - private bool IsInitOnlyProperty(IPropertySymbol propertySymbol) - { - return propertySymbol.SetMethod?.OriginalDefinition.IsInitOnly ?? false; - } - - private bool HasDefaultConstructor(ITypeSymbol typeSymbol) - { - return typeSymbol.GetMembers().OfType().Where(x => x.MethodKind == MethodKind.Constructor).AnyOrNull(x => x.Parameters.IsEmpty); - } - private void CallConstructIfEmpty(BuilderState state, string toInitialize, bool leaveOpen) { IEnumerable parameters = Enumerable.Empty(); - if (!HasDefaultConstructor(state.TypeInfo.TypeSymbol)) + if (!state.TypeInfo.HasDefaultCtor) { - var typeSymbol = state.TypeInfo.TypeSymbol; - var ctor = typeSymbol.GetMembers().OfType().Where(x => x.MethodKind == MethodKind.Constructor).OrderByDescending(x => x.Parameters.Length).First(); - var properties = state.TypeInfo.Properties.Select(x => x.Property).ToList(); - parameters = Enumerable.Repeat("default", ctor.Parameters.Length); + parameters = Enumerable.Repeat("default", state.TypeInfo.CtorParameterLength); } var ending = leaveOpen ? "" : ";"; state.AppendLine($"{toInitialize} new {state.TypeInfo.SourceTypeName}({string.Join(", ", parameters)}){ending}"); diff --git a/src/JsonMergePatch.SourceGenerator/TypeInformation.cs b/src/JsonMergePatch.SourceGenerator/TypeInformation.cs deleted file mode 100644 index 951fe14..0000000 --- a/src/JsonMergePatch.SourceGenerator/TypeInformation.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using LaDeak.JsonMergePatch.SourceGenerator.ApplyPatchBuilders; -using Microsoft.CodeAnalysis; - -namespace LaDeak.JsonMergePatch.SourceGenerator; - -public class TypeInformation -{ - public string? Name { get; set; } - public string? SourceTypeName { get; set; } - public List Properties { get; } = new List(); - public ITypeSymbol? TypeSymbol { get; set; } -} - -public class PropertyInformation -{ - public IPropertySymbol? Property { get; set; } - public bool IsConvertedToNullableType { get; set; } - public ApplyPatchBuilder? Builder { get; set; } -} diff --git a/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs b/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs index 07966d2..7e6bd96 100644 --- a/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs +++ b/src/JsonMergePatch.SourceGenerator/TypeRepositoryGenerator.cs @@ -1,16 +1,14 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Text; -namespace LaDeak.JsonMergePatch.SourceGenerator +namespace LaDeak.JsonMergePatch.SourceGenerator; + +public class TypeRepositoryGenerator { - public class TypeRepositoryGenerator + public string CreateTypeRepository(IEnumerable<(string, string)> typeRegistrations, string assemblyName) { - public string CreateTypeRepository(IEnumerable<(string, string)> typeRegistrations, string assemblyName) - { - StringBuilder sb = new StringBuilder(@$" + StringBuilder sb = new StringBuilder(@$" namespace {NameBuilder.GetNamespace(assemblyName)}"); - sb.Append(@" + sb.Append(@" { public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository { @@ -19,10 +17,10 @@ public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository private TypeRepository() { "); - foreach ((var originalType, var generatedType) in typeRegistrations ?? Enumerable.Empty<(string, string)>()) - sb.AppendLine($" Add<{originalType}, {generatedType}>();"); + foreach ((var originalType, var generatedType) in typeRegistrations ?? Enumerable.Empty<(string, string)>()) + sb.AppendLine($" Add<{originalType}, {generatedType}>();"); - sb.Append(@" + sb.Append(@" } public static LaDeak.JsonMergePatch.Abstractions.ITypeRepository Instance { get; } = new TypeRepository(); @@ -46,8 +44,7 @@ public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullW public System.Collections.Generic.IEnumerable> GetAll() => _repository; } }"); - return sb.ToString(); - } + return sb.ToString(); } } diff --git a/test/JsonMergePatch.Http.Tests/HttpContentExtensionsTests.cs b/test/JsonMergePatch.Http.Tests/HttpContentExtensionsTests.cs index 7a20006..52bed45 100644 --- a/test/JsonMergePatch.Http.Tests/HttpContentExtensionsTests.cs +++ b/test/JsonMergePatch.Http.Tests/HttpContentExtensionsTests.cs @@ -1,9 +1,6 @@ -using System; -using System.Net.Http; -using System.Net.Http.Headers; +using System.Net.Http.Headers; using System.Text; using System.Text.Json; -using System.Threading.Tasks; using LaDeak.JsonMergePatch.Abstractions; using NSubstitute; using Xunit; @@ -16,14 +13,14 @@ public class HttpContentExtensionsTests public async Task NullContent_ReadJsonPatchAsync_ThrowsArgumentNullException() { HttpContent content = null; - await Assert.ThrowsAsync(() => content.ReadJsonPatchAsync()).ConfigureAwait(false); + await Assert.ThrowsAsync(() => content.ReadJsonPatchAsync()); } [Fact] public async Task NullRepository_ReadJsonPatchAsync_ThrowsArgumentNullException() { var content = Substitute.For(); - await Assert.ThrowsAsync(() => content.ReadJsonPatchAsync(typeRepository: null)).ConfigureAwait(false); + await Assert.ThrowsAsync(() => content.ReadJsonPatchAsync(typeRepository: null)); } [Fact] @@ -32,7 +29,7 @@ public async Task NoRegistrationInRepository_ReadJsonPatchAsync_ThrowsArgumentEx var content = Substitute.For(); var typeRepository = Substitute.For(); typeRepository.TryGet(typeof(TestDto), out Arg.Any()).Returns(false); - await Assert.ThrowsAsync(() => content.ReadJsonPatchAsync(typeRepository)).ConfigureAwait(false); + await Assert.ThrowsAsync(() => content.ReadJsonPatchAsync(typeRepository)); } [Fact] @@ -42,7 +39,7 @@ public async Task NonMergePatchJsonContentType_ReadJsonPatchAsync_ReturnsNull() content.Headers.ContentType = new MediaTypeHeaderValue("application/not-merge-patch+json"); var typeRepository = Substitute.For(); typeRepository.TryGet(typeof(TestDto), out Arg.Any()).Returns(callInfo => { callInfo[1] = typeof(TestDtoWrapped); return true; }); - var result = await content.ReadJsonPatchAsync(typeRepository).ConfigureAwait(false); + var result = await content.ReadJsonPatchAsync(typeRepository); Assert.Null(result); } @@ -53,7 +50,7 @@ public async Task InvalidEncoding_ReadJsonPatchAsync_ThrowsInvalidOperationExcep content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/merge-patch+json; charset=Invalid"); var typeRepository = Substitute.For(); typeRepository.TryGet(typeof(TestDto), out Arg.Any()).Returns(callInfo => { callInfo[1] = typeof(TestDtoWrapped); return true; }); - await Assert.ThrowsAsync(() => content.ReadJsonPatchAsync(typeRepository)).ConfigureAwait(false); + await Assert.ThrowsAsync(() => content.ReadJsonPatchAsync(typeRepository)); } [Fact] @@ -63,7 +60,7 @@ public async Task EmptyEncoding_ReadJsonPatchAsync_UsesUtf8Encoding() content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/merge-patch+json"); var typeRepository = Substitute.For(); typeRepository.TryGet(typeof(TestDto), out Arg.Any()).Returns(callInfo => { callInfo[1] = typeof(TestDtoWrapped); return true; }); - var result = await content.ReadJsonPatchAsync(typeRepository).ConfigureAwait(false); + var result = await content.ReadJsonPatchAsync(typeRepository); Assert.NotNull(result); Assert.Equal(1, result.ApplyOnDefault().Prop1); } @@ -75,7 +72,7 @@ public async Task Utf32Encoding_ReadJsonPatchAsync_UsesUtf32Encoding() content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/merge-patch+json; charset=utf-32"); var typeRepository = Substitute.For(); typeRepository.TryGet(typeof(TestDto), out Arg.Any()).Returns(callInfo => { callInfo[1] = typeof(TestDtoWrapped); return true; }); - var result = await content.ReadJsonPatchAsync(typeRepository).ConfigureAwait(false); + var result = await content.ReadJsonPatchAsync(typeRepository); Assert.NotNull(result); Assert.Equal(1, result.ApplyOnDefault().Prop1); } @@ -87,7 +84,7 @@ public async Task DoubleQuotesInCharset_ReadJsonPatchAsync_IgnoredWhenParsingEnc content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/merge-patch+json; charset=\"utf-32\""); var typeRepository = Substitute.For(); typeRepository.TryGet(typeof(TestDto), out Arg.Any()).Returns(callInfo => { callInfo[1] = typeof(TestDtoWrapped); return true; }); - var result = await content.ReadJsonPatchAsync(typeRepository).ConfigureAwait(false); + var result = await content.ReadJsonPatchAsync(typeRepository); Assert.NotNull(result); Assert.Equal(1, result.ApplyOnDefault().Prop1); } diff --git a/test/JsonMergePatch.Http.Tests/JsonMergePatch.Http.Tests.csproj b/test/JsonMergePatch.Http.Tests/JsonMergePatch.Http.Tests.csproj index 4978aab..4db833f 100644 --- a/test/JsonMergePatch.Http.Tests/JsonMergePatch.Http.Tests.csproj +++ b/test/JsonMergePatch.Http.Tests/JsonMergePatch.Http.Tests.csproj @@ -1,16 +1,17 @@ - net8.0 + net9.0 false LaDeak.JsonMergePatch.Http.Tests + true - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/JsonMergePatch.Http.Tests/TestDto.cs b/test/JsonMergePatch.Http.Tests/TestDto.cs index 8744788..379173f 100644 --- a/test/JsonMergePatch.Http.Tests/TestDto.cs +++ b/test/JsonMergePatch.Http.Tests/TestDto.cs @@ -1,7 +1,6 @@ -namespace LaDeak.JsonMergePatch.Http.Tests +namespace LaDeak.JsonMergePatch.Http.Tests; + +public class TestDto { - public class TestDto - { - public int Prop1 { get; set; } - } + public int Prop1 { get; set; } } diff --git a/test/JsonMergePatch.Http.Tests/TestDtoWrapped.cs b/test/JsonMergePatch.Http.Tests/TestDtoWrapped.cs index b2d37fe..11f6739 100644 --- a/test/JsonMergePatch.Http.Tests/TestDtoWrapped.cs +++ b/test/JsonMergePatch.Http.Tests/TestDtoWrapped.cs @@ -1,23 +1,22 @@ using LaDeak.JsonMergePatch.Abstractions; -namespace LaDeak.JsonMergePatch.Http.Tests +namespace LaDeak.JsonMergePatch.Http.Tests; + +public class TestDtoWrapped : Patch { - public class TestDtoWrapped : Patch + public TestDtoWrapped() { - public TestDtoWrapped() - { - Properties = new bool[1]; - } + Properties = new bool[1]; + } - private int? _prop1; - public int? Prop1 { get => _prop1; set { Properties[0] = true; _prop1 = value; } } + private int? _prop1; + public int? Prop1 { get => _prop1; set { Properties[0] = true; _prop1 = value; } } - public override TestDto ApplyPatch(TestDto input) - { - input ??= new(); - if (Properties[0]) - input.Prop1 = Prop1.HasValue ? Prop1.Value : default; - return input; - } + public override TestDto ApplyPatch(TestDto input) + { + input ??= new(); + if (Properties[0]) + input.Prop1 = Prop1.HasValue ? Prop1.Value : default; + return input; } } diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BasicSerializationTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BasicSerializationTests.cs index dbe3bdc..c5f7367 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BasicSerializationTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BasicSerializationTests.cs @@ -119,12 +119,16 @@ public BasicSerializationData() private readonly string _classesWithReadWriteProperties = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class SubDto { public System.Int32 NumberProp { get; set; } public System.DateTime? NullableDateTimeProperty { get; set; } [System.Text.Json.Serialization.JsonPropertyNameAttribute(""camelCaseProperty"")] public System.Double CamelCaseProperty { get; set; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class ParentDto { public System.String ParentStringProperty { get; set; } public SubDto OtherDto { get; set; } public System.Collections.Generic.IEnumerable Values { get; set; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class SomeBase { public System.Int32 NumberPropBase { get; set; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class SomeDerived : SomeBase { public System.String SomeDerivedProp { get; set; } } public class Program @@ -143,8 +147,10 @@ public void SomeMethod2(LaDeak.JsonMergePatch.Abstractions.Patch da private readonly string _classesWithInitOnlyProperties = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class SubDto { public System.Int32 NumberProp { get; init; } public System.DateTime? NullableDateTimeProperty { get; init; } [System.Text.Json.Serialization.JsonPropertyNameAttribute(""camelCaseProperty"")] public System.Double CamelCaseProperty { get; init; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class ParentDto { public System.String ParentStringProperty { get; init; } public SubDto OtherDto { get; init; } public System.Collections.Generic.IEnumerable Values { get; init; } } public class Program @@ -159,8 +165,10 @@ public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) private readonly string _recordsWithInitOnlyProperties = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record SubDto { public System.Int32 NumberProp { get; init; } public System.DateTime? NullableDateTimeProperty { get; init; } [System.Text.Json.Serialization.JsonPropertyNameAttribute(""camelCaseProperty"")] public System.Double CamelCaseProperty { get; init; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record ParentDto { public System.String ParentStringProperty { get; init; } public SubDto OtherDto { get; init; } public System.Collections.Generic.IEnumerable Values { get; init; } } public class Program @@ -175,8 +183,10 @@ public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) private readonly string _vanillaRecordTypesProperties = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record SubDto(System.Int32 NumberProp, System.DateTime? NullableDateTimeProperty, System.Double CamelCaseProperty); + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record ParentDto(System.String ParentStringProperty, SubDto OtherDto, System.Collections.Generic.IEnumerable Values); public class Program diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BuilderStateTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BuilderStateTests.cs index c8cc7eb..5368e9b 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BuilderStateTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/BuilderStateTests.cs @@ -1,83 +1,70 @@ using System; using Xunit; -namespace LaDeak.JsonMergePatch.SourceGenerator.Tests +namespace LaDeak.JsonMergePatch.SourceGenerator.Tests; + +public class BuilderStateTests { - public class BuilderStateTests + [Fact] + public void Increment_Increases_Identation() { - [Fact] - public void NewBuilder_Initialized() - { - var typeInfo = new TypeInformation(); - var sut = new BuilderState(typeInfo); - Assert.Same(typeInfo, sut.TypeInfo); - Assert.Equal(0, sut.Identation); - Assert.Empty(sut.ToProcessTypeSymbols); - Assert.NotNull(sut.Builder); - } - - [Fact] - public void Increment_Increases_Identation() - { - var sut = new BuilderState(new TypeInformation()); - var state = sut.IncrementIdentation(); - Assert.Equal(0, sut.Identation); - Assert.Equal(4, state.Identation); - } - - [Fact] - public void Increment_DoesNotChange_TypeInformation_Builder_ToProcessTypeSymbols() - { - var sut = new BuilderState(new TypeInformation()); - var state = sut.IncrementIdentation(); - Assert.Same(sut.Builder, state.Builder); - Assert.Same(sut.TypeInfo, state.TypeInfo); - Assert.Same(sut.ToProcessTypeSymbols, state.ToProcessTypeSymbols); - } + var sut = new BuilderState(GeneratorClassInfo.Default); + var state = sut.IncrementIdentation(); + Assert.Equal(0, sut.Identation); + Assert.Equal(4, state.Identation); + } - [Fact] - public void AppendLine_Adds_EmptyLine() - { - var sut = new BuilderState(new TypeInformation()); - sut.AppendLine(); - Assert.Equal(Environment.NewLine, sut.Builder.ToString()); - } + [Fact] + public void Increment_DoesNotChange_GeneratorClassInfo_Builder_ToProcessTypeSymbols() + { + var sut = new BuilderState(GeneratorClassInfo.Default); + var state = sut.IncrementIdentation(); + Assert.Same(sut.Builder, state.Builder); + Assert.Same(sut.TypeInfo, state.TypeInfo); + } - [Fact] - public void AppendLineWithText_Adds_Text() - { - var sut = new BuilderState(new TypeInformation()); - sut.AppendLine("hello"); - Assert.Equal($"hello{Environment.NewLine}", sut.Builder.ToString()); - } + [Fact] + public void AppendLine_Adds_EmptyLine() + { + var sut = new BuilderState(GeneratorClassInfo.Default); + sut.AppendLine(); + Assert.Equal(Environment.NewLine, sut.Builder.ToString()); + } - [Fact] - public void AppendLineWithText_Adds_IdentationSpacesAndText() - { - var baseIdentation = new BuilderState(new TypeInformation()); - var sut = baseIdentation.IncrementIdentation(); - sut.AppendLine("world"); - Assert.Equal($" world{Environment.NewLine}", sut.Builder.ToString()); - } + [Fact] + public void AppendLineWithText_Adds_Text() + { + var sut = new BuilderState(GeneratorClassInfo.Default); + sut.AppendLine("hello"); + Assert.Equal($"hello{Environment.NewLine}", sut.Builder.ToString()); + } - [Fact] - public void Builder_AppendsLine_BasedOnItsIdentation() - { - var baseIdentation = new BuilderState(new TypeInformation()); - baseIdentation.AppendLine("hello"); - var sut = baseIdentation.IncrementIdentation(); - sut.AppendLine("world"); - baseIdentation.AppendLine("!"); - Assert.Equal($"hello{Environment.NewLine} world{Environment.NewLine}!{Environment.NewLine}", sut.Builder.ToString()); - } + [Fact] + public void AppendLineWithText_Adds_IdentationSpacesAndText() + { + var baseIdentation = new BuilderState(GeneratorClassInfo.Default); + var sut = baseIdentation.IncrementIdentation(); + sut.AppendLine("world"); + Assert.Equal($" world{Environment.NewLine}", sut.Builder.ToString()); + } - [Fact] - public void DoubleIncrement_Adds_DoubleIdentation() - { - var sut = new BuilderState(new TypeInformation()).IncrementIdentation().IncrementIdentation(); - sut.AppendLine("hello"); - Assert.Equal($" hello{Environment.NewLine}", sut.Builder.ToString()); - } + [Fact] + public void Builder_AppendsLine_BasedOnItsIdentation() + { + var baseIdentation = new BuilderState(GeneratorClassInfo.Default); + baseIdentation.AppendLine("hello"); + var sut = baseIdentation.IncrementIdentation(); + sut.AppendLine("world"); + baseIdentation.AppendLine("!"); + Assert.Equal($"hello{Environment.NewLine} world{Environment.NewLine}!{Environment.NewLine}", sut.Builder.ToString()); + } + [Fact] + public void DoubleIncrement_Adds_DoubleIdentation() + { + var sut = new BuilderState(GeneratorClassInfo.Default).IncrementIdentation().IncrementIdentation(); + sut.AppendLine("hello"); + Assert.Equal($" hello{Environment.NewLine}", sut.Builder.ToString()); } + } diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/DictionarySerializationTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/DictionarySerializationTests.cs index fdc3fce..d1fcf31 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/DictionarySerializationTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/DictionarySerializationTests.cs @@ -64,6 +64,7 @@ public class DictionarySerializationData : IEnumerable private const string NullableType = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public System.Collections.Generic.Dictionary Values { get; set; } } public class Program @@ -78,6 +79,7 @@ public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) private const string ValueType = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public System.Collections.Generic.Dictionary Values { get; set; } } public class Program @@ -93,6 +95,7 @@ public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) private const string ReferenceType = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public System.Collections.Generic.Dictionary Values { get; set; } } public class Program @@ -107,6 +110,7 @@ public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) private const string NullableReferenceType = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public System.Collections.Generic.Dictionary Values { get; set; } } public class Program @@ -121,6 +125,7 @@ public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) private const string RecordWithDictionaryType = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record Dto { public System.Collections.Generic.Dictionary Values { get; set; } } public class Program @@ -135,6 +140,7 @@ public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) private const string InitOnlyDictionaryOnRecordType = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record Dto { public System.Collections.Generic.Dictionary Values { get; init; } } public class Program @@ -149,6 +155,7 @@ public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) private const string InitOnlyDictionaryOnClassType = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public System.Collections.Generic.Dictionary Values { get; init; } } public class Program diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/GeneratedWrapperTypeTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/GeneratedWrapperTypeTests.cs index d2b2a0e..41926ec 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/GeneratedWrapperTypeTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/GeneratedWrapperTypeTests.cs @@ -171,8 +171,10 @@ private static (Compilation Input, Compilation Output) CreateInputOutputCompilat Compilation inputCompilation = CreateCompilation(@" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto1 { public System.Int32 NumberProp { get; set; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto2 { public System.String Property { get; set; } public Dto1 OtherDto { get; set; } } public class Program2 diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/IEnumerableExtensionsTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/IEnumerableExtensionsTests.cs index af3061a..8fcba6a 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/IEnumerableExtensionsTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/IEnumerableExtensionsTests.cs @@ -2,58 +2,57 @@ using System.Linq; using Xunit; -namespace LaDeak.JsonMergePatch.SourceGenerator.Tests +namespace LaDeak.JsonMergePatch.SourceGenerator.Tests; + +public class IEnumerableExtensionsTests { - public class IEnumerableExtensionsTests + [Fact] + public void Null_AnyOrNull_ReturnsTrue() + { + IEnumerable source = null; + Assert.True(source.AnyOrNull(x => x == 0)); + } + + [Fact] + public void Empty_AnyOrNull_ReturnsTrue() + { + IEnumerable source = Enumerable.Empty(); + Assert.True(source.AnyOrNull(x => x == 0)); + } + + [Fact] + public void FalsePredicate_AnyOrNull_ReturnsFalse() { - [Fact] - public void Null_AnyOrNull_ReturnsTrue() - { - IEnumerable source = null; - Assert.True(source.AnyOrNull(x => x == 0)); - } - - [Fact] - public void Empty_AnyOrNull_ReturnsTrue() - { - IEnumerable source = Enumerable.Empty(); - Assert.True(source.AnyOrNull(x => x == 0)); - } - - [Fact] - public void FalsePredicate_AnyOrNull_ReturnsFalse() - { - IEnumerable source = new[] { 1, 2, 3 }; - Assert.False(source.AnyOrNull(x => x == 0)); - } - - [Fact] - public void TruePredicateFirst_AnyOrNull_ReturnsTrue() - { - IEnumerable source = new[] { 0, 1, 2 }; - Assert.True(source.AnyOrNull(x => x == 0)); - } - - [Fact] - public void TruePredicateLast_AnyOrNull_ReturnsTrue() - { - IEnumerable source = new[] { 1, 2, 0 }; - Assert.True(source.AnyOrNull(x => x == 0)); - } - - [Fact] - public void TruePredicateMiddle_AnyOrNull_ReturnsTrue() - { - IEnumerable source = new[] { 1, 0, 2 }; - Assert.True(source.AnyOrNull(x => x == 0)); - } - - [Fact] - public void WithNull_AnyOrNull_ReturnsTrue() - { - IEnumerable source = new[] { null, "hello" }; - Assert.True(source.AnyOrNull(x => x == "hello")); - } + IEnumerable source = new[] { 1, 2, 3 }; + Assert.False(source.AnyOrNull(x => x == 0)); + } + [Fact] + public void TruePredicateFirst_AnyOrNull_ReturnsTrue() + { + IEnumerable source = new[] { 0, 1, 2 }; + Assert.True(source.AnyOrNull(x => x == 0)); } + + [Fact] + public void TruePredicateLast_AnyOrNull_ReturnsTrue() + { + IEnumerable source = new[] { 1, 2, 0 }; + Assert.True(source.AnyOrNull(x => x == 0)); + } + + [Fact] + public void TruePredicateMiddle_AnyOrNull_ReturnsTrue() + { + IEnumerable source = new[] { 1, 0, 2 }; + Assert.True(source.AnyOrNull(x => x == 0)); + } + + [Fact] + public void WithNull_AnyOrNull_ReturnsTrue() + { + IEnumerable source = new[] { null, "hello" }; + Assert.True(source.AnyOrNull(x => x == "hello")); + } + } diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/JsonMergePatch.SourceGenerator.Tests.csproj b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/JsonMergePatch.SourceGenerator.Tests.csproj index a054728..574c44f 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/JsonMergePatch.SourceGenerator.Tests.csproj +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/JsonMergePatch.SourceGenerator.Tests.csproj @@ -1,18 +1,18 @@ - net8.0 + net9.0 false LaDeak.JsonMergePatch.SourceGenerator.Tests - - - + + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/JsonMergePatchSourceGeneratorTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/JsonMergePatchSourceGeneratorTests.cs index 4d3cbed..1ee7e77 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/JsonMergePatchSourceGeneratorTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/JsonMergePatchSourceGeneratorTests.cs @@ -17,6 +17,7 @@ public void SinglePatchType_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public int Property { get; set; } } public class Program { @@ -45,7 +46,9 @@ public void MultiplePatchType_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode2 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto0 { public double Property { get; set; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto1 { public Dto0 Property { get; set; } } public class Program @@ -80,6 +83,7 @@ public void DtoWithProperties_WrapperTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode3 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto4 { public System.String Property { get; set; } } public class Program2 @@ -106,6 +110,7 @@ public void RecordType_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record Dto { public int Property { get; set; } } public class Program { @@ -135,6 +140,7 @@ public void RecordTypeWithInitValueProperty_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record Dto { public int Property { get; set; } } public class Program { @@ -163,7 +169,9 @@ public void MultiplePatchType_WithInitProperties_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode2 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record Dto0 { public double Property { get; set; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record Dto1 { public Dto0 Property { get; set; } } public class Program @@ -199,6 +207,7 @@ public void RecordTypeWithInitReferenceProperty_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record Dto { public string Property { get; set; } } public class Program { @@ -228,6 +237,7 @@ public void ClassTypeWithInitOnlyValueProperty_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public int Property { get; set; } } public class Program { @@ -256,7 +266,9 @@ public void MultipleClassTypeType_WithInitProperties_ExtensionAndTypeAddedToSour Compilation inputCompilation = CreateCompilation(@" namespace TestCode2 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto0 { public double Property { get; set; } } + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto1 { public Dto0 Property { get; set; } } public class Program @@ -292,6 +304,7 @@ public void ClassType_WithInitReferenceProperty_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public string Property { get; set; } } public class Program { @@ -321,6 +334,7 @@ public void ClassType_InitOnlyWithGetSetProperty_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public class Dto { public string Property { get; set; } public string Property1 { get; set; } } public class Program { @@ -350,6 +364,7 @@ public void DefaultRecordType_ExtensionAndTypeAddedToSource() Compilation inputCompilation = CreateCompilation(@" namespace TestCode1 { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record Dto(int Property0, string Property1); public class Program { diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/MultiTypeBuilderTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/MultiTypeBuilderTests.cs deleted file mode 100644 index e5ba8c1..0000000 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/MultiTypeBuilderTests.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using NSubstitute; -using Xunit; - -namespace LaDeak.JsonMergePatch.SourceGenerator.Tests -{ - public class MultiTypeBuilderTests - { - private const string SimpleTestCode = @" -namespace TestCode1 -{ - public class Dto { public int Property { get; set; } } - public class Program - { - public void SomeMethod(LaDeak.JsonMergePatch.Abstractions.Patch data) - { - } - } -} -"; - [Fact] - public void MissingDependency_AtConstruction_ThrowsArgumentNullException() - { - Compilation inputCompilation = CreateCompilation(SimpleTestCode); - Assert.Throws(() => new MultiTypeBuilder(null, inputCompilation, Substitute.For(), Substitute.For())); - Assert.Throws(() => new MultiTypeBuilder(new[] { CSharpSyntaxTree.ParseText(SimpleTestCode) }, null, Substitute.For(), Substitute.For())); - Assert.Throws(() => new MultiTypeBuilder(new[] { CSharpSyntaxTree.ParseText(SimpleTestCode) }, inputCompilation, null, Substitute.For())); - Assert.Throws(() => new MultiTypeBuilder(new[] { CSharpSyntaxTree.ParseText(SimpleTestCode) }, inputCompilation, Substitute.For(), null)); - } - - [Fact] - public void WithAllDependencies_AtConstruction_CreatesMultiTypeBuilder() - { - Compilation inputCompilation = CreateCompilation(SimpleTestCode); - var sut = new MultiTypeBuilder(new[] { CSharpSyntaxTree.ParseText(SimpleTestCode) }, inputCompilation, Substitute.For(), Substitute.For()); - } - - [Fact] - public void SimpleTestCode_Generate_ReturnsWrappedDtoType() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - walker.Process(Arg.Any(), Arg.Any()).Returns(new[] { Substitute.For() }); - var typeBuilder = Substitute.For(); - typeBuilder.BuildWrapperType(Arg.Any(), Arg.Any()).Returns(new GeneratedWrapper() { ToProcessTypes = new List() }); - var sut = new MultiTypeBuilder(new[] { tree }, inputCompilation, typeBuilder, walker); - - var results = sut.Generate(); - - Assert.Single(results); - } - - [Fact] - public void MultipleTypesFound_Generate_ReturnsWrappedForAllTypes() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - walker.Process(Arg.Any(), Arg.Any()).Returns(new[] { Substitute.For(), Substitute.For(), Substitute.For() }); - var typeBuilder = Substitute.For(); - typeBuilder.BuildWrapperType(Arg.Any(), Arg.Any()).Returns(new GeneratedWrapper() { ToProcessTypes = new List() }); - var sut = new MultiTypeBuilder(new[] { tree }, inputCompilation, typeBuilder, walker); - - var results = sut.Generate(); - - Assert.Equal(3, results.Count()); - } - - [Fact] - public void MultipleSyntaxTrees_Generate_ReturnsWrappedForAllTrees() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - walker.Process(Arg.Any(), Arg.Any()).Returns(new[] { Substitute.For() }); - var typeBuilder = Substitute.For(); - typeBuilder.BuildWrapperType(Arg.Any(), Arg.Any()).Returns(new GeneratedWrapper() { ToProcessTypes = new List() }); - var sut = new MultiTypeBuilder(new[] { tree, tree, tree }, inputCompilation, typeBuilder, walker); - - var results = sut.Generate(); - - Assert.Equal(3, results.Count()); - } - - [Fact] - public void GeneratedTypeReturnInnerType_Generate_ReturnsWrappedTypeForInner() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - walker.Process(Arg.Any(), Arg.Any()).Returns(new[] { Substitute.For() }); - var typeBuilder = Substitute.For(); - typeBuilder.BuildWrapperType(Arg.Any(), Arg.Any()).Returns(new GeneratedWrapper() { ToProcessTypes = new List() { Substitute.For() } }, new GeneratedWrapper() { ToProcessTypes = new List() }); - var sut = new MultiTypeBuilder(new[] { tree }, inputCompilation, typeBuilder, walker); - - var results = sut.Generate(); - - Assert.Equal(2, results.Count()); - } - - [Fact] - public void MultipleInnerType_Generate_ReturnsWrappedMultipleInnerType() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - walker.Process(Arg.Any(), Arg.Any()).Returns(new[] { Substitute.For() }); - var typeBuilder = Substitute.For(); - typeBuilder.BuildWrapperType(Arg.Any(), Arg.Any()) - .Returns( - new GeneratedWrapper() { ToProcessTypes = new List() { Substitute.For(), Substitute.For() } }, - new GeneratedWrapper() { ToProcessTypes = new List() }, - new GeneratedWrapper() { ToProcessTypes = new List() }); - var sut = new MultiTypeBuilder(new[] { tree }, inputCompilation, typeBuilder, walker); - - var results = sut.Generate(); - - Assert.Equal(3, results.Count()); - } - - [Fact] - public void NoSyntaxTree_Generate_ReturnsEmptyResults() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - var typeBuilder = Substitute.For(); - typeBuilder.BuildWrapperType(Arg.Any(), Arg.Any()).Returns(new GeneratedWrapper() { ToProcessTypes = new List() }); - var sut = new MultiTypeBuilder(Enumerable.Empty(), inputCompilation, typeBuilder, walker); - - var results = sut.Generate(); - - Assert.Empty(results); - walker.DidNotReceive().Process(Arg.Any(), Arg.Any()); - } - - [Fact] - public void WalkerReturnsNoFinding_Generate_ReturnsEmptyResults() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - walker.Process(Arg.Any(), Arg.Any()).Returns(Enumerable.Empty()); - var typeBuilder = Substitute.For(); - var sut = new MultiTypeBuilder(new[] { tree }, inputCompilation, typeBuilder, walker); - - var results = sut.Generate(); - - Assert.Empty(results); - typeBuilder.DidNotReceive().BuildWrapperType(Arg.Any(), Arg.Any()); - } - - [Fact] - public void MultipleGenerateCalls_ReturnsEqualResults() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - walker.Process(Arg.Any(), Arg.Any()).Returns(new[] { Substitute.For() }); - var typeBuilder = Substitute.For(); - typeBuilder.BuildWrapperType(Arg.Any(), Arg.Any()).Returns(new GeneratedWrapper() { ToProcessTypes = new List() }); - var sut = new MultiTypeBuilder(new[] { tree }, inputCompilation, typeBuilder, walker); - - var results0 = sut.Generate(); - var results1 = sut.Generate(); - - Assert.Equal(results0.Single(), results1.Single()); - } - - [Fact] - public void GeneratedWrappersSource_SameAsReturnedByTypeBuilder() - { - (Compilation inputCompilation, SyntaxTree tree) = SourceBuilder.Compile(SimpleTestCode); - var walker = Substitute.For(); - walker.Process(Arg.Any(), Arg.Any()).Returns(new[] { Substitute.For() }); - var typeBuilder = Substitute.For(); - var typeBuilderResult = new GeneratedWrapper() { ToProcessTypes = new List(), FileName = "file.cs", SourceCode = "namespace TestNameSpace { }", SourceTypeFullName = "Source.FullName", TargetTypeFullName = "Target.FullName" }; - typeBuilder.BuildWrapperType(Arg.Any(), Arg.Any()).Returns(typeBuilderResult); - var sut = new MultiTypeBuilder(new[] { tree }, inputCompilation, typeBuilder, walker); - - var result = sut.Generate().Single(); - - Assert.Equal(typeBuilderResult.FileName, result.FileName); - Assert.Equal(typeBuilderResult.SourceCode, result.SourceCode); - Assert.Equal(typeBuilderResult.SourceTypeFullName, result.SourceTypeFullName); - Assert.Equal(typeBuilderResult.TargetTypeFullName, result.TargetTypeFullName); - } - - private static Compilation CreateCompilation(string source) => SourceBuilder.Compile(source).Compilation; - - } -} diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/NameBuilderTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/NameBuilderTests.cs index 59ec057..27ee3cd 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/NameBuilderTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/NameBuilderTests.cs @@ -1,59 +1,53 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using NSubstitute; using Xunit; -namespace LaDeak.JsonMergePatch.SourceGenerator.Tests +namespace LaDeak.JsonMergePatch.SourceGenerator.Tests; + +public class NameBuilderTests { - public class NameBuilderTests + [Fact] + public void GetName_ReturnsTypeName_WithWrappedSuffix() { - [Fact] - public void GetName_ReturnsTypeName_WithWrappedSuffix() - { - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestName"); - var result = NameBuilder.GetName(typeSymbol); - Assert.Equal("TestNameWrapped", result); - } + 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 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 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 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); - } + [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); } } diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeBuilderTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeBuilderTests.cs index 7cd2bbe..1c7f947 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeBuilderTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeBuilderTests.cs @@ -1,88 +1,48 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using NSubstitute; using Xunit; -namespace LaDeak.JsonMergePatch.SourceGenerator.Tests +namespace LaDeak.JsonMergePatch.SourceGenerator.Tests; + +public class TypeBuilderTests { - public class TypeBuilderTests - { - [Fact] - public void BuildWrapperType_SourceTypeName_ReturnedInResultsSourceTypeName() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal("SourceName", result.SourceTypeFullName); - } - - [Fact] - public void BuildWrapperType_TargetTypeName_ReturnedTypeNameAndWrapped() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - typeSymbol.ContainingNamespace.ToDisplayString().Returns("TestTypeNamespace"); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal("LaDeak.JsonMergePatch.Generated.SafeTestTypeNamespace.TestTypeWrapped", result.TargetTypeFullName); - } - - [Fact] - public void BuildWrapperType_ToProcessTypes_NotNull() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.NotNull(result.ToProcessTypes); - } - - [Fact] - public void BuildWrapperType_SourceCode_NotNull() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.NotNull(result.SourceCode); - } - - [Fact] - public void BuildWrapperType_FileName_UsesTypeName() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal("LaDeakJsonMergePatchSafeTestTypeWrapped", result.FileName); - } - - [Fact] - public void EmptyType_Returns_EmptyTypeAndNamespace() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void BuildWrapperType_SourceCode_NotNull() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.NotNull(result.SourceCode); + } + + [Fact] + public void BuildWrapperType_FileName_UsesTypeName() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal("LaDeakJsonMergePatchSafeTestTypeWrapped", result.FileName); + } + + [Fact] + public void EmptyType_Returns_EmptyTypeAndNamespace() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -102,31 +62,31 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void TypeWithField_Returns_EmptyTypeAndNamespace() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var members = ImmutableArray.Create( - Substitute.For(), - Substitute.For(), - Substitute.For(), - Substitute.For(), - Substitute.For(), - Substitute.For(), - Substitute.For(), - Substitute.For(), - Substitute.For(), - Substitute.For()); - typeSymbol.GetMembers().Returns(members); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void TypeWithField_Returns_EmptyTypeAndNamespace() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var members = ImmutableArray.Create( + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For()); + typeSymbol.GetMembers().Returns(members); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -146,31 +106,31 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void ReadonlyWriteonlyStaticIndexerAbstract_Property_Ignored() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var readonlyProperty = Substitute.For(); - readonlyProperty.IsReadOnly.Returns(true); - var abstractProperty = Substitute.For(); - abstractProperty.IsAbstract.Returns(true); - var writeonlyProperty = Substitute.For(); - writeonlyProperty.IsWriteOnly.Returns(true); - var staticProperty = Substitute.For(); - staticProperty.IsStatic.Returns(true); - var indexerProperty = Substitute.For(); - indexerProperty.IsIndexer.Returns(true); - var members = ImmutableArray.Create(readonlyProperty, writeonlyProperty, staticProperty, indexerProperty, abstractProperty); - typeSymbol.GetMembers().Returns(members); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void ReadonlyWriteonlyStaticIndexerAbstract_Property_Ignored() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var readonlyProperty = Substitute.For(); + readonlyProperty.IsReadOnly.Returns(true); + var abstractProperty = Substitute.For(); + abstractProperty.IsAbstract.Returns(true); + var writeonlyProperty = Substitute.For(); + writeonlyProperty.IsWriteOnly.Returns(true); + var staticProperty = Substitute.For(); + staticProperty.IsStatic.Returns(true); + var indexerProperty = Substitute.For(); + indexerProperty.IsIndexer.Returns(true); + var members = ImmutableArray.Create(readonlyProperty, writeonlyProperty, staticProperty, indexerProperty, abstractProperty); + typeSymbol.GetMembers().Returns(members); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -190,19 +150,19 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void ClassAttribute_Added_ToGeneratedClass() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var attributes = ImmutableArray.Create(new TestAttribute(), new TestAttribute("Hello")); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void ClassAttribute_Added_ToGeneratedClass() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var attributes = ImmutableArray.Create(new TestAttribute(), new TestAttribute("Hello")); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -224,22 +184,22 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void ReadWriteProperty_Added_BackingFieldAndProperty() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property = GetProperty("System", "String", "TestProp"); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void ReadWriteProperty_Added_BackingFieldAndProperty() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property = GetProperty("System", "String", "TestProp"); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -272,24 +232,23 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - Assert.Contains(result.ToProcessTypes, x => x.ToDisplayString() == "System.String"); - } - - [Fact] - public void MultipleReadWriteProperty_Added_BackingFieldsAndProperties() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var prop0 = GetProperty("System", "String", "TestProp0"); - var prop1 = GetProperty("System", "Int32", "TestProp1"); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(prop0, prop1)); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( - @"#nullable enable + } + + [Fact] + public void MultipleReadWriteProperty_Added_BackingFieldsAndProperties() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var prop0 = GetProperty("System", "String", "TestProp0"); + var prop1 = GetProperty("System", "Int32", "TestProp1"); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(prop0, prop1)); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( +@"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch @@ -334,23 +293,23 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void MultipleReadWriteProperty_AddsTypeDataToProcessSymbols() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var prop0 = GetProperty("Test", "Dto", "TestProp0"); - var prop1 = GetProperty("System", "Int32", "TestProp1"); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(prop0, prop1)); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( - @"#nullable enable + [Fact] + public void MultipleReadWriteProperty_AddsTypeDataToProcessSymbols() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var prop0 = GetProperty("Test", "Dto", "TestProp0"); + var prop1 = GetProperty("System", "Int32", "TestProp1"); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(prop0, prop1)); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( +@"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { public class TestTypeWrapped : LaDeak.JsonMergePatch.Abstractions.Patch @@ -395,23 +354,21 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - Assert.Contains(result.ToProcessTypes, x => x.ToDisplayString() == "Test.Dto"); - Assert.Contains(result.ToProcessTypes, x => x.ToDisplayString() == "System.Int32"); - } - - [Fact] - public void PropertyWitAttribute_Adds_AttributeOnProperty() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property = GetProperty("System", "String", "TestProp", new TestAttribute("JsonPropertyName(\"temp\")")); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + } + + [Fact] + public void PropertyWitAttribute_Adds_AttributeOnProperty() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property = GetProperty("System", "String", "TestProp", new TestAttribute("JsonPropertyName(\"temp\")")); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -445,20 +402,20 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void DictionaryWithValueProperty_CreatesPatchToPatchEachValue() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp", GetType("System", "String"), GetType("System", "Int32")); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void DictionaryWithValueProperty_CreatesPatchToPatchEachValue() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp", GetType("System", "String"), GetType("System", "Int32")); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -501,21 +458,21 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void MultipleDictionaryProperty_CreatesPatchToPatchEachValue() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property0 = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp0", GetType("System", "String"), GetType("System", "Int32")); - var property1 = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp1", GetType("System", "String"), GetType("System", "Int32")); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property0, property1)); - typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void MultipleDictionaryProperty_CreatesPatchToPatchEachValue() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property0 = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp0", GetType("System", "String"), GetType("System", "Int32")); + var property1 = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp1", GetType("System", "String"), GetType("System", "Int32")); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property0, property1)); + typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -581,20 +538,20 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void DictionaryWithReferenceProperty_CreatesPatchToPatchEachValue() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp", GetType("System", "String"), GetType("System", "String")); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void DictionaryWithReferenceProperty_CreatesPatchToPatchEachValue() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp", GetType("System", "String"), GetType("System", "String")); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -637,20 +594,20 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void DictionaryWithNullableValueProperty_CreatesPatchToPatchEachValue() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp", GetType("System", "String"), GetNullableType(GetType("System", "Int32"))); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void DictionaryWithNullableValueProperty_CreatesPatchToPatchEachValue() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp", GetType("System", "String"), GetNullableType(GetType("System", "Int32"))); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -693,20 +650,20 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void DictionaryWithNullableReferenceProperty_CreatesPatchToPatchEachValue() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp", GetType("System", "String"), GetNullableReferenceType(GetType("System", "String"))); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void DictionaryWithNullableReferenceProperty_CreatesPatchToPatchEachValue() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property = GetGenericProperty("System.Collections.Generic", "Dictionary", "TestProp", GetType("System", "String"), GetNullableReferenceType(GetType("System", "String"))); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -749,27 +706,27 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } - - [Fact] - public void PropertiesInBaseTypeIncluded() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - var property = GetProperty("System", "String", "TestProp"); - - var baseTypeSymbol = Substitute.For(); - baseTypeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var propertyBase = GetProperty("System", "Int32", "TestPropBase"); - baseTypeSymbol.GetMembers().Returns(ImmutableArray.Create(propertyBase)); - typeSymbol.BaseType.Returns(baseTypeSymbol); + } - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - var attributes = ImmutableArray.Create(); - typeSymbol.GetAttributes().Returns(attributes); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void PropertiesInBaseTypeIncluded() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + var property = GetProperty("System", "String", "TestProp"); + + var baseTypeSymbol = Substitute.For(); + baseTypeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var propertyBase = GetProperty("System", "Int32", "TestPropBase"); + baseTypeSymbol.GetMembers().Returns(ImmutableArray.Create(propertyBase)); + typeSymbol.BaseType.Returns(baseTypeSymbol); + + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + var attributes = ImmutableArray.Create(); + typeSymbol.GetAttributes().Returns(attributes); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -815,21 +772,20 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - Assert.Contains(result.ToProcessTypes, x => x.ToDisplayString() == "System.String"); - } + } - [Fact] - public void ListOfComplexType_CreatesPatchToPatchEachValue() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property = GetGenericProperty("System.Collections.Generic", "List", "TestProp", GetType("Test", "Dto")); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void ListOfComplexType_CreatesPatchToPatchEachValue() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property = GetGenericProperty("System.Collections.Generic", "List", "TestProp", GetType("Test", "Dto")); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -869,20 +825,20 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - [Fact] - public void ListOfKnownType_CreatesPatchToPatchEachValue() - { - var sut = new TypeBuilder(); - var typeSymbol = Substitute.For(); - typeSymbol.Name.Returns("TestType"); - typeSymbol.BaseType.Returns((INamedTypeSymbol)null); - var property = GetGenericProperty("System.Collections.Generic", "List", "TestProp", GetType("System", "String")); - typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); - typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); - var result = sut.BuildWrapperType(typeSymbol, "SourceName"); - Assert.Equal( + [Fact] + public void ListOfKnownType_CreatesPatchToPatchEachValue() + { + var sut = new TypeBuilder(); + var typeSymbol = Substitute.For(); + typeSymbol.Name.Returns("TestType"); + typeSymbol.BaseType.Returns((INamedTypeSymbol)null); + var property = GetGenericProperty("System.Collections.Generic", "List", "TestProp", GetType("System", "String")); + typeSymbol.GetMembers().Returns(ImmutableArray.Create(property)); + typeSymbol.GetAttributes().Returns(ImmutableArray.Create()); + var result = sut.BuildWrapperType(new GeneratorClassInfo(typeSymbol, "SourceName")); + Assert.Equal( @"#nullable enable namespace LaDeak.JsonMergePatch.Generated.Safe { @@ -915,98 +871,97 @@ public override SourceName ApplyPatch([System.Diagnostics.CodeAnalysis.AllowNull } #nullable disable ", result.SourceCode); - } + } - private ITypeSymbol GetType(string namespaceName, string typeName) - { - var propertyTypeSymbol = Substitute.For(); - propertyTypeSymbol.Name.Returns(typeName); - SpecialType specialType = GetSpecialTypeFlag(typeName); - propertyTypeSymbol.SpecialType.Returns(specialType); - propertyTypeSymbol.ToDisplayString().ReturnsForAnyArgs($"{namespaceName}.{typeName}"); - var namespaceSymbol = Substitute.For(); - namespaceSymbol.ToDisplayString().Returns(namespaceName); - propertyTypeSymbol.ContainingNamespace.Returns(namespaceSymbol); - propertyTypeSymbol.IsValueType.Returns(GetIsValueTypeFlag(typeName)); - propertyTypeSymbol.NullableAnnotation.Returns(NullableAnnotation.NotAnnotated); - return propertyTypeSymbol; - } + private ITypeSymbol GetType(string namespaceName, string typeName) + { + var propertyTypeSymbol = Substitute.For(); + propertyTypeSymbol.Name.Returns(typeName); + SpecialType specialType = GetSpecialTypeFlag(typeName); + propertyTypeSymbol.SpecialType.Returns(specialType); + propertyTypeSymbol.ToDisplayString().ReturnsForAnyArgs($"{namespaceName}.{typeName}"); + var namespaceSymbol = Substitute.For(); + namespaceSymbol.ToDisplayString().Returns(namespaceName); + propertyTypeSymbol.ContainingNamespace.Returns(namespaceSymbol); + propertyTypeSymbol.IsValueType.Returns(GetIsValueTypeFlag(typeName)); + propertyTypeSymbol.NullableAnnotation.Returns(NullableAnnotation.NotAnnotated); + return propertyTypeSymbol; + } - private INamedTypeSymbol GetNullableType(ITypeSymbol typeParameter) - { - var type = Substitute.For(); - type.IsGenericType.Returns(true); - type.IsAnonymousType.Returns(false); - type.IsAbstract.Returns(false); - type.SpecialType.Returns(SpecialType.System_Nullable_T); - type.TypeArguments.Returns(ImmutableArray.Create(typeParameter)); - string name = $"System.Nullable<{typeParameter.ToDisplayString()}>"; - type.ToDisplayString(GeneratedTypeFilter.SymbolFormat).ReturnsForAnyArgs(name); - type.NullableAnnotation.Returns(NullableAnnotation.Annotated); - return type; - } + private INamedTypeSymbol GetNullableType(ITypeSymbol typeParameter) + { + var type = Substitute.For(); + type.IsGenericType.Returns(true); + type.IsAnonymousType.Returns(false); + type.IsAbstract.Returns(false); + type.SpecialType.Returns(SpecialType.System_Nullable_T); + type.TypeArguments.Returns(ImmutableArray.Create(typeParameter)); + string name = $"System.Nullable<{typeParameter.ToDisplayString()}>"; + type.ToDisplayString(GeneratedTypeFilter.SymbolFormat).ReturnsForAnyArgs(name); + type.NullableAnnotation.Returns(NullableAnnotation.Annotated); + return type; + } - private ITypeSymbol GetNullableReferenceType(ITypeSymbol typeParameter) - { - typeParameter.NullableAnnotation.Returns(NullableAnnotation.Annotated); - return typeParameter; - } + private ITypeSymbol GetNullableReferenceType(ITypeSymbol typeParameter) + { + typeParameter.NullableAnnotation.Returns(NullableAnnotation.Annotated); + return typeParameter; + } - private IPropertySymbol GetProperty(string namespaceName, string typeName, string name, AttributeData attribute = null) - { - var propertyTypeSymbol = GetType(namespaceName, typeName); - var property = Substitute.For(); - property.Name.Returns(name); - property.Type.Returns(propertyTypeSymbol); - property.GetAttributes().Returns(attribute == null ? ImmutableArray.Create() : ImmutableArray.Create(attribute)); - return property; - } + private IPropertySymbol GetProperty(string namespaceName, string typeName, string name, AttributeData attribute = null) + { + var propertyTypeSymbol = GetType(namespaceName, typeName); + var property = Substitute.For(); + property.Name.Returns(name); + property.Type.Returns(propertyTypeSymbol); + property.GetAttributes().Returns(attribute == null ? ImmutableArray.Create() : ImmutableArray.Create(attribute)); + return property; + } - private IPropertySymbol GetGenericProperty(string namespaceName, string typeName, string name, ITypeSymbol propertyTypeSymbol0, ITypeSymbol propertyTypeSymbol1) - { - return GetGenericProperty(namespaceName, typeName, name, new[] { propertyTypeSymbol0, propertyTypeSymbol1 }); - } + private IPropertySymbol GetGenericProperty(string namespaceName, string typeName, string name, ITypeSymbol propertyTypeSymbol0, ITypeSymbol propertyTypeSymbol1) + { + return GetGenericProperty(namespaceName, typeName, name, new[] { propertyTypeSymbol0, propertyTypeSymbol1 }); + } - private IPropertySymbol GetGenericProperty(string namespaceName, string typeName, string name, ITypeSymbol propertyTypeSymbol0) - { - return GetGenericProperty(namespaceName, typeName, name, new[] { propertyTypeSymbol0 }); - } + private IPropertySymbol GetGenericProperty(string namespaceName, string typeName, string name, ITypeSymbol propertyTypeSymbol0) + { + return GetGenericProperty(namespaceName, typeName, name, new[] { propertyTypeSymbol0 }); + } + + private IPropertySymbol GetGenericProperty(string namespaceName, string typeName, string name, ITypeSymbol[] genericTypes) + { + var propertyTypeSymbol = Substitute.For(); + propertyTypeSymbol.Name.Returns(typeName); + SpecialType specialType = GetSpecialTypeFlag(typeName); + propertyTypeSymbol.SpecialType.Returns(specialType); + propertyTypeSymbol.ToDisplayString().ReturnsForAnyArgs($"{namespaceName}.{typeName}"); + var namespaceSymbol = Substitute.For(); + namespaceSymbol.ToDisplayString().Returns(namespaceName); + propertyTypeSymbol.ContainingNamespace.Returns(namespaceSymbol); + propertyTypeSymbol.IsValueType.Returns(GetIsValueTypeFlag(typeName)); + propertyTypeSymbol.IsGenericType.Returns(true); + propertyTypeSymbol.TypeArguments.Returns(ImmutableArray.Create(genericTypes, 0, genericTypes.Length)); + var property = Substitute.For(); + property.Name.Returns(name); + property.Type.Returns(propertyTypeSymbol); + property.GetAttributes().Returns(ImmutableArray.Create()); + return property; + } - private IPropertySymbol GetGenericProperty(string namespaceName, string typeName, string name, ITypeSymbol[] genericTypes) + private SpecialType GetSpecialTypeFlag(string typeName) => + typeName switch { - var propertyTypeSymbol = Substitute.For(); - propertyTypeSymbol.Name.Returns(typeName); - SpecialType specialType = GetSpecialTypeFlag(typeName); - propertyTypeSymbol.SpecialType.Returns(specialType); - propertyTypeSymbol.ToDisplayString().ReturnsForAnyArgs($"{namespaceName}.{typeName}"); - var namespaceSymbol = Substitute.For(); - namespaceSymbol.ToDisplayString().Returns(namespaceName); - propertyTypeSymbol.ContainingNamespace.Returns(namespaceSymbol); - propertyTypeSymbol.IsValueType.Returns(GetIsValueTypeFlag(typeName)); - propertyTypeSymbol.IsGenericType.Returns(true); - propertyTypeSymbol.TypeArguments.Returns(ImmutableArray.Create(genericTypes, 0, genericTypes.Length)); - var property = Substitute.For(); - property.Name.Returns(name); - property.Type.Returns(propertyTypeSymbol); - property.GetAttributes().Returns(ImmutableArray.Create()); - return property; - } + "Int32" => SpecialType.System_Int32, + "String" => SpecialType.System_String, + "Dictionary" => SpecialType.None, + _ => SpecialType.None, + }; - private SpecialType GetSpecialTypeFlag(string typeName) => - typeName switch - { - "Int32" => SpecialType.System_Int32, - "String" => SpecialType.System_String, - "Dictionary" => SpecialType.None, - _ => SpecialType.None, - }; - - private bool GetIsValueTypeFlag(string typeName) => - typeName switch - { - "Int32" => true, - "String" => false, - _ => false, - }; - } + private bool GetIsValueTypeFlag(string typeName) => + typeName switch + { + "Int32" => true, + "String" => false, + _ => false, + }; } \ No newline at end of file diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeRepositoryGeneratorTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeRepositoryGeneratorTests.cs index 66b2a8f..893e707 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeRepositoryGeneratorTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypeRepositoryGeneratorTests.cs @@ -6,16 +6,16 @@ using Microsoft.CodeAnalysis; using Xunit; -namespace LaDeak.JsonMergePatch.AspNetCore.Tests +namespace LaDeak.JsonMergePatch.AspNetCore.Tests; + +public class TypeRepositoryGeneratorTests { - public class TypeRepositoryGeneratorTests + [Fact] + public void EmptyInput_CreateRepository_GeneratesEmptyConstructor() { - [Fact] - public void EmptyInput_CreateRepository_GeneratesEmptyConstructor() - { - var sut = new TypeRepositoryGenerator(); - var result = sut.CreateTypeRepository(null, string.Empty); - Assert.Equal(@" + var sut = new TypeRepositoryGenerator(); + var result = sut.CreateTypeRepository(null, string.Empty); + Assert.Equal(@" namespace LaDeak.JsonMergePatch.Generated.Safe { public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository @@ -48,14 +48,14 @@ public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullW 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(@" + [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 @@ -88,14 +88,14 @@ public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullW public System.Collections.Generic.IEnumerable> GetAll() => _repository; } }", result); - } + } - [Fact] - public void WithTypes_CreateRepository_GeneratesEmptyConstructor() - { - var sut = new TypeRepositoryGenerator(); - var result = sut.CreateTypeRepository(new[] { ("TestDto0", "TestDto0Wrapped"), ("TestDto1", "TestDto1Wrapped") }, string.Empty); - Assert.Equal(@" + [Fact] + public void WithTypes_CreateRepository_GeneratesEmptyConstructor() + { + var sut = new TypeRepositoryGenerator(); + var result = sut.CreateTypeRepository(new[] { ("TestDto0", "TestDto0Wrapped"), ("TestDto1", "TestDto1Wrapped") }, string.Empty); + Assert.Equal(@" namespace LaDeak.JsonMergePatch.Generated.Safe { public class TypeRepository : LaDeak.JsonMergePatch.Abstractions.ITypeRepository @@ -130,102 +130,101 @@ public bool TryGet(System.Type source, [System.Diagnostics.CodeAnalysis.NotNullW public System.Collections.Generic.IEnumerable> GetAll() => _repository; } }", result); - } + } - [Fact] - public void EmptyInput_CreateRepository_Compiles() - { - var sut = new TypeRepositoryGenerator(); - _ = Compile(sut.CreateTypeRepository(null, string.Empty)); - } + [Fact] + public void EmptyInput_CreateRepository_Compiles() + { + var sut = new TypeRepositoryGenerator(); + _ = Compile(sut.CreateTypeRepository(null, string.Empty)); + } - [Fact] - public void Compiled_Repository_ReturnsInstanceSingleton() - { - var sut = new TypeRepositoryGenerator(); - var typeRepository = GetTypeRepository(Compile(sut.CreateTypeRepository(null, string.Empty))); - Assert.NotNull(typeRepository); - } + [Fact] + public void Compiled_Repository_ReturnsInstanceSingleton() + { + var sut = new TypeRepositoryGenerator(); + var typeRepository = GetTypeRepository(Compile(sut.CreateTypeRepository(null, string.Empty))); + Assert.NotNull(typeRepository); + } - [Fact] - public void EmptyRepository_Add_DoesNotThrow() - { - var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); - sut.Add(); - } + [Fact] + public void EmptyRepository_Add_DoesNotThrow() + { + var generator = new TypeRepositoryGenerator(); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); + sut.Add(); + } - [Fact] - public void EmptyRepository_AddTwiceSameType_ThrowsException() - { - var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); - sut.Add(); - Assert.Throws(() => sut.Add()); - } + [Fact] + public void EmptyRepository_AddTwiceSameType_ThrowsException() + { + var generator = new TypeRepositoryGenerator(); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); + sut.Add(); + Assert.Throws(() => sut.Add()); + } - [Fact] - public void EmptyRepository_TryGet_ReturnsFalse() - { - var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); - Assert.False(sut.TryGet(typeof(TestDto), out _)); - } + [Fact] + public void EmptyRepository_TryGet_ReturnsFalse() + { + var generator = new TypeRepositoryGenerator(); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); + Assert.False(sut.TryGet(typeof(TestDto), out _)); + } - [Fact] - public void RepositoryWitTestDto_TryGet_ReturnsTrue() - { - var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); - sut.Add(); - Assert.True(sut.TryGet(typeof(TestDto), out _)); - } + [Fact] + public void RepositoryWitTestDto_TryGet_ReturnsTrue() + { + var generator = new TypeRepositoryGenerator(); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); + sut.Add(); + Assert.True(sut.TryGet(typeof(TestDto), out _)); + } - [Fact] - public void RepositoryWitTestDto_TryGet_ReturnsRegisteredType() - { - var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); - sut.Add(); - sut.TryGet(typeof(TestDto), out var result); - Assert.Equal(typeof(TestDtoWrapped), result); - } + [Fact] + public void RepositoryWitTestDto_TryGet_ReturnsRegisteredType() + { + var generator = new TypeRepositoryGenerator(); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); + sut.Add(); + sut.TryGet(typeof(TestDto), out var result); + Assert.Equal(typeof(TestDtoWrapped), result); + } - [Fact] - public void RepositoryWitTestDto_TryGet_ReturnsPatchOfUserType() - { - var generator = new TypeRepositoryGenerator(); - var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); - sut.Add(); - sut.TryGet(typeof(TestDto), out var result); - Assert.True(typeof(Patch).IsAssignableFrom(result)); - } + [Fact] + public void RepositoryWitTestDto_TryGet_ReturnsPatchOfUserType() + { + var generator = new TypeRepositoryGenerator(); + var sut = GetTypeRepository(Compile(generator.CreateTypeRepository(null, string.Empty))); + sut.Add(); + sut.TryGet(typeof(TestDto), out var result); + Assert.True(typeof(Patch).IsAssignableFrom(result)); + } - [Fact] - public void GeneratedTypesInRepository_TryGet_ReturnsRegisteredType() - { - var generator = new TypeRepositoryGenerator(); - 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); - } + [Fact] + public void GeneratedTypesInRepository_TryGet_ReturnsRegisteredType() + { + var generator = new TypeRepositoryGenerator(); + 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); + } - [Fact] - 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, string.Empty)))); - } + [Fact] + 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, string.Empty)))); + } - private Assembly Compile(string code) - { - return SourceBuilder.CompileToAssembly(code, new[] { MetadataReference.CreateFromFile(typeof(TestDto).Assembly.Location) }); - } + private Assembly Compile(string code) + { + return SourceBuilder.CompileToAssembly(code, new[] { MetadataReference.CreateFromFile(typeof(TestDto).Assembly.Location) }); + } - private ITypeRepository GetTypeRepository(Assembly assembly) - { - return assembly.GetType("LaDeak.JsonMergePatch.Generated.Safe.TypeRepository").GetProperty("Instance").GetValue(null) as ITypeRepository; - } + private ITypeRepository GetTypeRepository(Assembly assembly) + { + return assembly.GetType("LaDeak.JsonMergePatch.Generated.Safe.TypeRepository").GetProperty("Instance").GetValue(null) as ITypeRepository; } -} +} \ No newline at end of file diff --git a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypesWithCtorSerializationTests.cs b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypesWithCtorSerializationTests.cs index 78890eb..f8e3e5c 100644 --- a/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypesWithCtorSerializationTests.cs +++ b/test/JsonMergePatch.SourceGenerator.Abstractions.Tests/TypesWithCtorSerializationTests.cs @@ -100,8 +100,10 @@ public TypesWithCtorSerializationData() private readonly string _vanillaRecordTypesProperties = @" namespace TestCode { + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record SubDto(System.Int32 NumberProp, System.DateTime? NullableDateTimeProperty, System.Double CamelCaseProperty); + [LaDeak.JsonMergePatch.Abstractions.Patchable] public record ParentDto(System.String ParentStringProperty, SubDto OtherDto, System.Collections.Generic.IEnumerable Values); public class Program diff --git a/test/JsonMergePatch.Tests/JsonMergePatch.AspNetCore.Tests.csproj b/test/JsonMergePatch.Tests/JsonMergePatch.AspNetCore.Tests.csproj index b8fd6f2..05804bb 100644 --- a/test/JsonMergePatch.Tests/JsonMergePatch.AspNetCore.Tests.csproj +++ b/test/JsonMergePatch.Tests/JsonMergePatch.AspNetCore.Tests.csproj @@ -1,16 +1,17 @@ - net8.0 + net9.0 false LaDeak.JsonMergePatch.AspNetCore.Tests + true - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/JsonMergePatch.Tests/JsonMergePatchInputReaderTests.cs b/test/JsonMergePatch.Tests/JsonMergePatchInputReaderTests.cs index ac3c3e5..6fa1a2a 100644 --- a/test/JsonMergePatch.Tests/JsonMergePatchInputReaderTests.cs +++ b/test/JsonMergePatch.Tests/JsonMergePatchInputReaderTests.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; -using System.Text; -using System.Threading.Tasks; +using System.Text; using LaDeak.JsonMergePatch.Abstractions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Json; @@ -68,7 +65,7 @@ public async Task NoTypeRepositoryRegistration_ReadRequestBodyAsync_ReturnsFailu public async Task InvalidJson_ReadRequestBodyAsync_ReturnsFailure() { var sut = new JsonMergePatchInputReader(new JsonOptions(), CreateTypeRepository()); - DefaultHttpContext httpContext = await CreateHttpContextAsync("invalid").ConfigureAwait(false); + DefaultHttpContext httpContext = await CreateHttpContextAsync("invalid"); var inputContext = CreateInputFormatterContext(typeof(Patch), httpContext); var result = await sut.ReadRequestBodyAsync(inputContext, Encoding.UTF8); @@ -81,7 +78,7 @@ public async Task InvalidJson_ReadRequestBodyAsync_ReturnsFailure() public async Task ValidJson_ReadRequestBodyAsync_ReturnsFailure() { var sut = new JsonMergePatchInputReader(new JsonOptions(), CreateTypeRepository()); - DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }").ConfigureAwait(false); + DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }"); var inputContext = CreateInputFormatterContext(typeof(Patch), httpContext); var result = await sut.ReadRequestBodyAsync(inputContext, Encoding.UTF8); @@ -93,7 +90,7 @@ public async Task ValidJson_ReadRequestBodyAsync_ReturnsFailure() public async Task OtherEncodingJson_ReadRequestBodyAsync_ReturnsFailure() { var sut = new JsonMergePatchInputReader(new JsonOptions(), CreateTypeRepository()); - DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }", Encoding.Unicode).ConfigureAwait(false); + DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }", Encoding.Unicode); var inputContext = CreateInputFormatterContext(typeof(Patch), httpContext); var result = await sut.ReadRequestBodyAsync(inputContext, Encoding.Unicode); @@ -105,7 +102,7 @@ public async Task OtherEncodingJson_ReadRequestBodyAsync_ReturnsFailure() public async Task NoPatchInputType_ReadRequestBodyAsync_ReturnsFailure() { var sut = new JsonMergePatchInputReader(new JsonOptions(), CreateTypeRepository()); - DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }").ConfigureAwait(false); + DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }"); var inputContext = CreateInputFormatterContext(typeof(TestDto), httpContext); var result = await sut.ReadRequestBodyAsync(inputContext, Encoding.UTF8); @@ -117,7 +114,7 @@ public async Task NoPatchInputType_ReadRequestBodyAsync_ReturnsFailure() public async Task OpenPatchType_CanRead_ReturnsTrue() { var sut = new JsonMergePatchInputReader(new JsonOptions(), CreateTypeRepository()); - DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }").ConfigureAwait(false); + DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }"); var inputContext = CreateInputFormatterContext(typeof(Patch<>), httpContext); Assert.True(sut.CanRead(inputContext)); @@ -127,7 +124,7 @@ public async Task OpenPatchType_CanRead_ReturnsTrue() public async Task ClosedPatchType_CanRead_ReturnsTrue() { var sut = new JsonMergePatchInputReader(new JsonOptions(), CreateTypeRepository()); - DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }").ConfigureAwait(false); + DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }"); var inputContext = CreateInputFormatterContext(typeof(Patch), httpContext); Assert.True(sut.CanRead(inputContext)); @@ -137,7 +134,7 @@ public async Task ClosedPatchType_CanRead_ReturnsTrue() public async Task NonPatchType_CanRead_ReturnsFalse() { var sut = new JsonMergePatchInputReader(new JsonOptions(), CreateTypeRepository()); - DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }").ConfigureAwait(false); + DefaultHttpContext httpContext = await CreateHttpContextAsync("{ \"Prop1\" : 5 }"); var inputContext = CreateInputFormatterContext(typeof(TestDto), httpContext); Assert.False(sut.CanRead(inputContext)); @@ -159,7 +156,7 @@ private async Task CreateHttpContextAsync(string requestBody var memoryStream = new MemoryStream(); using var writer = new StreamWriter(memoryStream, encoding: encoding ?? Encoding.UTF8, leaveOpen: true); - await writer.WriteAsync(requestBody).ConfigureAwait(false); + await writer.WriteAsync(requestBody); await writer.FlushAsync(); memoryStream.Seek(0, SeekOrigin.Begin); httpContext.Request.Body = memoryStream; diff --git a/test/JsonMergePatch.Tests/TestDto.cs b/test/JsonMergePatch.Tests/TestDto.cs index 823975d..3a0bf4b 100644 --- a/test/JsonMergePatch.Tests/TestDto.cs +++ b/test/JsonMergePatch.Tests/TestDto.cs @@ -1,7 +1,6 @@ -namespace LaDeak.JsonMergePatch.AspNetCore.Tests +namespace LaDeak.JsonMergePatch.AspNetCore.Tests; + +public class TestDto { - public class TestDto - { - public int Prop1 { get; set; } - } + public int Prop1 { get; set; } } diff --git a/test/JsonMergePatch.Tests/TestDtoWrapped.cs b/test/JsonMergePatch.Tests/TestDtoWrapped.cs index 1d93d44..b188344 100644 --- a/test/JsonMergePatch.Tests/TestDtoWrapped.cs +++ b/test/JsonMergePatch.Tests/TestDtoWrapped.cs @@ -1,23 +1,22 @@ using LaDeak.JsonMergePatch.Abstractions; -namespace LaDeak.JsonMergePatch.AspNetCore.Tests +namespace LaDeak.JsonMergePatch.AspNetCore.Tests; + +public class TestDtoWrapped : Patch { - public class TestDtoWrapped : Patch + public TestDtoWrapped() { - public TestDtoWrapped() - { - Properties = new bool[1]; - } + Properties = new bool[1]; + } - private int? _prop1; - public int? Prop1 { get => _prop1; set { Properties[0] = true; _prop1 = value; } } + private int? _prop1; + public int? Prop1 { get => _prop1; set { Properties[0] = true; _prop1 = value; } } - public override TestDto ApplyPatch(TestDto input) - { - input ??= new(); - if (Properties[0]) - input.Prop1 = Prop1.HasValue ? Prop1.Value : default; - return input; - } + public override TestDto ApplyPatch(TestDto input) + { + input ??= new(); + if (Properties[0]) + input.Prop1 = Prop1.HasValue ? Prop1.Value : default; + return input; } }