DotnetBpa 1.0.0
dotnet tool install --global DotnetBpa --version 1.0.0
dotnet new tool-manifest
dotnet tool install --local DotnetBpa --version 1.0.0
#tool dotnet:?package=DotnetBpa&version=1.0.0
nuke :add-package DotnetBpa --version 1.0.0
dotnet-bpa
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
| Product | Versions 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. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 193 | 6/29/2026 |