bit Code Analyzers


Mistakes caught while you type, not after you ship

We care about code quality, and a rule that lives only in a review checklist gets missed sooner or later. bit Code Analyzers put our rules into the compiler instead: the build fails or warns, right in the editor, whether the code was written by a new teammate, a senior developer or an AI assistant.
dotnet add package Bit.CodeAnalyzers
Every bit Boilerplate project has it already.

Why rules belong in the compiler
  • The same rules for everyone. People and AI assistants get the same feedback, in the editor and in CI.
  • Right away. A mistake shows while the code is still on the screen, not in a review days later.
  • Cheaper than a bug. An app crash or a server that stops scaling is found in seconds, before it reaches production.
Async database calls
NonAsyncEFCoreMethodsUsageAnalyzer, warning.

Scalability matters: the same server should serve ten users today and ten thousand tomorrow. A synchronous Entity Framework Core call, like ToList, First, Count or SaveChanges, holds a thread for as long as the database takes to answer, doing nothing. Under load those waiting threads pile up until the server has none left for new requests, and it stops scaling however strong the machine is. The async twin of each call gives the thread back while the database works, so the same server handles far more requests at once:
var products = await dbContext.Products
    .Where(p => p.Price > 50)
    .ToListAsync(cancellationToken);
It looks at queries, DbContext, its Database, change tracking entries and context factories. A call inside an expression that EF Core turns into SQL, such as a Count() in a Select, isn't reported.
No async void
AsyncVoidMethodAnalyzer1 and AsyncVoidMethodAnalyzer2, errors.

An exception thrown in an async void method can't be caught by its caller, so it takes the whole app down. Return a Task instead:
async Task SaveAsync()
{
    await productController.Update(product, cancellationToken);
}
When it has to be async void, like an event handler, the whole body goes in a try/catch, and a statement left outside the try is reported too:
async void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{
    try
    {
        await RefreshAsync();
    }
    catch (Exception exp)
    {
        exceptionHandler.Handle(exp);
    }
}
Classes say who can use them
ClassWithoutModifierAnalyzer, error, with a quick fix.

A class or record with no modifier at all is reported, so who can use it is always written down rather than left to the language's default. The editor's quick fix adds public.
No internal accessors
PropertyAccessorsInternalModifierAnalyzer, error.

A property accessor can't be internal, as in public string Name { get; internal set; }. JSON deserialization, for one, skips such a setter without a word and leaves the property empty. Make the whole property internal, or the accessor private or protected.
Changing a rule's severity
Each rule's severity can be raised or lowered in .editorconfig, like any compiler rule:
[*.cs]
dotnet_diagnostic.NonAsyncEFCoreMethodsUsageAnalyzer.severity = error