Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a028b9d
Add ZLinq
trejjam Jan 5, 2026
e4530b0
Add MultipleContextsWorks
trejjam Jan 5, 2026
39ff510
Add PolymorphicJsonSerializerMetadata
trejjam Jan 5, 2026
7ba5a42
Update snapshots
trejjam Jan 5, 2026
f9b56ed
Group polymorhic targets
trejjam Jan 5, 2026
3647a06
Bump MySecondJsonSerializerContext
trejjam Jan 5, 2026
9402d58
Update packages.lock.json
trejjam Jan 5, 2026
96041f1
Force use of testing platform
trejjam Jan 5, 2026
9ee0067
Bump AGENTS.md
trejjam Jan 5, 2026
64009d4
Add ZLinq to the Aviationexam.GeneratedJsonConverters.SourceGenerator…
trejjam Jan 5, 2026
8c317f9
Add PolymorphicJsonSerializerContextConfigurationMerger
trejjam Jan 5, 2026
6c56c9f
Refactor BaseContractWithCustomDelimiterSerializationTests
trejjam Jan 5, 2026
073a8d2
Add Verify into Target.Tests
trejjam Jan 5, 2026
b84b649
Use Verifier.VerifyJson
trejjam Jan 5, 2026
acf57c6
Add withMySecondJsonSerializerContext tests
trejjam Jan 5, 2026
c94fdb3
Add NullableLeafContractWithCustomDelimiter
trejjam Jan 5, 2026
9125ddf
Use the DefaultIgnoreCondition for serialization
trejjam Jan 5, 2026
1d17ce1
Use NET_10_OR_GREATER
trejjam Jan 5, 2026
d07278c
Do not use flags for non flags enums
trejjam Jan 5, 2026
d236085
Replace System.Linq with ZLinq in SourceGenerator project
trejjam Jan 5, 2026
b036e74
Replace System.Linq with ZLinq in parser files
trejjam Jan 5, 2026
8241796
Replace System.Linq with ZLinq in PolymorphicJsonSerializerContextTra…
trejjam Jan 5, 2026
8e77e0b
Replace System.Linq with ZLinq in EnumJsonConverterGenerator
trejjam Jan 5, 2026
6d880e0
Replace System.Linq with ZLinq in EnumJsonConverterIncrementalGenerator
trejjam Jan 5, 2026
b76efe6
Add ZLinq to the Tests
trejjam Jan 5, 2026
fc74ad9
Update packages.lock.json
trejjam Jan 5, 2026
96b605b
Simplify PolymorphicJsonSerializerContextConfigurationFilter
trejjam Jan 5, 2026
912071c
Reference ZLinq in source generator tests
trejjam Jan 5, 2026
3e6bdeb
Restructure JsonConvertersSerializerJsonContextGenerator
trejjam Jan 5, 2026
7847b9d
Improve API usage
trejjam Jan 5, 2026
8543022
Revert to System.Linq
trejjam Jan 5, 2026
c2ade00
Add ZLinq
trejjam Jan 5, 2026
618ab54
Return ZLinq
trejjam Jan 5, 2026
3e06149
Use ZLinq
trejjam Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Agent Guidelines for JSON Converter Source Generator

This repository contains a C# source generator for JSON converters supporting polymorphic contracts and enum serialization.

## Build & Test Commands

### Basic Commands
```bash
# Restore dependencies
dotnet restore --nologo

# Build entire solution (Release)
dotnet build --no-restore --nologo --configuration Release

# Build specific project
dotnet build src/Aviationexam.GeneratedJsonConverters.SourceGenerator/Aviationexam.GeneratedJsonConverters.SourceGenerator.csproj --configuration Release

# Run all tests
dotnet test --no-build --configuration Release --results-directory TestResults --report-trx

# Run specific test project
dotnet test src/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests/Aviationexam.GeneratedJsonConverters.SourceGenerator.Tests.csproj --configuration Release

# Run single test by fully qualified name
dotnet test --filter "FullyQualifiedName~EnumJsonConverterIncrementalGeneratorSnapshotTests.SimpleWorks" --configuration Release

# Run all tests in a class
dotnet test --filter "FullyQualifiedName~EnumJsonConverterIncrementalGeneratorSnapshotTests" --configuration Release
```

### Code Style & Linting
```bash
# Check code formatting (CI validation)
dotnet format --no-restore --verify-no-changes -v diag

# Apply code formatting
dotnet format --no-restore
```

## Project Structure

- **SourceGenerator**: Core source generator implementation (`netstandard2.0`)
- **SourceGenerator.Tests**: Snapshot tests for generator output (xUnit v3, Microsoft Testing Platform)
- **SourceGenerator.Target**: Sample target project demonstrating usage
- **SourceGenerator.Target.Tests**: Integration tests for generated code

## Code Style Guidelines

### File & Namespace Conventions
- **Line endings**: CRLF (enforced by `.editorconfig`)
- **Encoding**: UTF-8 with BOM for `.cs` files
- **Namespaces**: File-scoped namespaces (C# 14.0 / .NET 10)
- **Namespace structure**: Should match folder structure (`dotnet_style_namespace_match_folder = true`)

### C# Formatting
```csharp
// Indentation: 4 spaces for .cs files
// Max line length: 160 characters
// Space after cast: true
// System directives: NOT sorted first (dotnet_sort_system_directives_first = false)

// Example formatting:
namespace Aviationexam.GeneratedJsonConverters.SourceGenerator;

[Generator]
public class EnumJsonConverterIncrementalGenerator : IIncrementalGenerator
{
public const string Id = "AVI_EJC";

private const EnumSerializationStrategy DefaultEnumSerializationStrategy =
EnumSerializationStrategy.FirstEnumName;
}
```

### XML/Project Files
- **Indentation**: 2 spaces for `.csproj`, `.targets`, `.props`
- **Max line length**: 120 characters

### Import Organization
```csharp
// Standard ordering (System namespaces NOT first)
using Aviationexam.GeneratedJsonConverters.SourceGenerator.Extensions;
using H.Generators.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
```

### Naming Conventions
- **Enums**: Prefix with `E` (e.g., `EMyEnum`, `EBackingEnum`)
- **Interfaces**: Prefix with `I` (e.g., `IDiscriminatorStruct`)
- **Constants**: PascalCase with clear descriptors (e.g., `DefaultEnumSerializationStrategy`)
- **Private fields**: Use `_camelCase` for instance fields (not shown in samples but standard C# practice)
- **Method parameters**: camelCase
- **Local variables**: camelCase

### Type Usage
- **Nullable reference types**: ENABLED (`<Nullable>enable</Nullable>`)
- **LangVersion**: 14.0 (C# 14)
- **Target frameworks**: `netstandard2.0` (generator), `net8.0;net9.0;net10.0` (tests)
- Use modern C# features: file-scoped namespaces, target-typed `new()`, collection expressions `[..]`
- Prefer `ImmutableArray` for generator collections
- Use `SymbolEqualityComparer` for Roslyn symbol comparisons

### Comments & Documentation
- XML documentation not required for internal generator code (`GenerateDocumentationFile = false`)
- Use clear, descriptive names over excessive comments
- Comment complex logic (e.g., configuration parsing, symbol transformations)

### Error Handling
- Use nullable patterns and null-coalescing operators
- Throw `ArgumentNullException` for critical null cases in generators
- Use `out` parameters for converter names and diagnostic results
- Leverage `ResultWithDiagnostics<T>` pattern for incremental generators

### Testing Conventions
- **Test framework**: xUnit v3 with Microsoft Testing Platform
- **Snapshot testing**: Use `Verify` library for generator output validation
- **Test naming**: `MethodName_Scenario` or `ScenarioWorks` pattern
- **Theory data**: Use `[InlineData]` for parameterized tests
- Disable specific warnings with `#pragma warning disable/restore` when intentional (e.g., `xUnit1025` for duplicate test values)

### Generator-Specific Patterns
```csharp
// Use IncrementalValueProvider patterns
context.SyntaxProvider.CreateSyntaxProvider(
predicate: static (node, _) => node is EnumDeclarationSyntax,
transform: TransformerMethod
)
.Collect()
.Combine(otherProvider)
.SelectAndReportExceptions(GetSourceCode, context, Id)
.SelectAndReportDiagnostics(context)
.AddSource(context);

// Embed resources for generated attributes
i.AddEmbeddedResources<GeneratorClass>([
"AttributeName",
"OtherTypeName",
]);
```

### Build Configuration
- **MSBuild properties prefix**: `AVI_EJC_` for this generator
- **Configuration access**: Via `AnalyzerConfigOptionsProvider.GetGlobalOption()`
- Example: `build_property.AVI_EJC_DefaultJsonSerializerContext_Namespace`

## Common Pitfalls
1. **Roslyn symbol comparisons**: Always use `SymbolEqualityComparer.Default.Equals()`, never `==`
2. **Incremental generators**: Ensure all data structures implement proper equality for caching
3. **Embedded resources**: Must be registered in `.csproj` and loaded in `PostInitialization`
4. **netstandard2.0 constraints**: Be mindful of API availability in generator projects
5. **Line endings**: Always CRLF (use `.editorconfig` settings)

## Critical Diagnostic Rules (Error Severity)
- CA1507: Use nameof when possible
- CA2000, CA2012, CA2016: Dispose/async patterns
- CA2213, CA2215, CA2217, CA2234: Dispose and base calls
- CA53xx, CA59xx: Security-related analyzers

## Testing Strategy
- **Snapshot tests**: Verify generated source code exactly matches approved snapshots
- **Integration tests**: Validate serialization/deserialization with actual JSON
- **Configuration tests**: Test various MSBuild property combinations
- Run tests with `--configuration Release` in CI to match production build

## Version Control
- Use GitVersion for semantic versioning
- All changes must pass `dotnet format` validation
- Tests must pass on .NET 8, 9, and 10
3 changes: 2 additions & 1 deletion JsonConverter.SourceGenerator.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EUnitTestFramework_002EMigrations_002EEnableDisabledProvidersMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/UnitTesting/DisabledProviders/=Testing_0020Platform/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/UnitTesting/DisabledProviders/=VsTest/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Aviationexam/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Aviationexam/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@
<PackageReference Include="System.Collections.Immutable" Version="10.0.1" />
<PackageReference Include="System.Text.Encodings.Web" Version="10.0.1" />
<PackageReference Include="System.IO.Pipelines" Version="10.0.1" />
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.1" />
<PackageReference Include="System.Text.Json" Version="10.0.1" />
<PackageReference Include="Verify.XunitV3" Version="31.9.3" />
<PackageReference Include="Verify.SourceGenerators" Version="2.5.0" />
<PackageReference Include="xunit.v3.mtp-v2" Version="3.2.1" />
<PackageReference Include="ZLinq" Version="1.5.4" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.ContractWithCustomDelimiter;
using System;
using System.Text.Json;
using System.Threading.Tasks;
using VerifyTests;
using VerifyXunit;
using Xunit;

namespace Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Tests;

public partial class BaseContractWithCustomDelimiterSerializationTests
{
[Theory]
[MemberData(nameof(BaseJsonContractData))]
public void DeserializeBaseContractWorks(string json, Type targetType)
{
var baseContract = JsonSerializer.Deserialize<BaseContractWithCustomDelimiter>(
json,
MyJsonSerializerContext.Default.Options
);

Assert.NotNull(baseContract);
Assert.Equal(1, baseContract.BaseProperty);
Assert.IsType(targetType, baseContract);

if (baseContract is LeafContractWithCustomDelimiter leafContract)
{
Assert.Equal(2, leafContract.LeafProperty);
}
}

[Theory]
[MemberData(nameof(LeafContractData))]
public Task SerializeBaseContractWorks(int testId, BaseContractWithCustomDelimiter contract)
{
var json = JsonSerializer.Serialize(
contract,
MyJsonSerializerContext.Default.Options
);

var settings = new VerifySettings();
settings.UseParameters(testId);

return Verifier.VerifyJson(json, settings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.ContractWithCustomDelimiter;
using System;
using System.Text.Json;
using System.Threading.Tasks;
using VerifyTests;
using VerifyXunit;
using Xunit;

namespace Aviationexam.GeneratedJsonConverters.SourceGenerator.Target.Tests;

public partial class BaseContractWithCustomDelimiterSerializationTests
{
[Theory]
[MemberData(nameof(BaseJsonContractData))]
public void DeserializeBaseContractWorks_withMySecondJsonSerializerContext(string json, Type targetType)
{
var baseContract = JsonSerializer.Deserialize<BaseContractWithCustomDelimiter>(
json,
MySecondJsonSerializerContext.Default.Options
);

Assert.NotNull(baseContract);
Assert.Equal(1, baseContract.BaseProperty);
Assert.IsType(targetType, baseContract);

if (baseContract is LeafContractWithCustomDelimiter leafContract)
{
Assert.Equal(2, leafContract.LeafProperty);
}
}

[Theory]
[MemberData(nameof(LeafContractData))]
public Task SerializeBaseContractWorks_withMySecondJsonSerializerContext(int testId, BaseContractWithCustomDelimiter contract)
{
var json = JsonSerializer.Serialize(
contract,
MySecondJsonSerializerContext.Default.Options
);

var settings = new VerifySettings();
settings.UseParameters(testId);

return Verifier.VerifyJson(json, settings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
myCustomDelimiter: LeafContractWithCustomDelimiter,
leafProperty: 2,
baseProperty: 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
myCustomDelimiter: NullableLeafContractWithCustomDelimiter,
leafProperty: 2,
baseProperty: 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
myCustomDelimiter: NullableLeafContractWithCustomDelimiter,
leafProperty: null,
baseProperty: 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
myCustomDelimiter: LeafContractWithCustomDelimiter,
leafProperty: 2,
baseProperty: 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
myCustomDelimiter: NullableLeafContractWithCustomDelimiter,
leafProperty: 2,
baseProperty: 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
myCustomDelimiter: NullableLeafContractWithCustomDelimiter,
baseProperty: 1
}
Loading