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