Analyzer
SF0004
Flags source methods that route through more than eight abstraction layers because the primary path has been buried under wrappers.
Diagnostic contract
A deep call chain makes it hard to see where the product behavior actually lives. The deeper the path, the more ceremony a reader must traverse before reaching the point of change.
The computed source-level call graph shows a method depth above the fixed threshold of 8.
Method {0} passes through {1} abstraction layers, exceeding the limit of 8
/analyzers/sf0004/
When it fires
These are the concrete cases to look for in code review and IDE diagnostics.
- The analyzer builds a call graph from source methods and resolves unique dispatch targets for interface and override calls when possible.
- It reports only when the computed depth exceeds eight layers.
- Because it reasons over the whole compilation, the diagnostic fires at compilation end instead of per node.
Bad / better example
Use these examples to explain the rule, not just silence it.
Before
public sealed class CheckoutController(CheckoutApplicationService app)
{
public Task<Result> PostAsync(Request request) => app.HandleAsync(request);
}
public sealed class CheckoutApplicationService(CheckoutCoordinator coordinator)
{
public Task<Result> HandleAsync(Request request) => coordinator.RunAsync(request);
}
public sealed class CheckoutCoordinator(CheckoutPipeline pipeline)
{
public Task<Result> RunAsync(Request request) => pipeline.ExecuteAsync(request);
}
// ... Validator -> Mapper -> RepositoryFacade -> Repository -> SqlGateway ... The business path is technically present, but most of it is spent traversing wrappers.
After
public sealed class CheckoutController(CheckoutHandler handler)
{
public Task<Result> PostAsync(Request request) => handler.HandleAsync(request);
}
public sealed class CheckoutHandler(OrderRepository repository, PaymentGateway gateway)
{
public async Task<Result> HandleAsync(Request request)
{
var order = await repository.LoadAsync(request.OrderId);
return await gateway.AuthorizeAsync(order);
}
} The reader reaches the real flow quickly, with supporting concerns still available but not piled on top.
Code fix
There is no automatic code fix for this rule today. Treat it as a prompt to simplify deliberately.
- Look for wrapper classes that simply forward the same arguments to the next layer.
- Prefer collapsing the path toward a direct handler over adding another “shared abstraction” to hide it.
- Pair this rule with explicit [PrimaryPath] annotations when conventions are not enough to tell the truth.
Source and follow-up links
Use these links when you need to validate behavior against the source or connect the docs back to project tracking.
Analyzer implementation
AbstractionLayerDepthAnalyzer.cs
TrackingIssue #55
Documentation tracker for the analyzer pages and related integration guidance.
FilterPrimaryPathFirst
Deep call chains are one of the clearest ways to bury the primary path.
GuideIDE setup
Enable the analyzer package so the call-chain warning shows up during normal development.