Skip to content

Configuration

Verified by tests

ConfigurationUtilitiesTests, NamingConventionUtilitiesTests — library CI run #31657041675 (2026-08-13)

Whizbang source generators read configuration from MSBuild properties, enabling compile-time customization without code changes. This approach ensures configuration is available during source generation while maintaining AOT compatibility.

Overview

Configuration utilities provide a bridge between MSBuild project properties and the incremental source generator pipeline:

flowchart TD
    Props[".csproj / Directory.Build.props<br/><br/>&lt;WhizbangStripTableNameSuffixes&gt;true&lt;/...&gt;<br/>&lt;WhizbangTableNameSuffixesToStrip&gt;...&lt;/...&gt;"]
    Utils["ConfigurationUtilities<br/><br/>Reads AnalyzerConfigOptions<br/>Returns TableNameConfig"]
    Generators["Source Generators<br/><br/>Use config for table names, code generation"]

    Props --> Utils
    Utils --> Generators

    class Props,Utils,Generators layer-infrastructure

ConfigurationUtilities

The ConfigurationUtilities class provides static methods for reading MSBuild properties from the analyzer configuration:

GetTableNameConfig

Reads table name configuration from MSBuild properties:

GetTableNameConfig

using Whizbang.Generators.Shared.Utilities;

public void Initialize(IncrementalGeneratorInitializationContext context) {
    // Create a value provider for table name configuration
    var tableNameConfig = context.AnalyzerConfigOptionsProvider.Select(
        ConfigurationUtilities.SelectTableNameConfig
    );

    // Use in generator pipeline
    var combined = perspectives.Combine(tableNameConfig);

    context.RegisterSourceOutput(combined, (ctx, data) => {
        var (perspectiveList, config) = data;
        GenerateCode(ctx, perspectiveList, config);
    });
}

MSBuild Properties

Property Type Default Description
WhizbangStripTableNameSuffixes bool true Enable/disable suffix stripping
WhizbangTableNameSuffixesToStrip string ReadModel,Model,Projection,Dto,View Comma-separated list of suffixes
WhizbangMaxIdentifierLength int provider default (63 for PostgreSQL) Optional override for maximum database identifier length; read via ConfigurationUtilities.GetMaxIdentifierLengthOverride / SelectMaxIdentifierLengthOverride

Configuration in .csproj

Configuration in .csproj

<PropertyGroup>
  <!-- Disable suffix stripping entirely -->
  <WhizbangStripTableNameSuffixes>false</WhizbangStripTableNameSuffixes>

  <!-- Or customize which suffixes to strip -->
  <WhizbangStripTableNameSuffixes>true</WhizbangStripTableNameSuffixes>
  <WhizbangTableNameSuffixesToStrip>ReadModel,Projection,View</WhizbangTableNameSuffixesToStrip>
</PropertyGroup>

TableNameConfig

The TableNameConfig record holds the parsed configuration:

TableNameConfig

public sealed record TableNameConfig(
    bool StripSuffixes,
    string[] SuffixesToStrip
) {
  /// <summary>
  /// Default configuration: strip common suffixes (Model, Projection, ReadModel, Dto, View).
  /// </summary>
  public static TableNameConfig Default { get; } = new(
      StripSuffixes: true,
      SuffixesToStrip: ["ReadModel", "Model", "Projection", "Dto", "View"]
  );

  /// <summary>
  /// Configuration that preserves all suffixes (no stripping).
  /// </summary>
  public static TableNameConfig NoStripping { get; } = new(
      StripSuffixes: false,
      SuffixesToStrip: []
  );
}

Table Name Suffix Stripping

When StripSuffixes is enabled, perspective model type names are transformed for database table names via NamingConventionUtilities.GenerateTableName: the first matching suffix is stripped, the remainder is converted to snake_case, and the wh_per_ prefix is added (no pluralization):

Model Type Name Stripped Name Table Name
OrderReadModel Order wh_per_order
ProductProjection Product wh_per_product
CustomerDto Customer wh_per_customer
InventoryView Inventory wh_per_inventory
UserModel User wh_per_user
AccountDetails AccountDetails wh_per_account_details

Example: Table Name Suffix Stripping

// Perspective read-model type
public class OrderReadModel {
    [StreamId]
    public Guid OrderId { get; set; }
    public string Status { get; set; } = "";
}

// Generated table name (with default suffix stripping):
// Table: "wh_per_order" (not "wh_per_order_read_model")

Usage in Generators

Pipeline Integration

Pipeline Integration

[Generator]
public class PerspectiveSchemaGenerator : IIncrementalGenerator {
    public void Initialize(IncrementalGeneratorInitializationContext context) {
        // 1. Discover perspectives
        var perspectives = context.SyntaxProvider.CreateSyntaxProvider(
            predicate: static (node, _) => node is ClassDeclarationSyntax,
            transform: static (ctx, ct) => ExtractPerspective(ctx, ct)
        ).Where(static p => p is not null);

        // 2. Get configuration
        var config = context.AnalyzerConfigOptionsProvider.Select(
            ConfigurationUtilities.SelectTableNameConfig
        );

        // 3. Combine and generate
        var combined = perspectives.Collect().Combine(config);

        context.RegisterSourceOutput(combined, static (ctx, data) => {
            var (perspectiveList, tableConfig) = data;
            GenerateSchema(ctx, perspectiveList!, tableConfig);
        });
    }

    private static void GenerateSchema(
        SourceProductionContext context,
        ImmutableArray<PerspectiveInfo> perspectives,
        TableNameConfig config) {

        foreach (var perspective in perspectives) {
            // Apply table name configuration
            // e.g., "OrderProjection" -> "wh_per_order" with default config
            var tableName = NamingConventionUtilities.GenerateTableName(
                perspective.TableBaseName,
                config
            );

            // Generate schema with configured table name...
        }
    }
}

Direct Access

For simpler scenarios, access configuration directly:

Direct Access

var config = ConfigurationUtilities.GetTableNameConfig(
    context.AnalyzerConfigOptionsProvider.GlobalOptions
);

if (config.StripSuffixes) {
    // Apply suffix stripping logic
}

Suffix Parsing

The ParseSuffixList method handles comma-separated suffix lists:

Suffix Parsing

// Parse from MSBuild property
var suffixes = ConfigurationUtilities.ParseSuffixList("ReadModel, Model, Dto");
// Result: ["ReadModel", "Model", "Dto"]

// Handles whitespace and empty entries
var suffixes = ConfigurationUtilities.ParseSuffixList("  Foo , , Bar , ");
// Result: ["Foo", "Bar"]

// Empty or null returns empty array
var suffixes = ConfigurationUtilities.ParseSuffixList("");
// Result: []

Best Practices

DO

  • Use Directory.Build.props for solution-wide settings
  • Provide sensible defaults (don't require configuration)
  • Document available options in project README
  • Use incremental pipeline with Select for optimal caching

DON'T

  • Require configuration for basic functionality
  • Read configuration in predicates (performance impact)
  • Ignore null/missing options (use defaults)

Troubleshooting

Configuration Not Applied

Symptoms: Generator ignores MSBuild property values.

Causes: 1. Property not available in analyzer config 2. Property name typo

Solution: Ensure property is in a <PropertyGroup> (not <ItemGroup>):

Configuration Not Applied

<!-- Correct -->
<PropertyGroup>
  <WhizbangStripTableNameSuffixes>false</WhizbangStripTableNameSuffixes>
</PropertyGroup>

<!-- Wrong - ItemGroup -->
<ItemGroup>
  <WhizbangStripTableNameSuffixes>false</WhizbangStripTableNameSuffixes>
</ItemGroup>

Suffix Not Stripped

Symptoms: Model suffix appears in generated table name (e.g., wh_per_order_view_model).

Causes: 1. Suffix not in list 2. Stripping disabled

Solution: Add suffix to list:

Suffix Not Stripped

<WhizbangTableNameSuffixesToStrip>ReadModel,Model,Projection,Dto,View,ViewModel</WhizbangTableNameSuffixesToStrip>


Version 1.0.0 - Foundation Release | Last Updated: 2024-12-12