Skip to content

Roslyn Analyzers & Code Quality Rules

Granit.Analyzers provides Roslyn analyzers that enforce Granit conventions at compile time, with immediate feedback in the IDE (red squiggles). They complement runtime checks and architecture tests with zero-cost static analysis.

Terminal window
dotnet add package Granit.Analyzers

The package ships both analyzers and code fix providers. No additional configuration is required — rules activate automatically based on the project’s dependencies.

Rules enforcing modular monolith boundaries.

RuleSeverityCodeFixDescription
GRMOD001ErrorYesCross-module reference to internal type — use Contracts

GRMOD001 — Cross-module reference detection

Section titled “GRMOD001 — Cross-module reference detection”

Detects when code in one module references internal types from another module instead of using the .Contracts namespace.

Applies to: projects using the *.Modules.{ModuleName} namespace convention.

Opt-in: the analyzer only activates when *.Modules.* namespaces are present in the compilation. Projects without this convention pay zero runtime cost.

Allowed references:

  • Types in *.Modules.{Module}.Contracts and sub-namespaces
  • Types within the same module
  • Types outside *.Modules.* (framework, BCL, shared libraries)

Forbidden references:

  • Types in *.Modules.{OtherModule}.Domain
  • Types in *.Modules.{OtherModule}.Handlers
  • Types in *.Modules.{OtherModule}.Services
  • Any non-Contracts namespace of another module

CodeFix: if a type with the same name exists in the target module’s .Contracts namespace, the code fix suggests replacing the using directive.

// ❌ GRMOD001: InventoryService from module Inventory cannot be
// referenced from module Orders
using MyApp.Modules.Inventory.Services;
namespace MyApp.Modules.Orders.Handlers;
public class OrderHandler(InventoryService inventory) { }

Rules enforcing zero-downtime migration patterns (expand/migrate/contract).

RuleSeverityCodeFixDescription
GRMIGA001ErrorDropColumn requires a Contract-phase annotation
GRMIGA002ErrorRenameColumn is not zero-downtime safe
GRMIGA003WarningAddColumn NOT NULL without default risks table lock
GRMIGA004WarningAlterColumn type change requires Contract-phase annotation

Rules enforcing testability, secret management, and GDPR compliance.

RuleSeverityCodeFixDescription
GRSEC001WarningYesAvoid DateTime.Now/UtcNow — use IClock
GRSEC002WarningYesAvoid Guid.NewGuid() — use IGuidGenerator
GRSEC003ErrorPotential hardcoded secret detected
GRSEC004WarningYesAvoid direct IResponseCookies — use IGranitCookieManager
GRSEC010ErrorPII-indicative name used as metric tag key
GRSEC011ErrorPII-indicative placeholder in LoggerMessage template

Detects TagList initializers where the key (first string literal in each { "key", value } pair) matches a PII-indicative pattern: email, phone, ipAddress, username, ssn, passport, token, etc.

PII as metric tag keys causes both a GDPR violation (PII in telemetry backends) and a cardinality explosion (unbounded memory, high cost in Prometheus/Grafana).

// ❌ GRSEC010: Tag key 'email' matches a PII pattern
_counter.Add(1, new TagList
{
{ "email", user.Email },
{ "tenant_id", tenantId },
});

GRSEC011 — PII in LoggerMessage placeholders

Section titled “GRSEC011 — PII in LoggerMessage placeholders”

Detects [LoggerMessage] templates containing placeholders whose names match PII-indicative patterns. Supports both named (Message = "...") and positional ([LoggerMessage(id, Level, "...")]) constructor forms. Escaped braces ({{/}}) are correctly skipped.

PII in structured logs violates GDPR Art. 5 (data minimization) and exposes personal data to log aggregation systems (ELK, Loki, Application Insights).

// ❌ GRSEC011: PII placeholder {Email}
[LoggerMessage(Level = LogLevel.Information,
Message = "User {Email} logged in from {IpAddress}")]
private static partial void LogUserLogin(
ILogger logger, string email, string ipAddress);

Rules enforcing async-first EF Core patterns.

RuleSeverityCodeFixDescription
GREF001WarningYesUse SaveChangesAsync() instead of SaveChanges()

Rules enforcing Minimal API conventions and RFC 7807 compliance.

RuleSeverityCodeFixDescription
GRAPI001WarningYesUse TypedResults instead of Results for OpenAPI
GRAPI002WarningYesUse TypedResults.Problem() instead of BadRequest (RFC 7807)

To suppress a specific rule for a file or project:

// File-level suppression
#pragma warning disable GRMOD001
<!-- Project-level suppression in .csproj -->
<PropertyGroup>
<NoWarn>$(NoWarn);GRMOD001</NoWarn>
</PropertyGroup>