Skip to content

Getting Started

Preview SourceGenerator Framework Reviewed 2026-09-16 purview-dev/sourcegenerator-framework analyzers code-generation compile-time compiler csharp developer-experience developer-tools devex dotnet incremental-generator metaprogramming nuget roslyn source-generator source-generators testing
Terminal window
dotnet add package Purview.SourceGeneratorFramework

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>

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 both the generator and its framework dependency without adding either file to the consuming application’s runtime references. Specifying Targets="GetSourceGeneratorAnalyzerFiles" explicitly remains supported but is not required.

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 and supplies the generator’s runtime dependencies to Roslyn.

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.

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.

Reference the testing package and run the generator against a snippet of C#:

Terminal window
dotnet add package Purview.SourceGeneratorFramework.Testing
using 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:

Terminal window
dotnet add package Purview.SourceGeneratorFramework.Testing.TUnit