Skip to content
Purview Dev logo

Analyzers

Stable SourceGenerator Framework Reviewed 2026-10-21 purview-dev/sourcegenerator-framework Star on GitHub 3 analyzers code-generation compile-time compiler csharp developer-experience developer-tools devex dotnet incremental-generator metaprogramming nuget roslyn source-generator source-generators testing

The Purview.SourceGeneratorFramework package includes the Purview.SourceGeneratorFramework.Analyzers assembly as an analyzer asset, together with the Purview.SourceGeneratorFramework.CodeFixers code fix providers. The diagnostics are enabled automatically when you reference Purview.SourceGeneratorFramework from a source generator project.

The analyzer and code-fix assemblies are built as Roslyn components (IsRoslynComponent = true) and packed into the Purview.SourceGeneratorFramework package under analyzers/dotnet/cs/. Because they are not separately packable NuGet packages, they are documented here rather than in a standalone README.

The analyzers enforce two families of rules:

  • Incremental generator best practice — PSGFR11–PSGFR33, covering pipeline design, CodeWriter usage, Roslyn component discovery, and extension-class conventions.
  • C# 14 extension-member conventions — PSGFR34–PSGFR38, plus the associated ReorganizeExtensionClassCodeFixProvider and ConvertToExtensionBlockCodeFixProvider.

These are MSBuild diagnostics rather than compiler analyzers, so they are not tracked in AnalyzerReleases.*.md:

CodeRaised bySummary
PSGF0001Purview.BuildSdkA Roslyn component did not produce (or did not declare) a source-generator analyzer file.
PSGF0003Purview.SourceGeneratorFrameworkA PurviewGeneratorVisibleProperty is not compiler-visible in the declaring project or its Sdk/build/Sdk/buildTransitive assets, so consumers cannot read build_property.<Name>.
PRSGD0005Purview.BuildSdkA file in the returned analyzer closure references an assembly that is neither part of the closure nor a compiler-host assembly.
PSGFR41Purview.SourceGeneratorFramework merge toolA component’s public surface exposes framework types that the merge internalizes. Raised by the compiler analyzer of the same id at design time and by the merge pass as a message (default), warning or error.
PSGFR43Purview.SourceGeneratorFramework merge toolThe merged analyzer still exposes a public framework type, so it is not self-contained. Always an error; the merge fails with exit code 5.

PSGFR41 is deliberately shared with the compiler analyzer of the same id: the merge reports the same finding. The merge’s blocked code is PSGFR43, not PSGFR42, because PSGFR42 belongs to the compiler-analyzer family (the undisposed test-harness result rule).

Opt out with PurviewSourceGeneratorFrameworkAnalyzerValidation=false (PRSGD0005) or PurviewSourceGeneratorFrameworkGeneratorPropertyValidation=false (PSGF0003).

RuleSummary
PSGFR11Prefer SyntaxProvider.ForAttributeWithMetadataName over CreateSyntaxProvider for attribute-based detection.
PSGFR12Use IIncrementalGenerator / RegisterSourceOutput instead of ISourceGenerator.
PSGFR14Avoid RegisterImplementationSourceOutput unless implementation-only output is required.
PSGFR15Pipeline model collection members should use sequence equality (e.g. EquatableArray<T>).
PSGFR16Prefer the nullable-context Nullable()/MakeNullable() overload so annotations honour the target compilation.
PSGFR17Consume CodeWriter scope-returning methods (...Scope, IndentedScope) with using.
PSGFR18Prefer structured declaration APIs (Class, Method, Property, Field) over raw declaration text.
PSGFR19Prefer structured statement APIs (Return, MethodCall, Throw, Assignment, Using, Comment) over raw statement text.
PSGFR20Prefer the minimal CodeWriter overloads over constructing *DeclarationOptions values manually.
PSGFR21Prefer HashDefines/HashDefinesScope for #if/#endif conditional-compilation directives.
PSGFR22Prefer PragmaDisable/OpenPragmasScope for #pragma warning directives.
PSGFR23Prefer structured IfBlock/ElseIf/Else over raw if block text.
PSGFR24CodeFixProvider is not marked [ExportCodeFixProvider]; Visual Studio will never discover it.
PSGFR25DiagnosticAnalyzer is not marked [DiagnosticAnalyzer]; it will never run.
PSGFR26A generator type is not marked [Generator]; it will never run.
PSGFR27A Roslyn component type is not public; the compiler host cannot instantiate it.
PSGFR28FixableDiagnosticIds references a diagnostic ID no analyzer in the compilation produces; the fix will never be shown.
PSGFR29Do not embed a CodeWriter in a string; use XmlCommentWriter.XmlInlineCode instead.
PSGFR30Prefer static lambdas in incremental pipeline methods so the compiler never allocates a closure on the per-item hot path.
PSGFR31Prefer GeneratorAttributeSyntaxContext.TargetSymbol over SemanticModel.GetDeclaredSymbol(ctx.TargetNode).
PSGFR32Avoid NormalizeWhitespace when generating source; use an indented text writer such as CodeWriter.
PSGFR33Pipeline models must not retain Roslyn objects (ISymbol, SyntaxNode, Location, …); extract the information into value types.
PSGFR34Prefer C# 14 extension(Receiver) blocks over classic static this-parameter extension methods.
PSGFR35Extension class name must match the extended type ({Receiver}Extensions).
PSGFR36Extension classes must be placed in the extended type’s namespace under an Extensions folder.
PSGFR37One extension class per receiver type; split classes that extend multiple types.
PSGFR38Extension classes should carry [EditorBrowsable(EditorBrowsableState.Never)].
PSGFR39A non-packable Roslyn component that explicitly opts out of the default self-contained analyzer output (PurviewMergeSourceGeneratorFrameworkForAnalyzerFiles=false) while embedding the framework, otherwise the package embeds the loose framework DLL under analyzers/.
PSGFR40In Roslyn components (IsRoslynComponent=true), reference SGF types as inline code (<c>Type</c>) instead of a cref: copied documentation must not depend on cref resolution.
PSGFR41In Roslyn components whose framework implementation is merged, a public member (or generic constraint) whose signature references an SGF type: the merge internalizes every SGF type, so the signature is left referring to an internal type. Make the member or its declaring type non-public.
PSGFR42A disposable Testing-harness result (DriverRunResult) is produced and never disposed, leaking the collectible AssemblyLoadContext and MetadataLoadContext of a compiled run. Reported as a warning when the run syntactically compiles to an assembly (a Compile options member or CompileToAssembly = true), otherwise as info. Awaiting an assertion, or calling a helper that is handed the result — an extension on it, or a method taking it as a parameter — is not a production: both hand the same instance back, so only a task-like await (the shape the harness runners return) or a genuine producer is reported.
PSGFR44A throw in code that runs during source generation. An exception escaping a generator is reported as CS8785 in every consuming build and IDE, and the generator produces no output at all for that compilation — so the consumer also sees every “type or namespace not found” error that follows from the missing files, with a stack trace through the generator’s internals rather than a diagnostic on their own code. Report a diagnostic instead: carry it on GeneratorResult<T> as a ReportableDiagnostic, or call SourceProductionContext.ReportDiagnostic. Two scopes are checked — anything inside a type implementing IIncrementalGenerator (the Initialize body, the pipeline lambdas, and the emit helpers hanging off it), and any lambda handed to a pipeline method wherever it is written. Cancellation is unaffected: CancellationToken.ThrowIfCancellationRequested() is a method call, not a throw, and Roslyn expects that exception to propagate.

Type-library and attribute-model diagnostics

Section titled “Type-library and attribute-model diagnostics”

The bundled generators carry their own diagnostic families, reported by the TypeLibraryValidationAnalyzer (TLB0001–TLB0019) and the attribute-data-model validation analyzers. These are documented on their feature pages:

Code fix providers ship in the Purview.SourceGeneratorFramework.CodeFixers assembly and cover the analyzer rules above, including:

  • AddGeneratorAttributeCodeFixProvider — adds the missing [Generator] attribute (PSGFR26).
  • AddDiagnosticAnalyzerAttributeCodeFixProvider — adds [DiagnosticAnalyzer] (PSGFR25).
  • AddExportCodeFixProviderAttributeCodeFixProvider — adds [ExportCodeFixProvider] (PSGFR24).
  • MakeRoslynComponentPublicCodeFixProvider — makes the component type public (PSGFR27).
  • RemoveOrphanedFixableDiagnosticIdCodeFixProvider — removes unused fixable diagnostic IDs (PSGFR28).
  • PreferTargetSymbolCodeFixProvider — switches to TargetSymbol (PSGFR31).
  • PreferStaticLambdaCodeFixProvider — makes pipeline lambdas static (PSGFR30).
  • PreferNullableContextOverloadCodeFixProvider — adds the generation context to Nullable() / MakeNullable() calls, including project-wide “Fix all” support (PSGFR16).
  • PipelineModelReferenceEqualityCollectionCodeFixProvider — wraps collection members for sequence equality (PSGFR15).
  • PreferStructuredCodeWriterIfBlockCodeFixProvider — rewrites raw if/else if/else block text to the structured IfBlock/ElseIf/Else APIs (PSGFR23).
  • CodeWriterToStringCodeFixProvider — replaces embedded CodeWriter string interpolation (PSGFR29).
  • PreferInlineCodeForFrameworkCrefCodeFixProvider — rewrites SGF XML doc cref targets to inline code (<c>Type</c>) (PSGFR40).
  • MakeComponentSurfaceNonPublicCodeFixProvider — makes the exposing member, or its declaring type, non-public (PSGFR41).
  • DisposeTestResultCodeFixProvider — turns an undisposed test-harness result declaration into a using declaration (PSGFR42).
  • MakeTypeLibrarySpecNonPublicCodeFixProvider — declares a type-library spec non-public in a merged component (TLB0021).
  • AttributeDataModelSymbolPropertyCodeFixProvider — fixes attribute-data-model symbol properties.
  • ReorganizeExtensionClassCodeFixProvider — renames (PSGFR35), splits multi-receiver classes (PSGFR37), moves the class under Extensions/{ReceiverNamespace}/, and updates referencing files (PSGFR36).
  • ConvertToExtensionBlockCodeFixProvider — converts classic methods to C# 14 extension blocks (PSGFR34).
  • AddExtensionClassMetadataCodeFixProvider — adds [EditorBrowsable(EditorBrowsableState.Never)] to extension classes (PSGFR38).
  • Type-library fixes — TypeLibraryMemberAccessibilityCodeFixProvider, TypeLibraryMarkerDefaultInitializerCodeFixProvider, MakeTypeLibrarySpecPartialCodeFixProvider, RenameTypeLibrarySpecCodeFixProvider, and TypeLibraryMemberTypeCodeFixProvider.

See Guide.md for the extension-class conventions the PSGFR34–PSGFR38 rules enforce.

The compiler host only loads a source generator, diagnostic analyzer, or code fix provider when three conditions hold. Missing any one means the component is silently ignored:

  1. The type is public (PSGFR27).
  2. The type is decorated — [Generator] (PSGFR26), [DiagnosticAnalyzer] (PSGFR25), or [ExportCodeFixProvider] (PSGFR24).
  3. The assembly is loaded as an analyzer — packed under analyzers/dotnet/cs/ in a package, or referenced with OutputItemType="Analyzer" in a project reference.

A code fix provider also only appears when the diagnostic ID in FixableDiagnosticIds is actually produced by an analyzer loaded alongside it (PSGFR28). Visual Studio MEF-composes fix providers when the analyzer set loads, so after adding or updating a fixer assembly you must restart Visual Studio or reload the project for the fixes to appear.

This documentation is part of the MIT-licensed Purview.SourceGeneratorFramework project.