SourceGenerator Framework
Getting Started
Section titled “Getting Started”Install
Section titled “Install”dotnet add package Purview.SourceGeneratorFrameworkReference 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>Referencing a generator project
Section titled “Referencing a generator project”Roslyn must receive both a source-generator assembly and its framework runtime dependency as analyzer inputs. Use an analyzer project reference:
<ProjectReference Include="..\MyGenerator\MyGenerator.csproj" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>The Purview SDK automatically invokes GetSourceGeneratorAnalyzerFiles, which returns the generator
assembly without adding it to the consuming application’s runtime references. By default the framework
returns a merged, self-contained generator from its intermediate output, so no loose
Purview.SourceGeneratorFramework.dll is needed and GASF-based packages stay self-contained. The
generator’s own bin output stays unmerged, so its in-process test harness retains shared framework
type identity. Specifying Targets="GetSourceGeneratorAnalyzerFiles" explicitly remains supported but
is not required. Set PurviewMergeSourceGeneratorFrameworkForAnalyzerFiles to false only when the
unmerged assembly + loose framework DLL shape is required (see Packaging.md).
A component that references another component
Section titled “A component that references another component”A code-fix component can reference the generator component normally (ProjectReference,
ReferenceOutputAssembly not false) when it needs the generator’s internal diagnostic identity.
The generator’s analyzer artifact is the merged, self-contained assembly, while its bin output
stays unmerged; the framework assembly is copied beside that bin output, and assembly-reference
resolution copies it beside the dependent component’s bin output, so that folder is self-sufficient.
A project that references a component only as an analyzer never receives it. The dependent
component’s analyzer closure includes the referenced component’s merged artifact — it is never
IL-merged a second time, which would duplicate its types. See
Packaging.md.
Declare each MSBuild property the generator reads with
<PurviewGeneratorVisibleProperty Include="MyGenerator_Disable" />; PSGF0003 fails the build when
the property is not compiler-visible.
Referencing a generator from its test project
Section titled “Referencing a generator from its test project”A test project can need the source-generator project in two different roles at the same time:
- as an analyzer, so the generator runs against the test project and its generated attributes and other types can be used directly by test source files; and
- as a normal assembly reference, so the test code can name and instantiate the generator type
through
Purview.SourceGeneratorFramework.Testing.
Add two project references with deliberately different metadata:
<ItemGroup> <!-- Run the generator against this test project. --> <ProjectReference Include="..\MyGenerator\MyGenerator.csproj" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<!-- Make MyGenerator available to the test code and test runner. --> <ProjectReference Include="..\MyGenerator\MyGenerator.csproj" PrivateAssets="all" ReferenceOutputAssembly="true" /></ItemGroup>Do not put OutputItemType="Analyzer" on the normal reference. The Purview SDK automatically
uses GetSourceGeneratorAnalyzerFiles for the analyzer reference, which by default returns the
generator’s merged, self-contained assembly from its intermediate output, so the generator loads
correctly with no extra runtime dependencies. The normal assembly reference resolves to the
generator’s unmerged bin output, so the two roles do not interfere and the test compilation sees no
duplicate framework types.
Because the second reference is a normal assembly reference, the generator’s Roslyn dependencies
also become visible to the test compilation. For a multi-target test project, build the generator
against the Roslyn version that supports its API usage and is compatible with the oldest test target.
This framework is built against Roslyn 5.0 (C# 14 / .NET 10 generation), which ships net8.0 and
net9.0 package assets, so a .NET 8–.NET 10 test matrix still loads it. Compiler hosts must be
Roslyn 5.0 or later (.NET 10 SDK / Visual Studio 2026). Do not centrally pin
System.Collections.Immutable to a newer runtime version merely to make the generator load.
Write a generator
Section titled “Write a generator”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 TypeIdentity AttributeType = new("MyAttribute", "MyNamespace");
public void Initialize(IncrementalGeneratorInitializationContext context) { var contextProvider = IncrementalPipeline.DefaultGenerationContextValueProvider<MyGenerator>(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.AutoGeneratedHeader(); writer.FileScopedNamespace("MyNamespace"); writer.Class( name, TypeDeclarationAccessibility.Public, options => options with { IsStatic = true }, body => body.Comment("generated content") );
spc.AddSource($"{name}.g.cs", writer.ToString()); } ); }}See the SourceGeneratorFramework.ExampleGenerator
reference implementation for a complete end-to-end sample, and
SourceGeneratorFramework.ExampleGenerator.CodeFixers
for a companion code-fix sample.
Test the generator
Section titled “Test the generator”Reference the testing package and run the generator against a snippet of C#:
dotnet add package Purview.SourceGeneratorFramework.Testingusing Purview.SourceGeneratorFramework.Testing;
public class MyGeneratorTests{ [Test] public async Task GeneratesExpectedSource() { var source = """ [MyNamespace.MyAttribute] public partial class MyClass { } """;
var runner = new SourceGeneratorTestRunner<MyGenerator>(); var result = await runner.RunAsync(source);
result.AssertNoCompilationErrors(); var generated = result.AssertSingleGeneratedSource(); }}Use the TUnit integration for ready-made test base classes and fluent assertions:
dotnet add package Purview.SourceGeneratorFramework.Testing.TUnit