Analyzers
Analyzers
Section titled “Analyzers”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.
How the analyzers are shipped
Section titled “How the analyzers are shipped”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,CodeWriterusage, Roslyn component discovery, and extension-class conventions. - C# 14 extension-member conventions —
PSGFR34–PSGFR38, plus the associatedReorganizeExtensionClassCodeFixProviderandConvertToExtensionBlockCodeFixProvider.
Build-time validation diagnostics
Section titled “Build-time validation diagnostics”These are MSBuild diagnostics rather than compiler analyzers, so they are not tracked in
AnalyzerReleases.*.md:
| Code | Raised by | Summary |
|---|---|---|
PSGF0001 | Purview.BuildSdk | A Roslyn component did not produce (or did not declare) a source-generator analyzer file. |
PSGF0003 | Purview.SourceGeneratorFramework | A PurviewGeneratorVisibleProperty is not compiler-visible in the declaring project or its Sdk/build/Sdk/buildTransitive assets, so consumers cannot read build_property.<Name>. |
PRSGD0005 | Purview.BuildSdk | A file in the returned analyzer closure references an assembly that is neither part of the closure nor a compiler-host assembly. |
PSGFR41 | Purview.SourceGeneratorFramework merge tool | A 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. |
PSGFR43 | Purview.SourceGeneratorFramework merge tool | The 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).
Rule reference
Section titled “Rule reference”| Rule | Summary |
|---|---|
PSGFR11 | Prefer SyntaxProvider.ForAttributeWithMetadataName over CreateSyntaxProvider for attribute-based detection. |
PSGFR12 | Use IIncrementalGenerator / RegisterSourceOutput instead of ISourceGenerator. |
PSGFR14 | Avoid RegisterImplementationSourceOutput unless implementation-only output is required. |
PSGFR15 | Pipeline model collection members should use sequence equality (e.g. EquatableArray<T>). |
PSGFR16 | Prefer the nullable-context Nullable()/MakeNullable() overload so annotations honour the target compilation. |
PSGFR17 | Consume CodeWriter scope-returning methods (...Scope, IndentedScope) with using. |
PSGFR18 | Prefer structured declaration APIs (Class, Method, Property, Field) over raw declaration text. |
PSGFR19 | Prefer structured statement APIs (Return, MethodCall, Throw, Assignment, Using, Comment) over raw statement text. |
PSGFR20 | Prefer the minimal CodeWriter overloads over constructing *DeclarationOptions values manually. |
PSGFR21 | Prefer HashDefines/HashDefinesScope for #if/#endif conditional-compilation directives. |
PSGFR22 | Prefer PragmaDisable/OpenPragmasScope for #pragma warning directives. |
PSGFR23 | Prefer structured IfBlock/ElseIf/Else over raw if block text. |
PSGFR24 | CodeFixProvider is not marked [ExportCodeFixProvider]; Visual Studio will never discover it. |
PSGFR25 | DiagnosticAnalyzer is not marked [DiagnosticAnalyzer]; it will never run. |
PSGFR26 | A generator type is not marked [Generator]; it will never run. |
PSGFR27 | A Roslyn component type is not public; the compiler host cannot instantiate it. |
PSGFR28 | FixableDiagnosticIds references a diagnostic ID no analyzer in the compilation produces; the fix will never be shown. |
PSGFR29 | Do not embed a CodeWriter in a string; use XmlCommentWriter.XmlInlineCode instead. |
PSGFR30 | Prefer static lambdas in incremental pipeline methods so the compiler never allocates a closure on the per-item hot path. |
PSGFR31 | Prefer GeneratorAttributeSyntaxContext.TargetSymbol over SemanticModel.GetDeclaredSymbol(ctx.TargetNode). |
PSGFR32 | Avoid NormalizeWhitespace when generating source; use an indented text writer such as CodeWriter. |
PSGFR33 | Pipeline models must not retain Roslyn objects (ISymbol, SyntaxNode, Location, …); extract the information into value types. |
PSGFR34 | Prefer C# 14 extension(Receiver) blocks over classic static this-parameter extension methods. |
PSGFR35 | Extension class name must match the extended type ({Receiver}Extensions). |
PSGFR36 | Extension classes must be placed in the extended type’s namespace under an Extensions folder. |
PSGFR37 | One extension class per receiver type; split classes that extend multiple types. |
PSGFR38 | Extension classes should carry [EditorBrowsable(EditorBrowsableState.Never)]. |
PSGFR39 | A 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/. |
PSGFR40 | In 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. |
PSGFR41 | In 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. |
PSGFR42 | A 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. |
PSGFR44 | A 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 fixes
Section titled “Code fixes”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 toTargetSymbol(PSGFR31).PreferStaticLambdaCodeFixProvider— makes pipeline lambdasstatic(PSGFR30).PreferNullableContextOverloadCodeFixProvider— adds the generation context toNullable()/MakeNullable()calls, including project-wide “Fix all” support (PSGFR16).PipelineModelReferenceEqualityCollectionCodeFixProvider— wraps collection members for sequence equality (PSGFR15).PreferStructuredCodeWriterIfBlockCodeFixProvider— rewrites rawif/else if/elseblock text to the structuredIfBlock/ElseIf/ElseAPIs (PSGFR23).CodeWriterToStringCodeFixProvider— replaces embeddedCodeWriterstring interpolation (PSGFR29).PreferInlineCodeForFrameworkCrefCodeFixProvider— rewrites SGF XML doccreftargets 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 ausingdeclaration (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 underExtensions/{ReceiverNamespace}/, and updates referencing files (PSGFR36).ConvertToExtensionBlockCodeFixProvider— converts classic methods to C# 14extensionblocks (PSGFR34).AddExtensionClassMetadataCodeFixProvider— adds[EditorBrowsable(EditorBrowsableState.Never)]to extension classes (PSGFR38).- Type-library fixes —
TypeLibraryMemberAccessibilityCodeFixProvider,TypeLibraryMarkerDefaultInitializerCodeFixProvider,MakeTypeLibrarySpecPartialCodeFixProvider,RenameTypeLibrarySpecCodeFixProvider, andTypeLibraryMemberTypeCodeFixProvider.
See Guide.md for the extension-class conventions the
PSGFR34–PSGFR38 rules enforce.
Roslyn component discovery
Section titled “Roslyn component discovery”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:
- The type is public (
PSGFR27). - The type is decorated —
[Generator](PSGFR26),[DiagnosticAnalyzer](PSGFR25), or[ExportCodeFixProvider](PSGFR24). - The assembly is loaded as an analyzer — packed under
analyzers/dotnet/cs/in a package, or referenced withOutputItemType="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.
License
Section titled “License”This documentation is part of the MIT-licensed Purview.SourceGeneratorFramework project.