Skip to content

Breadcrumb Documentation

Needlr's source generator can include helpful documentation comments (breadcrumbs) in generated code to help developers understand why and how code was generated. This is particularly useful for debugging, learning, and working with AI assistants.

Overview

Breadcrumbs are self-documenting comments embedded in generated source code that explain:

  • What triggered the code generation
  • Source file locations of discovered types
  • Lifetime configurations (Singleton, Scoped, Transient)
  • Interceptor chains and their execution order
  • Decorator chains and their order

Needlr supports three breadcrumb levels:

Level Description Use Case
None Only // <auto-generated/> header Production builds where file size matters
Minimal Brief inline comments (default) Normal development
Verbose Full context boxes with source paths Debugging or learning how generation works

Configuration

Set the breadcrumb level using the NeedlrBreadcrumbLevel MSBuild property.

Per-Project Configuration

In your .csproj file:

<PropertyGroup>
  <NeedlrBreadcrumbLevel>Verbose</NeedlrBreadcrumbLevel>
</PropertyGroup>

Solution-Wide Configuration

In a Directory.Build.props file at your solution root:

<Project>
  <PropertyGroup>
    <NeedlrBreadcrumbLevel>Verbose</NeedlrBreadcrumbLevel>
  </PropertyGroup>

  <!-- Import Needlr generator props -->
  <Import Project="$(MSBuildThisFileDirectory)../packages/nexuslabs.needlr.generators/*/build/NexusLabs.Needlr.Generators.props" 
          Condition="Exists('$(MSBuildThisFileDirectory)../packages/nexuslabs.needlr.generators')" />
</Project>

Inheritance and Overrides

Child projects automatically inherit the breadcrumb level from parent Directory.Build.props files. Individual projects can override the inherited setting:

Solution/
├── Directory.Build.props          # Sets NeedlrBreadcrumbLevel=Minimal (inherited by all)
├── src/
│   ├── MyApp/
│   │   └── MyApp.csproj           # Inherits Minimal
│   └── MyApp.Debug/
│       └── MyApp.Debug.csproj     # Can override to Verbose

Output Examples

None Level

// <auto-generated/>
#nullable enable

using System;
using System.Collections.Generic;

namespace MyApp.Generated
{
    public static class TypeRegistry
    {
        private static readonly InjectableTypeInfo[] _types = new InjectableTypeInfo[]
        {
            new InjectableTypeInfo(typeof(global::MyApp.MyService), new Type[] { typeof(global::MyApp.IMyService) }, InjectableLifetime.Singleton),
        };
        // ... rest of generated code
    }
}

Minimal Level (Default)

// <auto-generated/>
#nullable enable

using System;
using System.Collections.Generic;

namespace MyApp.Generated
{
    public static class TypeRegistry
    {
        // Injectable types discovered in this assembly
        private static readonly InjectableTypeInfo[] _types = new InjectableTypeInfo[]
        {
            // MyService -> IMyService (Singleton)
            new InjectableTypeInfo(typeof(global::MyApp.MyService), new Type[] { typeof(global::MyApp.IMyService) }, InjectableLifetime.Singleton),
        };
        // ... rest of generated code
    }
}

Verbose Level

// <auto-generated/>
// ═══════════════════════════════════════════════════════════════════════════════
// Needlr Type Registry
// Assembly: MyApp
// Breadcrumb Level: Verbose
// ═══════════════════════════════════════════════════════════════════════════════
#nullable enable

using System;
using System.Collections.Generic;

namespace MyApp.Generated
{
    public static class TypeRegistry
    {
        // ┌─────────────────────────────────────────────────────────────────────────┐
        // │ Injectable Types                                                        │
        // │ Total types discovered: 1                                               │
        // └─────────────────────────────────────────────────────────────────────────┘
        private static readonly InjectableTypeInfo[] _types = new InjectableTypeInfo[]
        {
            // ┌─────────────────────────────────────────────────────────────────────────┐
            // │ Type: MyService                                                         │
            // │ Source: Services/MyService.cs                                           │
            // │ Implements: IMyService                                                  │
            // │ Lifetime: Singleton                                                     │
            // └─────────────────────────────────────────────────────────────────────────┘
            new InjectableTypeInfo(typeof(global::MyApp.MyService), new Type[] { typeof(global::MyApp.IMyService) }, InjectableLifetime.Singleton),
        };
        // ... rest of generated code
    }
}

Interceptor Proxy Documentation

When using interceptors, verbose breadcrumbs provide detailed information about proxy classes:

// ┌─────────────────────────────────────────────────────────────────────────┐
// │ Interceptor Proxy: OrderService                                         │
// │ Source: Services/OrderService.cs                                        │
// │ Target Interface: IOrderService                                         │
// │ Interceptors (execution order):                                         │
// │   1. LoggingInterceptor                                                 │
// │   2. CachingInterceptor                                                 │
// │   3. ValidationInterceptor                                              │
// │ Methods proxied: GetOrder, ProcessOrder, GetOrderAsync                  │
// │ Methods forwarded: ToString, GetHashCode                                │
// └─────────────────────────────────────────────────────────────────────────┘
public sealed class OrderService_InterceptorProxy : IOrderService
{
    // ... generated proxy implementation
}

Source Path Resolution

Verbose breadcrumbs show source file paths relative to the project directory:

Scenario Path Display
File in project Services/MyService.cs
File in subfolder Domain/Entities/Order.cs
External assembly [AssemblyName]
Unknown location [unknown]

Best Practices

  1. Development: Use Minimal (default) for day-to-day development
  2. Debugging: Switch to Verbose when troubleshooting generated code issues
  3. CI/CD: Consider None for release builds to minimize generated file size
  4. Learning: Use Verbose when first learning how Needlr's generator works

Configuration in CI/CD

You can set the breadcrumb level based on build configuration:

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <NeedlrBreadcrumbLevel>None</NeedlrBreadcrumbLevel>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
  <NeedlrBreadcrumbLevel>Verbose</NeedlrBreadcrumbLevel>
</PropertyGroup>

Troubleshooting

  1. Ensure NexusLabs.Needlr.Generators package is referenced
  2. Verify the .props file is being imported (check build output)
  3. Rebuild the project (breadcrumb level is read at generation time)

Case Sensitivity

The breadcrumb level value is case-insensitive:

  • Verbose, verbose, VERBOSE, VeRbOsE all work the same

Invalid Values

If an invalid value is specified, Needlr defaults to Minimal:

  • InvalidLevel → defaults to Minimal
  • Empty string → defaults to Minimal
  • Whitespace only → defaults to Minimal

Compile-Time Diagnostics

In addition to inline breadcrumbs, Needlr can generate separate diagnostic files at build time. These files provide higher-level views of your dependency injection configuration without cluttering your generated code.

Enabling Diagnostics

Add the NeedlrDiagnostics property to your project:

<PropertyGroup>
  <NeedlrDiagnostics>true</NeedlrDiagnostics>
</PropertyGroup>

This generates three markdown files in your build output folder (e.g., bin/Debug/net10.0/NeedlrDiagnostics/):

File Description
DependencyGraph.md Mermaid diagram showing service dependencies
LifetimeSummary.md Breakdown of Singleton/Scoped/Transient registrations
RegistrationIndex.md Complete index of all services, decorators, and plugins

Custom Output Path

Specify a custom output directory (relative paths are resolved from the project directory):

<PropertyGroup>
  <NeedlrDiagnostics>true</NeedlrDiagnostics>
  <NeedlrDiagnosticsPath>docs/diagnostics</NeedlrDiagnosticsPath>
</PropertyGroup>

Filtering Types

Filter diagnostics to specific types (comma-separated fully qualified names):

<PropertyGroup>
  <NeedlrDiagnostics>true</NeedlrDiagnostics>
  <NeedlrDiagnosticsFilter>MyApp.OrderService,MyApp.PaymentService</NeedlrDiagnosticsFilter>
</PropertyGroup>

Example Output

DependencyGraph.md

The dependency graph now includes rich visualization sections:

# Needlr Dependency Graph

Generated: 2026-01-25 10:00:00 UTC
Assembly: MyApp

## Referenced Plugin Assemblies

Types from referenced assemblies with `[GenerateTypeRegistry]`:

### MyApp.Plugins

\`\`\`mermaid
graph TD
    subgraph Singleton["MyApp.Plugins - Singleton"]
        OrderHandler["OrderHandler"]
        PaymentHandler["PaymentHandler"]
        Connection{{"Connection"}}
        ConnectionFactory["ConnectionFactory"]
    end
    PaymentHandler --> OrderHandler
    ConnectionFactory -.->|produces| Connection
\`\`\`

| Service | Lifetime | Interfaces |
|---------|----------|------------|
| OrderHandler | Singleton | IHandler<Order> |
| PaymentHandler | Singleton | IHandler<Payment> |
| Connection | Singleton | IConnection |
| ConnectionFactory | Singleton | IConnectionFactory |

## Service Dependencies

\`\`\`mermaid
graph TD
    subgraph Singleton
        Logger["Logger"]
        ConfigService["ConfigService"]
    end
    subgraph Scoped
        OrderService["OrderService"]
    end
    OrderService --> Logger
    OrderService --> ConfigService
\`\`\`

## Decorator Chains

Shows decorator wrapping order from outermost to innermost:

\`\`\`mermaid
graph LR
    CachingDecorator[["CachingDecorator"]] --> LoggingDecorator[["LoggingDecorator"]] --> OrderService["OrderService"]
\`\`\`

## Keyed Services

Groups services by their `[Keyed]` attribute values:

\`\`\`mermaid
graph TD
    subgraph key_redis["redis"]
        RedisCache["RedisCache"]
    end
    subgraph key_memory["memory"]
        MemoryCache["MemoryCache"]
    end
\`\`\`

## Plugin Assemblies

Shows plugins grouped by their source assembly:

\`\`\`mermaid
graph TD
    subgraph asm_MyApp_Plugins["MyApp.Plugins"]
        OrderPlugin(["OrderPlugin"])
        PaymentPlugin(["PaymentPlugin"])
    end
\`\`\`

## Factory Services

Shows all factory→product relationships from host and referenced assemblies:

\`\`\`mermaid
graph LR
    ConnectionFactory["ConnectionFactory"]
    Connection{{"Connection"}}
    ConnectionFactory -.->|produces| Connection
    HttpClientFactory["HttpClientFactory"]
    HttpClient{{"HttpClient"}}
    HttpClientFactory -.->|produces| HttpClient
\`\`\`

> Note: This section aggregates factories from the host assembly and all referenced plugin assemblies
> with `[GenerateTypeRegistry]`. Hexagon shapes indicate factory-produced types.

## Interface Mapping

Shows interface-to-implementation relationships with dotted edges:

\`\`\`mermaid
graph LR
    IOrderService(("IOrderService")) -.-> OrderService["OrderService"]
    ILogger(("ILogger")) -.-> Logger["Logger"]
\`\`\`

## Complexity Metrics

| Metric | Value |
|--------|-------|
| Total Services | 15 |
| Max Dependency Depth | 4 |
| Hub Services (≥3 dependents) | 2 |

**Hub Services:** Logger (8), ConfigService (5)

## Dependency Details

| Service | Lifetime | Dependencies |
|---------|----------|--------------|
| Logger | Singleton | - |
| OrderService | Scoped | ILogger, IConfigService |

LifetimeSummary.md

# Needlr Lifetime Summary

Generated: 2026-01-25 10:00:00 UTC
Assembly: MyApp

| Lifetime | Count | Percentage |
|----------|-------|------------|
| Singleton | 5 | 50.0% |
| Scoped | 3 | 30.0% |
| Transient | 2 | 20.0% |
| **Total** | **10** | **100%** |

RegistrationIndex.md

# Needlr Registration Index

Generated: 2026-01-25 10:00:00 UTC
Assembly: MyApp

## Services (10)

| # | Interface | Implementation | Lifetime | Source |
|---|-----------|----------------|----------|--------|
| 1 | ILogger | Logger | Singleton | Services/Logger.cs |
| 2 | IOrderService | OrderService | Scoped | Services/OrderService.cs |

## Decorators (2)

| Service | Decorator Chain |
|---------|-----------------|
| IOrderService | LoggingDecorator → CachingDecorator |

Configuration Properties Summary

Property Default Description
NeedlrDiagnostics false Enable diagnostic file generation
NeedlrDiagnosticsPath $(OutputPath)NeedlrDiagnostics Output directory (defaults to bin folder)
NeedlrDiagnosticsFilter (all) Comma-separated type names to include

Analyzer Status

When diagnostics are enabled, Needlr also generates AnalyzerStatus.md showing which analyzers are active and their current severity. This provides a single place to understand what compile-time protection is enabled for your project.

Example AnalyzerStatus.md

# Needlr Analyzer Status

Generated: 2026-01-25 10:00:00 UTC

## Active Analyzers

| ID | Name | Status | Default Severity | Description |
|:---|:-----|:-------|:-----------------|:------------|
| NDLRCOR001 | Reflection in AOT | ⚪ Conditional | Error | Detects reflection APIs in AOT projects |
| NDLRCOR005 | Lifetime Mismatch | ✅ Active | Warning | Detects captive dependencies |
| NDLRCOR009 | Lazy Resolution | ✅ Active | Info | Lazy<T> references undiscovered type |
| NDLRCOR010 | Collection Resolution | ✅ Active | Info | IEnumerable<T> has no implementations |

## Mode

**Source Generation**: Enabled (GenerateTypeRegistry detected)

## Configuration

Analyzer severity can be configured via `.editorconfig`:

\`\`\`ini
# Example: Suppress Lazy resolution warnings
dotnet_diagnostic.NDLRCOR009.severity = none

# Example: Promote to warning
dotnet_diagnostic.NDLRCOR009.severity = warning
\`\`\`

Understanding Analyzer Status

Status Meaning
✅ Active Analyzer is running and checking your code
⚪ Conditional Analyzer only activates under certain conditions (e.g., AOT projects)
❌ Disabled Analyzer is disabled via configuration

Configuring Analyzers

Use .editorconfig to adjust analyzer severity:

# .editorconfig in your project root
[*.cs]

# Disable resolution validation (if using reflection)
dotnet_diagnostic.NDLRCOR009.severity = none
dotnet_diagnostic.NDLRCOR010.severity = none

# Promote lifetime mismatch to error
dotnet_diagnostic.NDLRCOR005.severity = error

See Analyzers Documentation for the complete list of available analyzers.