@ -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 = "Generated FactoryBuilder" ;
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 ( $"{INDENT}{INDENT}registrationBase.AddGeneratedFactory<TFactory>(factory);" ) ;
stringBuilder . AppendLine ( ) ;
stringBuilder . AppendLine ( ) ;
stringBuilder . AppendLine ( $"{INDENT}{INDENT}return registrationBase ;" ) ;
stringBuilder . AppendLine ( $"{INDENT}{INDENT}return registration;" ) ;
stringBuilder . AppendLine ( $"{INDENT}}}" ) ;
stringBuilder . AppendLine ( $"{INDENT}}}" ) ;
stringBuilder . AppendLine ( "}" ) ;
stringBuilder . AppendLine ( "}" ) ;
@ -99,6 +98,14 @@ public class FactoryGenerator : IIncrementalGenerator
string? classNamespace = typeof ( FactoryGenerator ) . Namespace ;
string? classNamespace = typeof ( FactoryGenerator ) . Namespace ;
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 )
{
{
@ -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 : IFactory Builder" ) ;
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 ( ) ;
}
}
@ -142,10 +151,131 @@ 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 ;
}
}
}