Compare commits

...

8 Commits

  1. 12
      .github/workflows/ci.yml
  2. 6
      .github/workflows/deploy.yml
  3. 156
      LightweightIocContainer.FactoryGenerator/FactoryGenerator.cs
  4. 19
      LightweightIocContainer/Interfaces/Factories/IFactoryBuilder.cs
  5. 7
      LightweightIocContainer/Interfaces/Registrations/Fluent/IWithFactory.cs
  6. 27
      LightweightIocContainer/LightweightIocContainer.xml
  7. 9
      LightweightIocContainer/Registrations/RegistrationBase.cs

@ -9,10 +9,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v5
- uses: actions/setup-dotnet@v1 - uses: actions/setup-dotnet@v5
with: with:
dotnet-version: '9.0.x' dotnet-version: '10.x'
- name: Build - name: Build
run: dotnet build run: dotnet build
@ -23,10 +23,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v5
- uses: actions/setup-dotnet@v1 - uses: actions/setup-dotnet@v5
with: with:
dotnet-version: '9.0.x' dotnet-version: '10.x'
- name: Run tests - name: Run tests
run: dotnet test run: dotnet test

@ -11,10 +11,10 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v5
- uses: actions/setup-dotnet@v1 - uses: actions/setup-dotnet@v5
with: with:
dotnet-version: '9.0.x' dotnet-version: '10.x'
- name: Build - name: Build
run: dotnet build -c Release -o output run: dotnet build -c Release -o output
- name: Deploy to nuGet gallery - name: Deploy to nuGet gallery

@ -14,18 +14,22 @@ namespace LightweightIocContainer.FactoryGenerator;
public class FactoryGenerator : IIncrementalGenerator public class FactoryGenerator : IIncrementalGenerator
{ {
private const string EXTENSION_CLASS_NAME = "FactoryExtensions"; private const string EXTENSION_CLASS_NAME = "FactoryExtensions";
private const string BUILDER_CLASS_NAME = "GeneratedFactoryBuilder"; private const string BUILDER_CLASS_NAME = "FactoryBuilder";
private const string GENERATED_FILE_HEADER = "//---GENERATED by FactoryGenerator! DO NOT EDIT!---"; private const string GENERATED_FILE_HEADER = "//---GENERATED by FactoryGenerator! DO NOT EDIT!---";
private const string INDENT = " "; private const string INDENT = " ";
private const string CLEAR_MULTITON_INSTANCE_METHOD_NAME = "ClearMultitonInstance";
public void Initialize(IncrementalGeneratorInitializationContext context) public void Initialize(IncrementalGeneratorInitializationContext context)
{ {
string? classNamespace = typeof(FactoryGenerator).Namespace; string? classNamespace = typeof(FactoryGenerator).Namespace;
context.RegisterPostInitializationOutput(c => c.AddSource($"{EXTENSION_CLASS_NAME}.g.cs", GenerateFactoryExtensionsClass(classNamespace, EXTENSION_CLASS_NAME))); context.RegisterPostInitializationOutput(c => c.AddSource($"{EXTENSION_CLASS_NAME}.g.cs", GenerateFactoryExtensionsClass(classNamespace, EXTENSION_CLASS_NAME)));
IncrementalValuesProvider<ITypeSymbol?> syntaxProvider = context.SyntaxProvider.CreateSyntaxProvider(IsCallToGenerateFactory, GetTypeArgument); IncrementalValuesProvider<ITypeSymbol?> syntaxProvider = context.SyntaxProvider.CreateSyntaxProvider(IsCallToGenerateFactory, GetTypeArgument);
context.RegisterSourceOutput(syntaxProvider.Collect(), GenerateTypeDependentClasses); context.RegisterSourceOutput(syntaxProvider.Collect(), GenerateTypeDependentClasses);
context.RegisterSourceOutput(syntaxProvider, GenerateFactory);
} }
private string GenerateFactoryExtensionsClass(string? classNamespace, string className) private string GenerateFactoryExtensionsClass(string? classNamespace, string className)
@ -35,9 +39,8 @@ public class FactoryGenerator : IIncrementalGenerator
stringBuilder.AppendLine(GENERATED_FILE_HEADER); stringBuilder.AppendLine(GENERATED_FILE_HEADER);
stringBuilder.AppendLine(); stringBuilder.AppendLine();
stringBuilder.AppendLine("using LightweightIocContainer.Interfaces.Factories;");
stringBuilder.AppendLine("using LightweightIocContainer.Interfaces.Registrations;"); stringBuilder.AppendLine("using LightweightIocContainer.Interfaces.Registrations;");
stringBuilder.AppendLine("using LightweightIocContainer.Interfaces.Registrations.Fluent;");
stringBuilder.AppendLine("using LightweightIocContainer.Registrations;");
stringBuilder.AppendLine(); stringBuilder.AppendLine();
if (!string.IsNullOrEmpty(classNamespace)) if (!string.IsNullOrEmpty(classNamespace))
@ -51,14 +54,10 @@ public class FactoryGenerator : IIncrementalGenerator
stringBuilder.AppendLine($"{INDENT}public static IRegistrationBase WithGeneratedFactory<TFactory>(this IRegistrationBase registration)"); stringBuilder.AppendLine($"{INDENT}public static IRegistrationBase WithGeneratedFactory<TFactory>(this IRegistrationBase registration)");
stringBuilder.AppendLine($"{INDENT}{{"); stringBuilder.AppendLine($"{INDENT}{{");
stringBuilder.AppendLine($"{INDENT}{INDENT}TFactory factory = Builder.Create<TFactory>();"); stringBuilder.AppendLine($"{INDENT}{INDENT}FactoryBuilder factoryBuilder = new();");
stringBuilder.AppendLine(); stringBuilder.AppendLine($"{INDENT}{INDENT}registration.AddGeneratedFactory<TFactory>(factoryBuilder);");
stringBuilder.AppendLine($"{INDENT}{INDENT}if (registration is not RegistrationBase registrationBase)");
stringBuilder.AppendLine($"{INDENT}{INDENT}{INDENT}throw new InvalidOperationException(\"The registration must be of type RegistrationBase to add a generated factory.\");");
stringBuilder.AppendLine(); stringBuilder.AppendLine();
stringBuilder.AppendLine($"{INDENT}{INDENT}registrationBase.AddGeneratedFactory<TFactory>(factory);"); stringBuilder.AppendLine($"{INDENT}{INDENT}return registration;");
stringBuilder.AppendLine();
stringBuilder.AppendLine($"{INDENT}{INDENT}return registrationBase;");
stringBuilder.AppendLine($"{INDENT}}}"); stringBuilder.AppendLine($"{INDENT}}}");
stringBuilder.AppendLine("}"); stringBuilder.AppendLine("}");
@ -100,6 +99,14 @@ public class FactoryGenerator : IIncrementalGenerator
context.AddSource($"{BUILDER_CLASS_NAME}.g.cs", GenerateBuilderClassSourceCode(classNamespace, types)); 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) private string GenerateBuilderClassSourceCode(string? classNamespace, ImmutableArray<ITypeSymbol?> types)
{ {
StringBuilder stringBuilder = new(); StringBuilder stringBuilder = new();
@ -107,6 +114,8 @@ public class FactoryGenerator : IIncrementalGenerator
stringBuilder.AppendLine(GENERATED_FILE_HEADER); stringBuilder.AppendLine(GENERATED_FILE_HEADER);
stringBuilder.AppendLine(); stringBuilder.AppendLine();
stringBuilder.AppendLine("using LightweightIocContainer.Interfaces.Factories;");
foreach (string typeNamespace in GetNamespacesOfTypes(types)) foreach (string typeNamespace in GetNamespacesOfTypes(types))
stringBuilder.AppendLine($"using {typeNamespace};"); stringBuilder.AppendLine($"using {typeNamespace};");
@ -118,9 +127,9 @@ public class FactoryGenerator : IIncrementalGenerator
stringBuilder.AppendLine(); stringBuilder.AppendLine();
} }
stringBuilder.AppendLine("public static class Builder"); stringBuilder.AppendLine("public class FactoryBuilder : IFactoryBuilder");
stringBuilder.AppendLine("{"); stringBuilder.AppendLine("{");
stringBuilder.AppendLine($"{INDENT}public static TFactory Create<TFactory>()"); stringBuilder.AppendLine($"{INDENT}public TFactory Create<TFactory>(IocContainer container)");
stringBuilder.AppendLine($"{INDENT}{{"); stringBuilder.AppendLine($"{INDENT}{{");
foreach (ITypeSymbol? type in types) foreach (ITypeSymbol? type in types)
@ -130,7 +139,7 @@ public class FactoryGenerator : IIncrementalGenerator
stringBuilder.AppendLine($"{INDENT}{INDENT}if (typeof(TFactory) == typeof({type.Name}))"); stringBuilder.AppendLine($"{INDENT}{INDENT}if (typeof(TFactory) == typeof({type.Name}))");
stringBuilder.AppendLine($"{INDENT}{INDENT}{{"); stringBuilder.AppendLine($"{INDENT}{INDENT}{{");
stringBuilder.AppendLine($"{INDENT}{INDENT}{INDENT}return (TFactory) (object) new Generated{type.Name}();"); stringBuilder.AppendLine($"{INDENT}{INDENT}{INDENT}return (TFactory) (object) new Generated{type.Name}(container);");
stringBuilder.AppendLine($"{INDENT}{INDENT}}}"); stringBuilder.AppendLine($"{INDENT}{INDENT}}}");
stringBuilder.AppendLine(); stringBuilder.AppendLine();
} }
@ -143,9 +152,130 @@ public class FactoryGenerator : IIncrementalGenerator
return stringBuilder.ToString(); return stringBuilder.ToString();
} }
private string GenerateFactorySourceCode(ITypeSymbol typeSymbol)
{
string typeName = typeSymbol.Name;
string? typeNamespace = typeSymbol.ContainingNamespace.IsGlobalNamespace ? null : typeSymbol.ContainingNamespace.ToString();
StringBuilder stringBuilder = new();
stringBuilder.AppendLine(GENERATED_FILE_HEADER);
stringBuilder.AppendLine();
stringBuilder.AppendLine("using LightweightIocContainer;");
stringBuilder.AppendLine();
if (typeNamespace is not null)
{
stringBuilder.AppendLine($"namespace {typeNamespace};");
stringBuilder.AppendLine();
}
stringBuilder.AppendLine($"public class Generated{typeName}(IocContainer container) : {typeName}");
stringBuilder.AppendLine("{");
foreach (ISymbol? member in typeSymbol.GetMembers())
{
if (member is not IMethodSymbol method)
continue;
stringBuilder.AppendLine();
if (!method.ReturnsVoid) //create method
{
stringBuilder.Append($"{INDENT}public {method.ReturnType.Name} {method.Name}");
if (method.IsGenericMethod)
stringBuilder.Append($"<{string.Join(", ", method.TypeParameters.Select(p => p.Name))}>");
stringBuilder.Append($"({string.Join(", ", method.Parameters.Select(GetParameterText))})");
if (method.IsGenericMethod)
{
foreach (ITypeParameterSymbol typeParameter in method.TypeParameters)
{
List<string> parameterConstraints = GetParameterConstraints(typeParameter);
if (parameterConstraints.Count == 0)
continue;
stringBuilder.Append($" where {typeParameter.Name} : {string.Join(", ", parameterConstraints)}");
}
}
stringBuilder.AppendLine();
stringBuilder.AppendLine($"{INDENT}{{");
foreach (IParameterSymbol parameter in method.Parameters)
{
stringBuilder.AppendLine($"{INDENT}{INDENT}object? {parameter.Name}Value = {parameter.Name}");
stringBuilder.AppendLine($"{INDENT}{INDENT}if ({parameter.Name}Value is null)");
stringBuilder.AppendLine($"{INDENT}{INDENT}{INDENT}{parameter.Name}Value = new NullParameter(typeof({parameter.Type.Name}));");
stringBuilder.AppendLine();
}
if (method.IsAsync)
stringBuilder.Append($"{INDENT}{INDENT}return await container.ResolveAsync<>("); //TODO: Get return type from Task<>
else
stringBuilder.Append($"{INDENT}{INDENT}return container.Resolve<{method.ReturnType.Name}>(");
stringBuilder.Append(string.Join(", ", method.Parameters.Select(p => $"{p.Name}Value")));
stringBuilder.AppendLine(");");
stringBuilder.AppendLine($"{INDENT}}}");
}
else if (method.Name == CLEAR_MULTITON_INSTANCE_METHOD_NAME)
{
//TODO
}
}
typeSymbol.GetMembers();
stringBuilder.AppendLine("}");
return stringBuilder.ToString();
}
private IEnumerable<string> GetNamespacesOfTypes(ImmutableArray<ITypeSymbol?> types) => private IEnumerable<string> GetNamespacesOfTypes(ImmutableArray<ITypeSymbol?> types) =>
types.OfType<ITypeSymbol>() types.OfType<ITypeSymbol>()
.Select(s => s.ContainingNamespace.IsGlobalNamespace ? null : s.ContainingNamespace.ToString()) .Select(s => s.ContainingNamespace.IsGlobalNamespace ? null : s.ContainingNamespace.ToString())
.OfType<string>() .OfType<string>()
.Distinct(); .Distinct();
private string GetParameterText(IParameterSymbol parameter)
{
StringBuilder stringBuilder = new();
stringBuilder.Append(parameter.Type.Name);
if (parameter.NullableAnnotation == NullableAnnotation.Annotated)
stringBuilder.Append("?");
stringBuilder.Append($" {parameter.Name}");
return stringBuilder.ToString();
}
private List<string> GetParameterConstraints(ITypeParameterSymbol typeParameterSymbol)
{
List<string> constraints = [];
foreach (ITypeSymbol constraintType in typeParameterSymbol.ConstraintTypes)
constraints.Add(constraintType.Name);
if (typeParameterSymbol.HasReferenceTypeConstraint)
constraints.Add("class");
if (typeParameterSymbol.HasValueTypeConstraint)
constraints.Add("struct");
if (typeParameterSymbol.HasConstructorConstraint)
constraints.Add("new()");
if (typeParameterSymbol.HasUnmanagedTypeConstraint)
constraints.Add("unmanaged");
if (typeParameterSymbol.HasNotNullConstraint)
constraints.Add("notnull");
return constraints;
}
} }

@ -0,0 +1,19 @@
// Author: Simon.Gockner
// Created: 2025-12-02
// Copyright(c) 2025 SimonG. All Rights Reserved.
namespace LightweightIocContainer.Interfaces.Factories;
/// <summary>
/// Internal class used by the factory source generator to create factory instances
/// </summary>
public interface IFactoryBuilder
{
/// <summary>
/// Internal method used by the factory source generator to create factory instances
/// </summary>
/// <param name="container">The current instance of the <see cref="IocContainer"/></param>
/// <typeparam name="TFactory">The type of the factory</typeparam>
/// <returns>The created factory instance</returns>
TFactory Create<TFactory>(IocContainer container);
}

@ -25,6 +25,13 @@ public interface IWithFactory
/// <typeparam name="TFactoryImplementation">The type of the implementation for the custom factory</typeparam> /// <typeparam name="TFactoryImplementation">The type of the implementation for the custom factory</typeparam>
/// <returns>The current instance of this <see cref="IRegistrationBase"/></returns> /// <returns>The current instance of this <see cref="IRegistrationBase"/></returns>
IRegistrationBase WithFactory<TFactoryInterface, TFactoryImplementation>() where TFactoryImplementation : TFactoryInterface; IRegistrationBase WithFactory<TFactoryInterface, TFactoryImplementation>() where TFactoryImplementation : TFactoryInterface;
/// <summary>
/// Internal method used by the factory source generator to add the generated factory to the registration
/// </summary>
/// <param name="factoryBuilder">The factory creator</param>
/// <typeparam name="TFactory">The type of the generated factory</typeparam>
void AddGeneratedFactory<TFactory>(IFactoryBuilder factoryBuilder);
} }
internal interface IWithFactoryInternal : IWithFactory internal interface IWithFactoryInternal : IWithFactory

@ -486,6 +486,19 @@
<param name="assembly">The given <see cref="T:System.Reflection.Assembly"/></param> <param name="assembly">The given <see cref="T:System.Reflection.Assembly"/></param>
<returns>A new <see cref="T:LightweightIocContainer.Interfaces.Installers.IAssemblyInstaller"/> with the given <see cref="T:System.Reflection.Assembly"/></returns> <returns>A new <see cref="T:LightweightIocContainer.Interfaces.Installers.IAssemblyInstaller"/> with the given <see cref="T:System.Reflection.Assembly"/></returns>
</member> </member>
<member name="T:LightweightIocContainer.Interfaces.Factories.IFactoryBuilder">
<summary>
Internal class used by the factory source generator to create factory instances
</summary>
</member>
<member name="M:LightweightIocContainer.Interfaces.Factories.IFactoryBuilder.Create``1(LightweightIocContainer.IocContainer)">
<summary>
Internal method used by the factory source generator to create factory instances
</summary>
<param name="container">The current instance of the <see cref="T:LightweightIocContainer.IocContainer"/></param>
<typeparam name="TFactory">The type of the factory</typeparam>
<returns>The created factory instance</returns>
</member>
<member name="T:LightweightIocContainer.Interfaces.Factories.ITypedFactory"> <member name="T:LightweightIocContainer.Interfaces.Factories.ITypedFactory">
<summary> <summary>
Non-generic <see cref="T:LightweightIocContainer.Interfaces.Factories.ITypedFactory`1"/> Non-generic <see cref="T:LightweightIocContainer.Interfaces.Factories.ITypedFactory`1"/>
@ -670,6 +683,13 @@
<typeparam name="TFactoryImplementation">The type of the implementation for the custom factory</typeparam> <typeparam name="TFactoryImplementation">The type of the implementation for the custom factory</typeparam>
<returns>The current instance of this <see cref="T:LightweightIocContainer.Interfaces.Registrations.IRegistrationBase"/></returns> <returns>The current instance of this <see cref="T:LightweightIocContainer.Interfaces.Registrations.IRegistrationBase"/></returns>
</member> </member>
<member name="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IWithFactory.AddGeneratedFactory``1(LightweightIocContainer.Interfaces.Factories.IFactoryBuilder)">
<summary>
Internal method used by the factory source generator to add the generated factory to the registration
</summary>
<param name="factoryBuilder">The factory creator</param>
<typeparam name="TFactory">The type of the generated factory</typeparam>
</member>
<member name="P:LightweightIocContainer.Interfaces.Registrations.Fluent.IWithFactoryInternal.Factory"> <member name="P:LightweightIocContainer.Interfaces.Registrations.Fluent.IWithFactoryInternal.Factory">
<summary> <summary>
The Factory added with the <see cref="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IWithFactory.WithFactory``1"/> method The Factory added with the <see cref="M:LightweightIocContainer.Interfaces.Registrations.Fluent.IWithFactory.WithFactory``1"/> method
@ -1601,6 +1621,13 @@
<typeparam name="TFactory">The type of the abstract typed factory</typeparam> <typeparam name="TFactory">The type of the abstract typed factory</typeparam>
<returns>The current instance of this <see cref="T:LightweightIocContainer.Interfaces.Registrations.IRegistrationBase"/></returns> <returns>The current instance of this <see cref="T:LightweightIocContainer.Interfaces.Registrations.IRegistrationBase"/></returns>
</member> </member>
<member name="M:LightweightIocContainer.Registrations.RegistrationBase.AddGeneratedFactory``1(LightweightIocContainer.Interfaces.Factories.IFactoryBuilder)">
<summary>
Internal method used by the factory source generator to add the generated factory to the registration
</summary>
<param name="factoryBuilder">The factory creator</param>
<typeparam name="TFactory">The type of the generated factory</typeparam>
</member>
<member name="M:LightweightIocContainer.Registrations.RegistrationBase.WithFactory``2"> <member name="M:LightweightIocContainer.Registrations.RegistrationBase.WithFactory``2">
<summary> <summary>
Register a custom implemented factory for the <see cref="T:LightweightIocContainer.Interfaces.Registrations.IRegistrationBase"/> Register a custom implemented factory for the <see cref="T:LightweightIocContainer.Interfaces.Registrations.IRegistrationBase"/>

@ -121,9 +121,14 @@ internal abstract class RegistrationBase : IRegistrationBase, IWithFactoryIntern
return this; return this;
} }
internal void AddGeneratedFactory<TFactory>(TFactory generatedFactory) /// <summary>
/// Internal method used by the factory source generator to add the generated factory to the registration
/// </summary>
/// <param name="factoryBuilder">The factory creator</param>
/// <typeparam name="TFactory">The type of the generated factory</typeparam>
public void AddGeneratedFactory<TFactory>(IFactoryBuilder factoryBuilder)
{ {
TypedFactory<TFactory> factory = new(generatedFactory); TypedFactory<TFactory> factory = new(factoryBuilder.Create<TFactory>(_container));
Factory = factory; Factory = factory;
_container.RegisterFactory(factory); _container.RegisterFactory(factory);

Loading…
Cancel
Save