Purview.SourceGeneratorFramework
1.0.0-prerelease.16
See the version list below for details.
dotnet add package Purview.SourceGeneratorFramework --version 1.0.0-prerelease.16
NuGet\Install-Package Purview.SourceGeneratorFramework -Version 1.0.0-prerelease.16
<PackageReference Include="Purview.SourceGeneratorFramework" Version="1.0.0-prerelease.16" />
<PackageVersion Include="Purview.SourceGeneratorFramework" Version="1.0.0-prerelease.16" />
<PackageReference Include="Purview.SourceGeneratorFramework" />
paket add Purview.SourceGeneratorFramework --version 1.0.0-prerelease.16
#r "nuget: Purview.SourceGeneratorFramework, 1.0.0-prerelease.16"
#:package Purview.SourceGeneratorFramework@1.0.0-prerelease.16
#addin nuget:?package=Purview.SourceGeneratorFramework&version=1.0.0-prerelease.16&prerelease
#tool nuget:?package=Purview.SourceGeneratorFramework&version=1.0.0-prerelease.16&prerelease
Purview.SourceGeneratorFramework
Core helpers, models, and MSBuild integration for writing incremental C# source generators with Roslyn.
Installation
dotnet add package Purview.SourceGeneratorFramework
What's included
CodeWriter— allocation-conscious helper for building generated C# source files with indentation, namespaces, type declarations, comments, and XML documentation.IncrementalPipeline— extension methods for composingIncrementalValueProvider<T>andIncrementalValuesProvider<T>pipelines, including attribute-based discovery, generation context creation, and disable-property checks.GenerationContext— a base context record that carries the RoslynCompilation, owns a configuredCodeWriter, and can replace it with a fresh writer using the same build-time settings.GeneratorResult<T>— a value-or-diagnostics result type for incremental source generator transforms.TypeValueObject,TargetSymbolDescriptor,EquatableArray<T>,DiagnosticInfo— reusable models for generator inputs and outputs.SymbolResolver,TypeHelpers,EmbeddedResources— helper classes for common symbol and resource tasks.AttributeDataModelGenerator— bundled source generator that emitsreadonly record structattribute parser models from[GenerateAttributeDataModel]declarations, eliminating repetitiveFromAttributeDataboilerplate. Supports manual mapping, auto-discovery, nested models, and inheritance matching.- MSBuild
.props/.targets— automatically addsglobal usingdirectives for the main namespaces and supports packaging source generators that reference this framework.
Usage
Reference the package from a Roslyn source generator project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Purview.SourceGeneratorFramework" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
</ItemGroup>
</Project>
Implement IIncrementalGenerator and use the framework helpers to build a pipeline:
using Microsoft.CodeAnalysis;
using Purview.SourceGeneratorFramework.Helpers;
using Purview.SourceGeneratorFramework.Models;
[Generator]
public sealed class MyGenerator : IIncrementalGenerator
{
static readonly TypeValueObject AttributeType = new("MyAttribute", "MyNamespace");
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var contextProvider = IncrementalPipeline.DefaultGenerationContextValueProvider(context);
var targets = IncrementalPipeline.ForAttributeWithMetadataName(
context,
AttributeType,
static (ctx, ct) => ctx.TargetSymbol.Name
);
context.RegisterSourceOutput(
targets.CombineWithContext(contextProvider),
static (spc, pair) =>
{
var (name, generationContext) = pair;
var writer = generationContext.CreateCodeWriter();
writer.WriteAutoGeneratedHeader(nameof(MyGenerator));
writer.WriteFileScopedNamespace("MyNamespace");
using (
writer.WriteClassScope(
new TypeDeclarationOptions(name)
{
Accessibility = TypeDeclarationAccessibility.Public,
IsStatic = true,
}
)
)
{
writer.WriteLine("// generated content");
}
spc.AddSource($"{name}.g.cs", writer.ToString());
}
);
}
}
See SourceGeneratorFramework.ExampleGenerator for a complete reference implementation.
Attribute model generation
The package includes AttributeDataModelGenerator, which generates readonly record struct parser models for .NET attributes. Instead of hand-writing FromAttributeData methods for every attribute you inspect, declare a readonly partial record struct with [GenerateAttributeDataModel] and let the generator fill in the Empty sentinel, FromAttributeData overloads, and property extraction logic.
using Microsoft.CodeAnalysis;
using Purview.SourceGeneratorFramework.Testing.Generators;
using System.ComponentModel.DataAnnotations;
namespace MySourceGenerator.Models;
[GenerateAttributeDataModel(typeof(ValidationAttribute), MatchByInheritance = true)]
public readonly partial record struct ValidationAttributeData(
[AttributeProperty] string? ErrorMessage,
[AttributeProperty] string? ErrorMessageResourceName,
[AttributeProperty] ITypeSymbol? ErrorMessageResourceType
);
[GenerateAttributeDataModel(typeof(RequiredAttribute))]
public readonly partial record struct RequiredAttributeData(
[AttributeProperty] bool AllowEmptyStrings,
[AttributeProperty(Source = AttributePropertySource.NestedModel)] ValidationAttributeData ValidationAttribute
);
Supported mapping sources:
NamedArgument— reads a named attribute propertyConstructorIndex— reads a constructor argument by positionConstructorName— reads a constructor argument by parameter nameNestedModel— populates a nested[GenerateAttributeDataModel]type
You can also target an attribute by fully-qualified name, which is useful when the attribute type is not available in the generator project (e.g., LengthAttribute in .NET 8+ or a self-generated attribute):
[GenerateAttributeDataModel("System.ComponentModel.DataAnnotations.RequiredAttribute")]
public readonly partial record struct RequiredAttributeData(
[AttributeProperty] bool AllowEmptyStrings
);
Enable auto-discovery with [GenerateAttributeDataModel(typeof(MyAttribute), AutoDiscover = true)] to generate properties for every constructor parameter and public named property. Auto-discovery requires the Type overload. Override defaults with [AttributeProperty(DefaultValue = ...)] or rely on inferred defaults from optional constructor parameters.
See SourceGeneratorFramework.Testing.Generators for full documentation and additional examples.
Structured member declarations
Methods, properties, fields, and constructors use immutable value-type declaration options. The
descriptor itself does not allocate an object; strings and ImmutableArray values are references
owned by the caller.
using (writer.WriteMethodScope(
new MethodDeclarationOptions(
"CreateAsync",
new TypeReferenceOptions("Task").MakeGeneric(new TypeReferenceOptions("Result"))
)
{
Accessibility = TypeDeclarationAccessibility.Public,
IsStatic = true,
IsAsync = true,
Parameters =
[
new("request", new TypeReferenceOptions("Request")),
new("cancellationToken", new TypeReferenceOptions("CancellationToken")),
],
}))
{
writer.WriteLine("return await ExecuteAsync(request, cancellationToken);");
}
writer.WriteProperty(
new PropertyDeclarationOptions("Name", new TypeReferenceOptions("string"))
{
Accessibility = TypeDeclarationAccessibility.Public,
HasSetter = true,
IsInitOnly = true,
Initializer = "string.Empty",
}
);
writer.WriteField(
new FieldDeclarationOptions("Instance", "Service")
{
Accessibility = TypeDeclarationAccessibility.Private,
IsStatic = true,
IsReadOnly = true,
Initializer = "new()",
}
);
WriteMethod folds long parameter lists automatically. WriteProperty supports automatic
accessors, expression bodies, and callback-generated getter/setter bodies. Structured methods and
constructors return a disposable body scope; callback overloads are available when a complete
member should be written in one call.
TypeDeclarationOptions.Kind supports classes, structs, record classes, record structs,
interfaces, enums, and delegates. The matching WriteInterface, WriteEnum, and WriteDelegate
helpers set the kind automatically. Interface inheritance is supplied through Interfaces, enums
can specify EnumUnderlyingType, and delegates use DelegateReturnType and
DelegateParameters. Generic delegate and interface constraints use the existing GenericTypes
model.
Attributes and parameters are structured as well; raw declaration fragments are not accepted:
new MethodDeclarationOptions("TryGet", "bool")
{
Accessibility = TypeDeclarationAccessibility.Public,
Attributes = [new("Obsolete")],
ReturnAttributes = [new("NotNull")],
Parameters =
[
new("value", "string?")
{
Modifier = ParameterModifier.Out,
Attributes =
[
new("NotNullWhen")
{
Arguments = [new("true")],
},
],
},
],
};
Every type, method, constructor, property, and field declaration exposes Attributes. Methods also
expose ReturnAttributes; parameters expose their own Attributes. AttributeArgumentOptions
supports positional arguments, constructor-named arguments using Name, and property assignments
using Name with IsPropertyAssignment = true.
All declaration type positions use TypeReferenceOptions. Nullability is therefore composed rather
than embedded in a type string:
var widget = new TypeReferenceOptions("Widget").Nullable();
var result = new TypeReferenceOptions("global::System.Collections.Generic.Dictionary")
.MakeGeneric(new TypeReferenceOptions("string"), widget)
.MakeArray()
.Nullable();
new ParameterDeclarationOptions(
"items",
new TypeReferenceOptions("global::System.Collections.Generic.List")
.MakeGeneric(widget)
)
{
IsNullable = true,
DefaultValue = "null",
};
For parameters, IsNullable = true is a convenience equivalent to calling .Nullable() on the
parameter's TypeReferenceOptions. If both are used, only one nullable annotation is emitted.
TypeReferenceOptions supports nullable annotations, nested constructed generics, open generic
arity, multidimensional and jagged arrays, pointers, and construction from Type, Roslyn
ITypeSymbol, or TypeValueObject. Arbitrary expressions such as default values and initializers
remain strings because they are expressions rather than type syntax.
Set TypeDeclarationOptions.IsAbstract for abstract classes or record classes. It takes precedence
over the default IsSealed = true, so callers do not need to disable sealing explicitly. Abstract
static classes and abstract non-class declarations are rejected.
Roslyn accessibility values can be converted in both directions:
TypeDeclarationAccessibility? declarationAccessibility =
symbol.DeclaredAccessibility.ToTypeDeclarationAccessibility();
Accessibility roslynAccessibility =
TypeDeclarationAccessibility.ProtectedInternal.ToRoslynAccessibility();
Both conversions are non-throwing. Roslyn NotApplicable maps to null; declaration File and
unknown future values map to Roslyn NotApplicable, because Roslyn models file-local types
separately from Accessibility.
Member spacing is tracked automatically at each declaration level:
- Consecutive fields are grouped without a blank line.
- A field followed by any other member has one blank line between them.
- Methods, constructors, properties, and nested types are separated from the following member by one blank line.
- An existing blank line is retained without adding another one.
Body-bearing declarations are registered when their returned scope is disposed. This means the next member is formatted correctly only after the preceding method, constructor, or type has been closed. If XML documentation or attributes were written after the previous member, the separator is inserted before that trivia so it remains attached to the declaration it documents.
Detecting undisposed CodeWriter scopes
CodeWriter can detect block or indentation scopes that have not been disposed before generated source is materialized. This validation is intended for development and automated tests and is disabled by default.
Enable it in the project consuming the source generator:
<PropertyGroup>
<PurviewSourceGeneratorFrameworkValidateCodeWriterScopes>true</PurviewSourceGeneratorFrameworkValidateCodeWriterScopes>
</PropertyGroup>
The default generation-context provider reads the property automatically:
var contextProvider =
IncrementalPipeline.DefaultGenerationContextValueProvider(context);
Create or reset the writer through the generation context so it inherits the setting:
var writer = generationContext.CreateCodeWriter();
GenerationContext.CodeWriter is initialized through CreateCodeWriter() in the context constructor. Calling CreateCodeWriter() later creates a fresh writer, assigns it to GenerationContext.CodeWriter, and returns that same instance. CodeWriter.ThrowOnUnclosedScopes is read-only; configuration is supplied through its constructor.
When validation is enabled, calling ToString() or implicitly converting a writer to Roslyn SourceText throws a CodeWriterScopeValidationException if OpenScopeCount is not zero. The dedicated exception allows generator error handlers to rethrow this framework invariant failure instead of reducing it to a generic generator diagnostic:
Cannot create generated source while 1 disposable scope(s) remain open. Dispose every scope before calling ToString().
Open scope #1: block — public sealed class Example
at MyGenerator.Generate(...)
The exception's OpenScopes collection exposes the scope kind, block header, and opening stack
trace programmatically. Stack traces are captured only when validation is enabled, avoiding this
diagnostic allocation during normal generator execution.
Both BlockScope and IndentScope are tracked. Prefer using or callback-based blocks so scopes are always closed:
writer.WriteBlock(
"if (value is null)",
body => body.WriteLine("return;")
);
Custom generation contexts
Scope validation is applied to every context returned by GenerationContextValueProvider. Custom
contexts do not need to accept or read the build property themselves:
public sealed record MyGenerationContext : GenerationContext
{
public MyGenerationContext(Compilation compilation)
: base(compilation)
{
}
}
Use the ordinary context-provider overload. The framework combines the compiler-visible property, configures the returned context, and replaces its default writer before publishing it downstream:
var contextProvider = IncrementalPipeline.GenerationContextValueProvider(
context,
static (compilation, cancellationToken) =>
{
cancellationToken.ThrowIfCancellationRequested();
return new MyGenerationContext(compilation);
}
);
Generators embedded in another package
If the generator assembly is embedded in a different NuGet package, the outer package must make the property compiler-visible to its consumers. Build assets from Purview.SourceGeneratorFramework are not automatically copied into the outer package.
Include this in a .props file imported by the outer package:
<Project>
<PropertyGroup>
<PurviewSourceGeneratorFrameworkValidateCodeWriterScopes
Condition="'$(PurviewSourceGeneratorFrameworkValidateCodeWriterScopes)' == ''"
>false</PurviewSourceGeneratorFrameworkValidateCodeWriterScopes>
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="PurviewSourceGeneratorFrameworkValidateCodeWriterScopes">
<Description>Throws when generated source is materialized while CodeWriter scopes remain undisposed.</Description>
</CompilerVisibleProperty>
</ItemGroup>
</Project>
Pack that file using the outer package's ID so NuGet imports it automatically:
<None
Include="Sdk\Sdk.props"
Pack="true"
PackagePath="buildTransitive\$(PackageId).props"
Visible="false"
/>
Framework-based generator tests enable scope validation by default. Disable it for a test only when partial source materialization is intentional:
new SourceGeneratorTestOptions
{
ValidateCodeWriterScopes = false,
};
Disabling a generator at build time
Use IncrementalPipeline.IsDisabledValueProvider to read an MSBuild analyzer-config property and skip generation when it is set to true:
<PropertyGroup>
<MyGenerator_Disable>true</MyGenerator_Disable>
</PropertyGroup>
var isDisabled = IncrementalPipeline.IsDisabledValueProvider(context, "MyGenerator_Disable");
License
This project is licensed under the MIT license.
| 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
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Purview.SourceGeneratorFramework:
| Package | Downloads |
|---|---|
|
Purview.SourceGeneratorFramework.Testing
Purview SourceGeneratorFramework libraries for building and testing incremental C# source generators. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-prerelease.26 | 0 | 8/29/2026 |
| 1.0.0-prerelease.25 | 57 | 8/27/2026 |
| 1.0.0-prerelease.24 | 65 | 8/19/2026 |
| 1.0.0-prerelease.23 | 66 | 8/19/2026 |
| 1.0.0-prerelease.22 | 68 | 8/19/2026 |
| 1.0.0-prerelease.21 | 66 | 8/18/2026 |
| 1.0.0-prerelease.20 | 71 | 8/18/2026 |
| 1.0.0-prerelease.19 | 72 | 8/17/2026 |
| 1.0.0-prerelease.18 | 73 | 8/16/2026 |
| 1.0.0-prerelease.17 | 65 | 8/14/2026 |
| 1.0.0-prerelease.16 | 65 | 8/13/2026 |
| 1.0.0-prerelease.15 | 66 | 8/13/2026 |
| 1.0.0-prerelease.14 | 61 | 8/13/2026 |
| 1.0.0-prerelease.13 | 67 | 8/13/2026 |
| 1.0.0-prerelease.12 | 69 | 8/12/2026 |
| 1.0.0-prerelease.11 | 66 | 8/12/2026 |
| 1.0.0-prerelease.10 | 61 | 8/12/2026 |
| 1.0.0-prerelease.9 | 76 | 8/11/2026 |
| 1.0.0-prerelease.7 | 69 | 8/9/2026 |
| 1.0.0-prerelease.6 | 66 | 8/8/2026 |