Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Create Roslyn-based analyzers and code-fix providers for detecting Web Forms migration patterns. Covers DiagnosticAnalyzer architecture, diagnostic ID conventions (BWFC001–BWFC099), CodeFixProvider authoring, testing with CSharpAnalyzerTest/CSharpCodeFixTest, and the analyzer release tracking files. Use when adding a new migration rule, creating a code-fix suggestion, or debugging analyzer behavior.
.claude/skills/fritzandfriends-analyzer-development/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 52% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 27% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 95% | 0% |
This skill covers creating Roslyn analyzers and code-fix providers in the BlazorWebFormsComponents.Analyzers project.
The analyzer project ships as a NuGet analyzer package alongside the main BWFC library. Analyzers detect Web Forms patterns in user code and suggest BWFC replacements.
src/BlazorWebFormsComponents.Analyzers/
├── *Analyzer.cs — DiagnosticAnalyzer implementations
├── *CodeFixProvider.cs — Optional companion code-fix providers
├── SyntaxExtensions.cs — Shared Roslyn helper methods
├── AnalyzerReleases.Shipped.md — Released diagnostic IDs
└── AnalyzerReleases.Unshipped.md — In-progress diagnostic IDs
src/BlazorWebFormsComponents.Analyzers.Test/
├── *AnalyzerTests.cs — Analyzer unit tests
├── *CodeFixTests.cs — Code-fix unit tests
└── AllAnalyzersIntegrationTests.cs — Cross-cutting integration tests| Analyzer | Diagnostic | Detects | |----------|------------|---------| | EventHandlerSignatureAnalyzer | BWFC001+ | Web Forms event handler signatures | | FindControlUsageAnalyzer | BWFC0xx | FindControl() calls | | IsPostBackUsageAnalyzer | BWFC0xx | IsPostBack property usage | | ViewStateUsageAnalyzer | BWFC0xx | ViewState["key"] access | | ViewStatePropertyPatternAnalyzer | BWFC0xx | ViewState-backed property patterns | | SessionUsageAnalyzer | BWFC0xx | Session["key"] access | | ResponseObjectUsageAnalyzer | BWFC0xx | Response.Redirect, Response.Write | | ResponseRedirectAnalyzer | BWFC0xx | Response.Redirect specifically | | RequestObjectUsageAnalyzer | BWFC0xx | Request.QueryString, Request.Form | | PageClientScriptUsageAnalyzer | BWFC0xx | ClientScript.Register* | | ScriptManagerUsageAnalyzer | BWFC0xx | ScriptManager usage | | RunatServerAnalyzer | BWFC0xx | runat="server" in markup | | NonSerializableViewStateAnalyzer | BWFC0xx | Non-serializable types in ViewState | | MissingParameterAttributeAnalyzer | BWFC0xx | Missing [Parameter] on public props | | RequiredAttributeAnalyzer | BWFC0xx | Required HTML attributes | | IPostBackEventHandlerUsageAnalyzer | BWFC0xx | IPostBackEventHandler implementation |
csharpusing Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; namespace BlazorWebFormsComponents.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class MyPatternAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "BWFC0XX"; // Pick next available ID private static readonly DiagnosticDescriptor Rule = new( id: DiagnosticId, title: "Web Forms pattern detected", messageFormat: "'{0}' is a Web Forms pattern — use '{1}' instead", category: "Migration", defaultSeverity: DiagnosticSeverity.Info, // Info, Warning, or Error isEnabledByDefault: true, description: "Detects a Web Forms pattern that should be replaced with a BWFC equivalent."); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); // Register for the syntax node types you want to analyze context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression); } private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { var invocation = (InvocationExpressionSyntax)context.Node; // Check if this matches the Web Forms pattern if (invocation.Expression is MemberAccessExpressionSyntax memberAccess && memberAccess.Name.Identifier.Text == "TargetMethod") { var diagnostic = Diagnostic.Create( Rule, invocation.GetLocation(), "TargetMethod", // {0} in messageFormat "BwfcAlternative"); // {1} in messageFormat context.ReportDiagnostic(diagnostic); } } }
csharpusing Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using System.Collections.Immutable; using System.Composition; namespace BlazorWebFormsComponents.Analyzers; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MyPatternCodeFixProvider))] [Shared] public class MyPatternCodeFixProvider : CodeFixProvider { public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(MyPatternAnalyzer.DiagnosticId); public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken); var diagnostic = context.Diagnostics[0]; var diagnosticSpan = diagnostic.Location.SourceSpan; var node = root?.FindNode(diagnosticSpan); if (node == null) return; context.RegisterCodeFix( CodeAction.Create( title: "Replace with BWFC equivalent", createChangedDocument: c => FixAsync(context.Document, node, c), equivalenceKey: nameof(MyPatternCodeFixProvider)), diagnostic); } private static async Task<Document> FixAsync( Document document, SyntaxNode node, CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken); // Create the replacement node var newNode = SyntaxFactory.ParseExpression("BwfcAlternative()"); var newRoot = root!.ReplaceNode(node, newNode); return document.WithSyntaxRoot(newRoot); } }
Add the new diagnostic ID to AnalyzerReleases.Unshipped.md:
markdown### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------ BWFC0XX | Migration | Info | Detects MyPattern usage
csharpusing Microsoft.CodeAnalysis.Testing; using Xunit; using VerifyAnalyzer = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest< BlazorWebFormsComponents.Analyzers.MyPatternAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>; namespace BlazorWebFormsComponents.Analyzers.Test; public class MyPatternAnalyzerTests { // Stub types the analyzer needs to compile against private const string ComponentStubs = @" namespace System.Web.UI { public class Page { public void TargetMethod() { } } }"; [Fact] public async Task DetectsTargetMethod_ReportsDiagnostic() { var test = new VerifyAnalyzer { TestCode = ComponentStubs + @" class TestPage : System.Web.UI.Page { void Test() { {|#0:TargetMethod()|}; } }", ExpectedDiagnostics = { new DiagnosticResult(MyPatternAnalyzer.DiagnosticId) .WithLocation(0) .WithArguments("TargetMethod", "BwfcAlternative") } }; await test.RunAsync(); } [Fact] public async Task NoTargetMethod_NoDiagnostic() { var test = new VerifyAnalyzer { TestCode = @" class MyClass { void Test() { var x = 1 + 2; } }" }; await test.RunAsync(); } }
bash# Run analyzer tests dotnet test src/BlazorWebFormsComponents.Analyzers.Test --nologo # Run a specific test dotnet test src/BlazorWebFormsComponents.Analyzers.Test --filter "MyPatternAnalyzerTests"
BWFC001 through BWFC099Migration (for Web Forms → Blazor patterns)Info — Pattern detected, BWFC equivalent available (most common)Warning — Pattern that will cause runtime issues if not addressedError — Pattern that prevents compilation in BlazorThe analyzer DLL is packed into the analyzers/dotnet/cs path automatically via the .csproj:
xml<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
Consumers get analyzers automatically when they reference the BWFC NuGet package.
[DiagnosticAnalyzer(LanguageNames.CSharp)] attributeBWFC0XX convention (pick next available)Initialize calls ConfigureGeneratedCodeAnalysis and EnableConcurrentExecution[ExportCodeFixProvider] and [Shared] attributesAnalyzerReleases.Unshipped.mddotnet test src/BlazorWebFormsComponents.Analyzers.Test passesOther measured skills in the registry, with their headline benchmark lift.