DotnetBpa 1.0.0

dotnet tool install --global DotnetBpa --version 1.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local DotnetBpa --version 1.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=DotnetBpa&version=1.0.0
                    
nuke :add-package DotnetBpa --version 1.0.0
                    

dotnet-bpa

CI

A Roslyn-powered CLI that analyzes C# code for best-practice issues — with SARIF output and an optional AI-assisted review pass.

dotnet-bpa runs as a .NET global tool. It parses your C# with the Roslyn compiler API and flags patterns that cause real production bugs: async void, blocking on async code, swallowed exceptions, and broken encapsulation.


The problem

Some of the most damaging .NET bugs never produce a compiler error. An async void method that throws crashes the process or silently swallows the failure; a stray .Result deadlocks under a synchronization context; an empty catch hides the one log line you needed at 2 a.m. These survive code review because they look harmless. dotnet-bpa catches them deterministically, before they ship.

What it checks

Rule Severity What it catches
BPA001 error async void methods (except event handlers) — exceptions escape and completion can't be awaited
BPA002 warning Blocking on async via .Result, .Wait(), .GetAwaiter().GetResult() — deadlock risk under a captured context
BPA003 warning Empty catch blocks that swallow exceptions silently
BPA004 info Public mutable fields that break encapsulation

The rule set is small on purpose: every rule earns its place by catching a bug that costs real debugging time. New rules are easy to add — implement IAnalyzerRule and register it.

Install

dotnet tool install -g DotnetBpa

Usage

# Analyze a directory recursively
dotnet-bpa analyze ./src

# Analyze one file and emit SARIF for GitHub code scanning
dotnet-bpa analyze ./src/OrderService.cs --sarif results.sarif

# Add an optional AI-assisted review (requires an API key, see below)
dotnet-bpa analyze ./src --ai

Example output

src/OrderProcessor.cs(8,23): error BPA001: Method 'Process' is declared 'async void'. Exceptions cannot be caught by callers and completion cannot be awaited.
    -> Change the return type to 'async Task'. Reserve 'async void' only for event handlers.
src/OrderProcessor.cs(12,36): warning BPA002: Blocking call '.Result' on a possibly-async member can deadlock.
    -> Await the operation instead (e.g. 'await task') and make the caller async.

Found 4 issue(s): 1 error, 2 warning, 1 info.

The CLI exits with code 1 when any error-severity issue is found, so it can fail a CI build.

Optional: AI-assisted review

The --ai flag adds a second, qualitative pass that sends each file to the Claude API for natural-language suggestions that complement the deterministic rules. It is entirely optional — the core analysis runs fully offline without it.

To enable it, set an API key:

export ANTHROPIC_API_KEY=sk-ant-...
dotnet-bpa analyze ./src --ai

If the key is missing, the AI pass is skipped with a notice and the deterministic analysis still runs.

Use in CI (GitHub Actions)

- name: Install dotnet-bpa
  run: dotnet tool install -g DotnetBpa
- name: Run best-practice analysis
  run: dotnet-bpa analyze ./src --sarif bpa.sarif
- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: bpa.sarif

The SARIF results then appear in the repository's Security → Code scanning tab.

How it works

┌─────────────┐   parse    ┌──────────────┐   run rules   ┌───────────────┐
│  .cs files  │ ─────────► │ Roslyn syntax │ ────────────► │  Diagnostics  │
│ (recursive) │            │     tree      │               │ (console +    │
└─────────────┘            └──────────────┘               │  SARIF + AI)  │
                                                            └───────────────┘

Each rule implements IAnalyzerRule and inspects the syntax tree independently, so the analyzer is just a thin loop over a rule list. See docs/adding-a-rule.md to add your own.

Limitations

dotnet-bpa uses syntax-only analysis (no semantic model or type resolution). This is intentional: it keeps the tool fast and dependency-free, and Roslyn's full semantic pipeline isn't needed to catch the targeted patterns reliably.

The practical consequence: BPA002 (blocking on async) flags .Result and .Wait() on any type, not only Task. False positives on non-async types are rare in practice but possible. A // dotnet-bpa disable BPA002 suppression mechanism is planned.

Build from source

git clone https://github.com/Heshamsoliman7/dotnet-bpa.git
cd dotnet-bpa
dotnet build
dotnet test

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.0.0 193 6/29/2026