Skip to content

[v2.0] Add source generator for type-safe per-module GetModule extensions #2006

@thomhurst

Description

@thomhurst

Summary

Extend the existing source generator to create type-safe extension methods for accessing each module's result, eliminating the need for developers to specify both type parameters.

Current API

// Requires knowing and specifying both types
var result = await context.GetModule<BuildModule, BuildOutput>();

Proposed Generated API

For each module in the assembly, generate:

// Auto-generated in: BuildModule.Generated.cs
public static class BuildModuleExtensions
{
    /// <summary>
    /// Gets the result of <see cref="BuildModule"/>.
    /// </summary>
    public static ModuleResult<BuildOutput> GetBuildModule(this IModuleContext context)
        => context.GetModule<BuildModule, BuildOutput>();
}

Usage becomes:

var result = await context.GetBuildModule();

Benefits

  1. Type inference: No need to specify result type
  2. Discoverability: IntelliSense shows all available modules
  3. Documentation: Generated XML docs link to module type
  4. Compile-time safety: Can't request wrong result type

Implementation

Source Generator Addition

Extend ModularPipelines.SourceGenerator:

[Generator]
public class ModuleAccessorGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // Find all types inheriting from Module<T>
        var modules = FindModuleTypes(context.Compilation);
        
        foreach (var module in modules)
        {
            var resultType = GetResultType(module);
            var source = GenerateAccessorExtension(module, resultType);
            context.AddSource($"{module.Name}Extensions.g.cs", source);
        }
    }
}

Generated Code Template

// <auto-generated />
using System.CodeDom.Compiler;
using ModularPipelines.Context;
using ModularPipelines.Models;

namespace {ModuleNamespace}
{
    [GeneratedCode("ModularPipelines.SourceGenerator", "1.0.0")]
    public static class {ModuleName}Extensions
    {
        /// <summary>
        /// Gets the result of <see cref="{ModuleName}"/>.
        /// </summary>
        /// <param name="context">The module context.</param>
        /// <returns>The module result containing <see cref="{ResultType}"/>.</returns>
        public static ModuleResult<{ResultType}> Get{ModuleName}(this IModuleContext context)
            => context.GetModule<{ModuleName}, {ResultType}>();
    }
}

Naming Conventions

  • BuildModuleGetBuildModule()
  • DeployToProductionModuleGetDeployToProductionModule()
  • Strip "Module" suffix if present: Build class → GetBuild()

Considerations

  1. Name collisions: Multiple modules with same name in different namespaces
  2. Visibility: Only generate for public modules
  3. Partial classes: Consider generating as partial for extensibility
  4. Opt-out: Attribute to disable generation for specific modules

Related Issues

Impact

  • Non-breaking: Additive feature, old API still works
  • Improves: Developer experience, type safety, discoverability

Labels

  • enhancement
  • source-generator
  • v2.0
  • developer-experience

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions