MintPlayer.SourceGenerators.Tools
12.1.0
dotnet add package MintPlayer.SourceGenerators.Tools --version 12.1.0
NuGet\Install-Package MintPlayer.SourceGenerators.Tools -Version 12.1.0
<PackageReference Include="MintPlayer.SourceGenerators.Tools" Version="12.1.0" />
<PackageVersion Include="MintPlayer.SourceGenerators.Tools" Version="12.1.0" />
<PackageReference Include="MintPlayer.SourceGenerators.Tools" />
paket add MintPlayer.SourceGenerators.Tools --version 12.1.0
#r "nuget: MintPlayer.SourceGenerators.Tools, 12.1.0"
#:package MintPlayer.SourceGenerators.Tools@12.1.0
#addin nuget:?package=MintPlayer.SourceGenerators.Tools&version=12.1.0
#tool nuget:?package=MintPlayer.SourceGenerators.Tools&version=12.1.0
MintPlayer.SourceGenerators.Tools
This package makes it easier to write your own source-generators. The library provides:
- An
IncrementalGeneratorclass that implementsIIncrementalGeneratorand already reads several options (like theRootNamespaceof the project) beforehand) - A
Producerclass that hands you a ready-to-useIndentedTextWriter - A
ProduceCode(...)extension method where you can pass in your source-providers
Example
Generator:
namespace ExampleGenerators;
// Use the ready-made IncrementalGenerator
[Generator(LanguageNames.CSharp)]
public class ExampleGenerator : IncrementalGenerator
{
public override void Initialize(IncrementalGeneratorInitializationContext context, IncrementalValueProvider<Settings> settingsProvider)
{
var typesToMapProvider = context.SyntaxProvider
.ForAttributeWithMetadataName(
"ExampleGenerators.Attributes.ExampleAttribute",
static (node, ct) => node is not null,
static (ctx, ct) =>
{
if (ctx.SemanticModel.GetDeclaredSymbol(ctx.TargetNode, ct) is INamedTypeSymbol typeSymbol &&
ctx.Attributes.FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "ExampleGenerators.Attributes.ExampleAttribute") is { } attr &&
attr.ConstructorArguments.FirstOrDefault().Value is INamedTypeSymbol mapType)
{
return new Models.TypeToMap
{
...
};
}
return null;
}
)
.Where(static m => m is not null);
var typesToMapSourceProvider = typesToMapProvider
.Collect()
.Combine(settingsProvider)
.Select(static Producer (p, ct) => new MapperProducer(p.Left, p.Right.RootNamespace!));
// Pass your source-providers to the ready-made ProduceCode extension method
context.ProduceCode(typesToMapSourceProvider);
}
}
Initialize(context, settingsProvider) is the method to override. settingsProvider carries the project's settings
(RootNamespace, LanguageVersion, target framework, ...) and is itself value-equal, so it stays cached until a
setting changes.
Producer:
public sealed class MapperProducer : Producer
{
private readonly IEnumerable<TypeToMap> typesToMap;
public MapperProducer(IEnumerable<TypeToMap> typesToMap, string rootNamespace) : base(rootNamespace, "Mappers.g.cs")
{
this.typesToMap = typesToMap;
}
protected override void ProduceSource(IndentedTextWriter writer, CancellationToken cancellationToken)
{
writer.WriteLine($"namespace {RootNamespace}");
writer.WriteLine("{");
writer.Indent++;
writer.WriteLine("public static class MapperExtensions");
writer.WriteLine("{");
writer.Indent++;
// Generate more code based on the data in typesToMap
writer.Indent--;
writer.WriteLine("}");
writer.Indent--;
writer.WriteLine("}");
}
}
Use the ValueComparerGenerator
Roslyn compares every step's output with EqualityComparer<T>.Default, so your models need value equality. If you
install MintPlayer.ValueComparerGenerator too,
it generates it for you.
TypeToMap.cs
namespace MintPlayer.Mapper.Models;
[GenerateEquality]
public partial class TypeToMap
{
public string DeclaredType { get; set; }
public string DeclaredTypeName { get; set; }
public PropertyDeclaration[] DeclaredProperties { get; set; } = [];
public string MappingType { get; set; }
public string MappingTypeName { get; set; }
public PropertyDeclaration[] MappingProperties { get; set; } = [];
public string DestinationNamespace { get; set; }
}
The generator writes IEquatable<T>, Equals(object) and GetHashCode() on TypeToMap itself, so the model is
value-equal in every step, in a Collect(), and inside a Combine tuple, with no comparer passed anywhere.
Value equality helpers
Two public types support value-equal models. The generated code uses them, and so can a model you write by hand.
ValueEqualityis a static, reflection-free helper. It hasArray,ImmutableArray,List,SequenceandDictionarycomparisons with matching*Hashmethods,Combinefor folding a hash, and a sealed comparer with a staticInstancefor each shape (ArrayComparer<T>,ImmutableArrayComparer<T>,ListComparer<T>,SequenceComparer<T>,DictionaryComparer<K,V>, plusDelegateComparer<T>). Pass one as the element comparer to compose nested collections. Sequences compare element-wise and in order, by the declared shape rather than the runtime type. Dictionaries compare order-insensitively. A defaultImmutableArrayequals only another default one.EquatableArray<T>is a readonly struct overT[]that implementsIEquatable. Return it from a step that builds a new collection, such as aSelectafterCollect()that filters or projects, or a transform returning an array. A plain array orImmutableArray<T>compares by reference, so that step would reportModifiedon every run. A defaultEquatableArray<T>behaves as empty..ToEquatableArray()converts any sequence, and arrays andImmutableArray<T>convert implicitly.
var registrations = classesProvider
.Combine(assemblyLevelProvider)
.Select(static (p, ct) => p.Left.Concat(p.Right).ToEquatableArray());
LocationKey, PathSpec, PathSpecElement and Settings implement IEquatable<T>, so a model can hold them.
Staying incremental
A generator that emits the same output on every keystroke is still doing the work on every keystroke. The pieces above are built so that an edit your generator does not care about stops at an equal model:
ProduceCode(...)gives each producer its own output step and never touches theCompilation, so a file is regenerated only when its own producer's input changed. Since 11.0.0; before that every producer was combined with theCompilationand re-ran on every edit. It takes any number of single providers (IncrementalValueProvider<Producer>, one file each) or, since 12.0.0, any number of multi-value providers (IncrementalValuesProvider<Producer>, one file per producer).- Models compare by value under the default comparer: generated by
[GenerateEquality], or written by hand withValueEquality. A step that builds a new collection returnsEquatableArray<T>. ReportDiagnostics(...)needs theCompilationto rebuild in-tree locations, which#pragmaand.editorconfigseverity depend on. ImplementIConditionalDiagnosticReporterand a reporter with nothing to report runs no step at all.
Rules of thumb for your models: no ISymbol, SyntaxNode, SyntaxTokenList, Location or Compilation (use strings and LocationKey; MINT001 enforces it on [GenerateEquality] models); return EquatableArray<T> from every step that builds a collection; and carry a LocationKey only on a model that will actually report a diagnostic, so that typing above a declaration does not invalidate it.
To prove it, MintPlayer.SourceGenerators.Testing runs a generator twice and reports the run reason of every output step.
Breaking changes in 12.0.0
The full list, with a migration guide, is in the changelog.
The value-comparer runtime is removed, with no backward compatibility. Models are value-equal by themselves now.
- Removed:
ValueComparer<T>,ComparerRegistry,[ValueComparer], the wholeMintPlayer.SourceGenerators.Tools.ValueComparersnamespace (every structural, tuple, dictionary, Symbol, Syntax and SourceText comparer, andReferenceEqualityComparer<T>),ICompilationCacheand its cache types,HashCodeCompat, theModuleInitializerAttributepolyfill, and the publicSettingsValueComparer. IncrementalGenerator.Initializeloses its third parameter. OverrideInitialize(IncrementalGeneratorInitializationContext context, IncrementalValueProvider<Settings> settingsProvider)and dropusing MintPlayer.SourceGenerators.Tools.ValueComparers;..WithComparer()/.WithNullableComparer()are no longer generated. Delete the calls. A step that builds a new collection returnsEquatableArray<T>instead of an array.- Registering a comparer (
ComparerRegistry.Register,JObjectValueComparer.Register()) is replaced by[UseEqualityComparer(typeof(...))]on the property. - Added:
ValueEqualityandEquatableArray<T>;LocationKey,PathSpec,PathSpecElementandSettingsimplementIEquatable<T>with consistent hashes.LocationKeyis sealed, andPathSpecElementequality now includesGenericTypeParameters.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.CSharp (>= 5.9.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 12.1.0 | 104 | 9/26/2026 | |
| 12.0.1 | 65 | 9/25/2026 | |
| 12.0.0 | 67 | 9/25/2026 | |
| 11.0.0 | 71 | 9/25/2026 | |
| 10.21.0 | 477 | 8/27/2026 | |
| 10.20.1 | 152 | 8/27/2026 | |
| 10.20.0 | 962 | 6/5/2026 | |
| 10.19.0 | 918 | 4/3/2026 | |
| 10.16.0 | 565 | 2/22/2026 | |
| 10.15.0 | 197 | 2/8/2026 | |
| 10.14.0 | 212 | 1/19/2026 | |
| 10.13.0 | 1,338 | 1/19/2026 | |
| 10.11.0 | 133 | 1/19/2026 | |
| 10.10.2 | 140 | 1/18/2026 | |
| 10.10.1 | 349 | 1/12/2026 | |
| 10.10.0 | 217 | 1/12/2026 | |
| 10.9.0 | 225 | 1/12/2026 | |
| 10.8.0 | 288 | 12/24/2025 | |
| 10.7.0 | 405 | 11/13/2025 | |
| 10.5.0 | 576 | 11/11/2025 |