Skip to content

Analyzer Extension Support

Alexander Stärk edited this page Sep 25, 2026 · 1 revision

Analyzer Extension Support

This page is for analyzer/source-generator authors.

Terminology note

This page describes analyzer extension support for external source generators (for example, generators that need to contribute additional DI registrations).

It is not the same as manual partial-class customization of DependencyRegistrator in consumer code.

For manual customization, see Manual Registrator Customization (Partial Class).

Why this exists

Source generators cannot directly see code generated by other source generators.

Because of that, Basilisque DI provides an extension mechanism so external analyzers can contribute additional registration code in a compatible way.

Package

Install:

Extension registration concept

At a high level:

  • your analyzer generates partial methods for Basilisque DI extension points
  • Basilisque DI invokes extension partial methods when configured
  • the setting BAS_DI_Extensions controls which extension names are expected

The setting supports values separated by ; or ,.

Available helper APIs

The ExtensionSupport package provides utilities around:

  • incremental generator value providers for root namespace/assembly name
  • extension implementation output helpers
  • service-registration code-line generation helpers

Typical workflow

  1. Reference ExtensionSupport in your analyzer project.
  2. Register extension output using the provided incremental generator helper APIs.
  3. Generate extension method implementations for registration and/or dependency initialization.
  4. Configure consuming projects with matching BAS_DI_Extensions values.

Concrete examples

Example 1: Register-only extension

Use this when your analyzer discovers implementation types and only needs to append service registrations.

using Basilisque.CodeAnalysis.Syntax;
using Basilisque.DependencyInjection.CodeAnalysis.ExtensionSupport.DependencyInjectionGenerator;
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;

internal static class FeatureExtensionOutput
{
    internal static void OutputImplementations(
        SourceProductionContext context,
        ((ImmutableArray<INamedTypeSymbol> Types, INamedTypeSymbol ServiceInterface) Input, (string? RootNamespace, string? AssemblyName) DIInfo) data,
        RegistrationOptions registrationOptions)
    {
        var extensionData = (data.Input, data.DIInfo);

        DependencyInjectionExtensionGeneratorOutput.OutputImplementations(
            context,
            extensionData,
            registrationOptions,
            extensionName: "MY_FEATURE_DIExt",
            registerExtensionCallback: AddRegistrations);
    }

    private static void AddRegistrations(
        SourceProductionContext context,
        CodeLines body,
        (ImmutableArray<INamedTypeSymbol> Types, INamedTypeSymbol ServiceInterface) input)
    {
        var serviceType = input.ServiceInterface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

        foreach (var type in input.Types)
        {
            var impl = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
            body.Add($"services.AddTransient(typeof({serviceType}), typeof({impl}));");
        }
    }
}

Example 2: Initialization + registration extension

Use this when your extension needs both:

  • dependency-chain initialization code
  • service registration code
using Basilisque.CodeAnalysis.Syntax;
using Basilisque.DependencyInjection.CodeAnalysis.ExtensionSupport.DependencyInjectionGenerator;
using Microsoft.CodeAnalysis;

internal static class PluginExtensionOutput
{
    internal static void OutputImplementations(
        SourceProductionContext context,
        ((string PluginRegistratorFqn, string PluginServiceFqn) Input, (string? RootNamespace, string? AssemblyName) DIInfo) data,
        RegistrationOptions registrationOptions)
    {
        DependencyInjectionExtensionGeneratorOutput.OutputImplementations(
            context,
            (data.Input, data.DIInfo),
            registrationOptions,
            extensionName: "MY_PLUGIN_DIExt",
            initializeExtensionCallback: InitializeDependencies,
            registerExtensionCallback: RegisterServices);
    }

    private static void InitializeDependencies(
        SourceProductionContext context,
        CodeLines body,
        (string PluginRegistratorFqn, string PluginServiceFqn) input)
    {
        body.Add($"collection.AddDependency<{input.PluginRegistratorFqn}>();");
    }

    private static void RegisterServices(
        SourceProductionContext context,
        CodeLines body,
        (string PluginRegistratorFqn, string PluginServiceFqn) input)
    {
        body.Add($"services.AddSingleton(typeof({input.PluginServiceFqn}));");
    }
}

Example 3: Consumer configuration for extension activation

The consuming project must configure matching extension names (separated by ; or ,).

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <BAS_DI_Extensions>MY_FEATURE_DIExt;MY_PLUGIN_DIExt</BAS_DI_Extensions>
  </PropertyGroup>
</Project>

Then normal registration is still a single call:

services.RegisterServices();

If extension names do not match, the extension partial hooks are not wired as expected.

Naming guidance for extension names

  • Use stable identifiers (for example, vendor + feature prefix).
  • Avoid spaces and special characters.
  • Keep names deterministic so consumers can configure BAS_DI_Extensions reliably.

Consumer-facing result

When configured correctly, consumers call services.RegisterServices() once, and both core Basilisque registrations and extension-contributed registrations are applied.

Clone this wiki locally