A lightweight IOC Container that is powerful enough to do all the things you need it to do.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

158 lines
6.3 KiB

// Author: Simon.Gockner
// Created: 2025-12-01
// Copyright(c) 2025 SimonG. All Rights Reserved.
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace LightweightIocContainer.FactoryGenerator;
[Generator]
public class FactoryGenerator : IIncrementalGenerator
{
private const string EXTENSION_CLASS_NAME = "FactoryExtensions";
private const string BUILDER_CLASS_NAME = "FactoryBuilder";
private const string GENERATED_FILE_HEADER = "//---GENERATED by FactoryGenerator! DO NOT EDIT!---";
private const string INDENT = " ";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
string? classNamespace = typeof(FactoryGenerator).Namespace;
context.RegisterPostInitializationOutput(c => c.AddSource($"{EXTENSION_CLASS_NAME}.g.cs", GenerateFactoryExtensionsClass(classNamespace, EXTENSION_CLASS_NAME)));
IncrementalValuesProvider<ITypeSymbol?> syntaxProvider = context.SyntaxProvider.CreateSyntaxProvider(IsCallToGenerateFactory, GetTypeArgument);
context.RegisterSourceOutput(syntaxProvider.Collect(), GenerateTypeDependentClasses);
}
private string GenerateFactoryExtensionsClass(string? classNamespace, string className)
{
StringBuilder stringBuilder = new();
stringBuilder.AppendLine(GENERATED_FILE_HEADER);
stringBuilder.AppendLine();
stringBuilder.AppendLine("using LightweightIocContainer.Interfaces.Factories;");
stringBuilder.AppendLine("using LightweightIocContainer.Interfaces.Registrations;");
stringBuilder.AppendLine();
if (!string.IsNullOrEmpty(classNamespace))
{
stringBuilder.AppendLine($"namespace {classNamespace};");
stringBuilder.AppendLine();
}
stringBuilder.AppendLine($"public static class {className}");;
stringBuilder.AppendLine("{");
stringBuilder.AppendLine($"{INDENT}public static IRegistrationBase WithGeneratedFactory<TFactory>(this IRegistrationBase registration)");
stringBuilder.AppendLine($"{INDENT}{{");
stringBuilder.AppendLine($"{INDENT}{INDENT}FactoryBuilder factoryBuilder = new();");
stringBuilder.AppendLine($"{INDENT}{INDENT}registration.AddGeneratedFactory<TFactory>(factoryBuilder);");
stringBuilder.AppendLine();
stringBuilder.AppendLine($"{INDENT}{INDENT}return registration;");
stringBuilder.AppendLine($"{INDENT}}}");
stringBuilder.AppendLine("}");
return stringBuilder.ToString();
}
private bool IsCallToGenerateFactory(SyntaxNode node, CancellationToken cancellationToken)
{
if (!node.IsKind(SyntaxKind.GenericName) || node is not GenericNameSyntax genericNameSyntax)
return false;
if (!genericNameSyntax.ToString().StartsWith("WithGeneratedFactory"))
return false;
if (genericNameSyntax.TypeArgumentList.Arguments[0] is not IdentifierNameSyntax)
return false;
return true;
}
private ITypeSymbol? GetTypeArgument(GeneratorSyntaxContext syntaxContext, CancellationToken cancellationToken)
{
if (syntaxContext.Node is not GenericNameSyntax genericNameSyntax)
return null;
if (genericNameSyntax.TypeArgumentList.Arguments[0] is not IdentifierNameSyntax identifierNameSyntax)
return null;
if (syntaxContext.SemanticModel.GetSymbolInfo(identifierNameSyntax).Symbol is not ITypeSymbol typeSymbol)
return null;
return typeSymbol;
}
private void GenerateTypeDependentClasses(SourceProductionContext context, ImmutableArray<ITypeSymbol?> types)
{
string? classNamespace = typeof(FactoryGenerator).Namespace;
context.AddSource($"{BUILDER_CLASS_NAME}.g.cs", GenerateBuilderClassSourceCode(classNamespace, types));
}
private void GenerateFactory(SourceProductionContext context, ITypeSymbol? typeSymbol)
{
if (typeSymbol is null)
return;
context.AddSource($"Generated{typeSymbol.Name}.g.cs", GenerateFactorySourceCode(typeSymbol));
}
private string GenerateBuilderClassSourceCode(string? classNamespace, ImmutableArray<ITypeSymbol?> types)
{
StringBuilder stringBuilder = new();
stringBuilder.AppendLine(GENERATED_FILE_HEADER);
stringBuilder.AppendLine();
stringBuilder.AppendLine("using LightweightIocContainer.Interfaces.Factories;");
stringBuilder.AppendLine("using LightweightIocContainer.Factories;");
foreach (string typeNamespace in GetNamespacesOfTypes(types))
stringBuilder.AppendLine($"using {typeNamespace};");
stringBuilder.AppendLine();
if (classNamespace is not null)
{
stringBuilder.AppendLine($"namespace {classNamespace};");
stringBuilder.AppendLine();
}
stringBuilder.AppendLine("public class FactoryBuilder : IFactoryBuilder");
stringBuilder.AppendLine("{");
stringBuilder.AppendLine($"{INDENT}public TFactory Create<TFactory>(IocContainer container)");
stringBuilder.AppendLine($"{INDENT}{{");
foreach (ITypeSymbol? type in types)
{
if (type is null)
continue;
stringBuilder.AppendLine($"{INDENT}{INDENT}if (typeof(TFactory) == typeof({type.Name}))");
stringBuilder.AppendLine($"{INDENT}{INDENT}{{");
stringBuilder.AppendLine($"{INDENT}{INDENT}{INDENT}return (TFactory) (object) new Generated{type.Name}(container, new FactoryHelper());");
stringBuilder.AppendLine($"{INDENT}{INDENT}}}");
stringBuilder.AppendLine();
}
stringBuilder.AppendLine($"{INDENT}{INDENT}throw new Exception(\"Invalid type.\");");
stringBuilder.AppendLine($"{INDENT}}}");
stringBuilder.AppendLine("}");
return stringBuilder.ToString();
}
private IEnumerable<string> GetNamespacesOfTypes(ImmutableArray<ITypeSymbol?> types) =>
types.OfType<ITypeSymbol>()
.Select(s => s.ContainingNamespace.IsGlobalNamespace ? null : s.ContainingNamespace.ToString())
.OfType<string>()
.Distinct();
}