Purview.SourceGeneratorFramework 1.0.0-prerelease.16

This is a prerelease version of Purview.SourceGeneratorFramework.
There is a newer prerelease version of this package available.
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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Purview.SourceGeneratorFramework" Version="1.0.0-prerelease.16" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Purview.SourceGeneratorFramework" Version="1.0.0-prerelease.16" />
                    
Directory.Packages.props
<PackageReference Include="Purview.SourceGeneratorFramework" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Purview.SourceGeneratorFramework --version 1.0.0-prerelease.16
                    
#r "nuget: Purview.SourceGeneratorFramework, 1.0.0-prerelease.16"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Purview.SourceGeneratorFramework@1.0.0-prerelease.16
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Purview.SourceGeneratorFramework&version=1.0.0-prerelease.16&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Purview.SourceGeneratorFramework&version=1.0.0-prerelease.16&prerelease
                    
Install as a Cake Tool

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 composing IncrementalValueProvider<T> and IncrementalValuesProvider<T> pipelines, including attribute-based discovery, generation context creation, and disable-property checks.
  • GenerationContext — a base context record that carries the Roslyn Compilation, owns a configured CodeWriter, 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 emits readonly record struct attribute parser models from [GenerateAttributeDataModel] declarations, eliminating repetitive FromAttributeData boilerplate. Supports manual mapping, auto-discovery, nested models, and inheritance matching.
  • MSBuild .props / .targets — automatically adds global using directives 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 property
  • ConstructorIndex — reads a constructor argument by position
  • ConstructorName — reads a constructor argument by parameter name
  • NestedModel — 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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
Loading failed