mirror of
https://github.com/OMGeeky/UnityCodeGenerators.git
synced 2025-12-27 14:59:20 +01:00
improve Generator
This commit is contained in:
@@ -15,4 +15,8 @@
|
||||
<Compile Remove="Generators.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Unity\Components\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
|
||||
namespace ExampleGenerator.Unity.Components
|
||||
{
|
||||
[Generator]
|
||||
public class GetComponentGenerator : ISourceGenerator
|
||||
{
|
||||
private const string AttributeText = @"
|
||||
using System;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
|
||||
internal class GetComponentAttribute : Attribute
|
||||
{
|
||||
public enum TargetType
|
||||
{
|
||||
This = 0,
|
||||
Parent = 1,
|
||||
Child = 2,
|
||||
}
|
||||
|
||||
public GetComponentAttribute(TargetType targetType = TargetType.This)
|
||||
{
|
||||
}
|
||||
}
|
||||
";
|
||||
|
||||
public void Initialize( GeneratorInitializationContext context )
|
||||
{
|
||||
context.RegisterForPostInitialization( i => i.AddSource( "GetComponentAttribute_g.cs" , AttributeText ) );
|
||||
context.RegisterForSyntaxNotifications( () => new SyntaxReceiver() );
|
||||
}
|
||||
|
||||
public void Execute( GeneratorExecutionContext context )
|
||||
{
|
||||
if ( !(context.SyntaxContextReceiver is SyntaxReceiver receiver) )
|
||||
return;
|
||||
|
||||
INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName( "GetComponentAttribute" );
|
||||
|
||||
foreach ( IGrouping<INamedTypeSymbol , IFieldSymbol> group in receiver.Fields
|
||||
.GroupBy<IFieldSymbol , INamedTypeSymbol>( f => f.ContainingType
|
||||
, SymbolEqualityComparer.Default ) )
|
||||
{
|
||||
var classSource = ProcessClass( group.Key , group , attributeSymbol );
|
||||
context.AddSource( $"{group.Key.Name}_Components_g.cs" , SourceText.From( classSource , Encoding.UTF8 ) );
|
||||
}
|
||||
}
|
||||
|
||||
private string ProcessClass( INamedTypeSymbol classSymbol , IEnumerable<IFieldSymbol> fields , ISymbol attributeSymbol )
|
||||
{
|
||||
var source = new StringBuilder( $@"
|
||||
|
||||
public partial class {classSymbol.Name}
|
||||
{{
|
||||
private void t()
|
||||
{{
|
||||
" );
|
||||
|
||||
foreach ( IFieldSymbol fieldSymbol in fields )
|
||||
{
|
||||
ProcessField( source , fieldSymbol , attributeSymbol );
|
||||
}
|
||||
|
||||
source.Append( "}\n\n}" );
|
||||
return source.ToString();
|
||||
}
|
||||
|
||||
private void ProcessField( StringBuilder source , IFieldSymbol fieldSymbol , ISymbol attributeSymbol )
|
||||
{
|
||||
var fieldName = fieldSymbol.Name;
|
||||
ITypeSymbol fieldType = fieldSymbol.Type;
|
||||
|
||||
AttributeData attributeData = fieldSymbol.GetAttributes()
|
||||
.Single( ad =>
|
||||
ad.AttributeClass?.Equals( attributeSymbol , SymbolEqualityComparer.Default ) ?? false );
|
||||
|
||||
var methodType = ProcessAttribute( attributeData );
|
||||
|
||||
source.AppendLine( $@"{fieldName} = {methodType}<{fieldType}>();" );
|
||||
}
|
||||
|
||||
private string ProcessAttribute( AttributeData attributeData )
|
||||
{
|
||||
var stringBuilder = new StringBuilder( "GetComponent" );
|
||||
var args = attributeData.ConstructorArguments;
|
||||
if ( args.Length > 0 && int.TryParse( args[0].Value?.ToString() , out var enumValue ) )
|
||||
{
|
||||
switch ( enumValue )
|
||||
{
|
||||
case 1:
|
||||
stringBuilder.Append( "InParent" );
|
||||
break;
|
||||
case 2:
|
||||
stringBuilder.Append( "InChildren" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal class SyntaxReceiver : ISyntaxContextReceiver
|
||||
{
|
||||
public List<IFieldSymbol> Fields { get; } = new List<IFieldSymbol>();
|
||||
|
||||
public void OnVisitSyntaxNode( GeneratorSyntaxContext context )
|
||||
{
|
||||
if ( !(context.Node is FieldDeclarationSyntax fieldDeclarationSyntax) || fieldDeclarationSyntax.AttributeLists.Count <= 0 )
|
||||
return;
|
||||
|
||||
foreach ( VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables )
|
||||
{
|
||||
if ( context.SemanticModel.GetDeclaredSymbol( variable ) is IFieldSymbol fieldSymbol
|
||||
&& IsDerivedFrom( fieldSymbol.ContainingType.BaseType , "MonoBehaviour" )
|
||||
&& IsDerivedFrom( fieldSymbol.Type.BaseType , "Component" )
|
||||
&& fieldSymbol.GetAttributes().Any( ad => ad.AttributeClass?.ToDisplayString() == "GetComponentAttribute" ) )
|
||||
{
|
||||
Fields.Add( fieldSymbol );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDerivedFrom( INamedTypeSymbol baseType , string targetType )
|
||||
{
|
||||
while ( baseType != null )
|
||||
{
|
||||
if ( baseType.Name == targetType )
|
||||
return true;
|
||||
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ namespace ExampleGenerator.Unity.Ui
|
||||
public static class Helpers
|
||||
{
|
||||
public const string UiElementAttribute = "UiElementAttribute";
|
||||
public const string AtUiComponentAttribute = "AtUiComponentAttribute";
|
||||
|
||||
internal static bool IsDerivedFrom( INamedTypeSymbol baseType , string targetType )
|
||||
{
|
||||
@@ -34,6 +35,16 @@ namespace ExampleGenerator.Unity.Ui
|
||||
public class UiBackingClassGenerator : ISourceGenerator
|
||||
{
|
||||
|
||||
private static readonly string AtUiComponentAttributeText = $@"// <auto-generated/>
|
||||
using System;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
internal class {Helpers.AtUiComponentAttribute} : Attribute
|
||||
{{
|
||||
public {Helpers.AtUiComponentAttribute}(string uxmlPath) {{ }}
|
||||
}}
|
||||
";
|
||||
|
||||
private static readonly string UiElementAttributeText = $@"// <auto-generated/>
|
||||
using System;
|
||||
|
||||
@@ -50,6 +61,9 @@ internal class {Helpers.UiElementAttribute} : Attribute
|
||||
{
|
||||
context.RegisterForPostInitialization( i =>
|
||||
{
|
||||
i.AddSource( $"{Helpers.AtUiComponentAttribute}_g.cs"
|
||||
, SourceText.From( AtUiComponentAttributeText , Encoding.UTF8 ) );
|
||||
|
||||
i.AddSource( $"{Helpers.UiElementAttribute}_g.cs"
|
||||
, SourceText.From( UiElementAttributeText , Encoding.UTF8 ) );
|
||||
} );
|
||||
@@ -62,57 +76,129 @@ internal class {Helpers.UiElementAttribute} : Attribute
|
||||
if ( !(context.SyntaxContextReceiver is SyntaxReceiver receiver) )
|
||||
return;
|
||||
|
||||
INamedTypeSymbol uiElementAttributeSymbol = context.Compilation.GetTypeByMetadataName( Helpers.UiElementAttribute );
|
||||
foreach ( IGrouping<INamedTypeSymbol , ISymbol> group in receiver.Fields
|
||||
.GroupBy<ISymbol , INamedTypeSymbol>( f => f.ContainingType
|
||||
, SymbolEqualityComparer.Default ) )
|
||||
var atUiComponentAttributeSymbol = context.Compilation.GetTypeByMetadataName( Helpers.AtUiComponentAttribute );
|
||||
var uiElementAttributeSymbol = context.Compilation.GetTypeByMetadataName( Helpers.UiElementAttribute );
|
||||
foreach ( var group in receiver.Fields
|
||||
.GroupBy<ISymbol , INamedTypeSymbol>( f => f.ContainingType
|
||||
, SymbolEqualityComparer.Default ) )
|
||||
{
|
||||
var classSource = ProcessClass( group.Key , group , uiElementAttributeSymbol );
|
||||
var classSymbol = group.Key;
|
||||
// if (! Helpers.IsDerivedFrom( classSymbol , "AtVisualElement" ) )
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
var classSource = ProcessClassUiElement( classSymbol , group , uiElementAttributeSymbol );
|
||||
if ( classSource == null )
|
||||
continue;
|
||||
|
||||
context.AddSource( $"{group.Key.Name}_ui_g.cs" , SourceText.From( classSource , Encoding.UTF8 ) );
|
||||
context.AddSource( $"{classSymbol.Name}_ui_query_g.cs" , SourceText.From( classSource , Encoding.UTF8 ) );
|
||||
}
|
||||
foreach ( var classSymbol in receiver.Classes )
|
||||
{
|
||||
if ( classSymbol is null )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// if (! Helpers.IsDerivedFrom( classSymbol , "AtVisualElement" ) )
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
var classSource = ProcessClassUiComponent( classSymbol , atUiComponentAttributeSymbol );
|
||||
if ( classSource == null )
|
||||
continue;
|
||||
|
||||
context.AddSource( $"{classSymbol.Name}_at_ui_component_g.cs" , SourceText.From( classSource , Encoding.UTF8 ) );
|
||||
}
|
||||
}
|
||||
|
||||
private string ProcessClass( INamedTypeSymbol classSymbol
|
||||
private static string ProcessClassUiComponent( INamedTypeSymbol classSymbol
|
||||
, INamedTypeSymbol atUiComponentAttributeSymbol )
|
||||
{
|
||||
var uiComponentAttributeData = GetUiElementAttributeData( classSymbol , atUiComponentAttributeSymbol );
|
||||
|
||||
var uxmlPath = uiComponentAttributeData?.UxmlPath;
|
||||
var source = new StringBuilder();
|
||||
AppendClassFrameStart( classSymbol,source );
|
||||
if ( !string.IsNullOrWhiteSpace( uxmlPath ) )
|
||||
{
|
||||
// Example output:
|
||||
// protected override string UxmlPath => "Ingame/Inventory/Inventory";
|
||||
source.AppendLine( $" protected override string UxmlPath => \"{uxmlPath}\";" );
|
||||
}
|
||||
AppendClassFrameEnd( source );
|
||||
return source.ToString();
|
||||
}
|
||||
private string ProcessClassUiElement( INamedTypeSymbol classSymbol
|
||||
, IEnumerable<ISymbol> fields
|
||||
, INamedTypeSymbol uiElementAttributeSymbol )
|
||||
{
|
||||
var fieldsList = fields.ToList();
|
||||
if ( !fieldsList.Any() )
|
||||
var elementFields = fields.Where( f => GetUiElementAttributeData( f , uiElementAttributeSymbol ) != null ).ToList();
|
||||
if ( !elementFields.Any() && uiElementAttributeSymbol is null )
|
||||
return null;
|
||||
var source = new StringBuilder();
|
||||
AppendClassFrameStart( classSymbol , source );
|
||||
|
||||
List<ISymbol> elementFields = fieldsList.Where( f => GetUiElementAttributeData( f , uiElementAttributeSymbol ) != null ).ToList();
|
||||
if ( elementFields.Any() )
|
||||
{
|
||||
|
||||
var source = new StringBuilder( $@"// <auto-generated/>
|
||||
|
||||
using UnityEngine.UIElements;
|
||||
namespace {classSymbol.ContainingNamespace}
|
||||
{{
|
||||
public partial class {classSymbol.Name}
|
||||
{{" );
|
||||
|
||||
source.Append( $@"
|
||||
source.Append( @"
|
||||
protected override void QueryElements()
|
||||
{{
|
||||
{
|
||||
" );
|
||||
|
||||
foreach ( ISymbol fieldSymbol in elementFields )
|
||||
{
|
||||
source.AppendLine( $" {fieldSymbol.Name} = this.Q<{GetQualifyingTypeNameFromSymbol( fieldSymbol )}>(\"{GetUiElementAttributeData( fieldSymbol , uiElementAttributeSymbol )?.Name}\");" );
|
||||
foreach ( var fieldSymbol in elementFields )
|
||||
{
|
||||
source.AppendLine( $" {fieldSymbol.Name} = this.Q<{GetQualifyingTypeNameFromSymbol( fieldSymbol )}>(\"{GetUiElementAttributeData( fieldSymbol , uiElementAttributeSymbol )?.Name}\");" );
|
||||
}
|
||||
|
||||
source.AppendLine( " }" );
|
||||
}
|
||||
|
||||
source.Append( $@" }}
|
||||
}}
|
||||
}}
|
||||
" );
|
||||
AppendClassFrameEnd( source );
|
||||
|
||||
return source.ToString();
|
||||
}
|
||||
|
||||
private static string GetQualifyingTypeName( ITypeSymbol type ) { return type.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat ); }
|
||||
private string GetQualifyingTypeNameFromSymbol( ISymbol symbol ) => GetQualifyingTypeName( GetTypeFromSymbol( symbol ) );
|
||||
|
||||
private static void AppendClassFrameStart( INamedTypeSymbol classSymbol , StringBuilder source )
|
||||
{
|
||||
source.AppendLine($@"// <auto-generated/>
|
||||
|
||||
using UnityEngine.UIElements;
|
||||
namespace {classSymbol.ContainingNamespace}
|
||||
{{
|
||||
public partial class {classSymbol.Name} : AtVisualElement
|
||||
{{");
|
||||
}
|
||||
private static void AppendClassFrameEnd( StringBuilder source )
|
||||
{
|
||||
source.Append( @"}
|
||||
}
|
||||
" );
|
||||
}
|
||||
|
||||
private static string GetQualifyingTypeName( ITypeSymbol type ) { return type.ToDisplayString( SymbolDisplayFormat.FullyQualifiedFormat ); }
|
||||
private static string GetQualifyingTypeNameFromSymbol( ISymbol symbol ) => GetQualifyingTypeName( GetTypeFromSymbol( symbol ) );
|
||||
|
||||
private static AtUiComponentAttributeData? GetUiElementAttributeData( INamedTypeSymbol classSymbol , INamedTypeSymbol uiElementAttributeSymbol )
|
||||
{
|
||||
if ( classSymbol is null || uiElementAttributeSymbol is null )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var attr = GetSingleAttributeData( classSymbol , uiElementAttributeSymbol );
|
||||
if ( attr == null )
|
||||
return null;
|
||||
var args = attr.ConstructorArguments.ToList();
|
||||
if ( args.Count != 1 )
|
||||
{
|
||||
throw new NotImplementedException( $"Attribute had a different parameter amount than expected: expected 1 got {args.Count} {attr}: args: {args}" );
|
||||
}
|
||||
return new AtUiComponentAttributeData
|
||||
{
|
||||
UxmlPath = args[0].Value as string ,
|
||||
};
|
||||
}
|
||||
|
||||
private static UiElementAttributeData? GetUiElementAttributeData( ISymbol fieldSymbol , INamedTypeSymbol uiElementAttributeSymbol )
|
||||
{
|
||||
@@ -123,7 +209,7 @@ public partial class {classSymbol.Name}
|
||||
var args = attr.ConstructorArguments.ToList();
|
||||
if ( args.Count > 1 )
|
||||
{
|
||||
throw new NotImplementedException( $"Attribute did not have enough parameters: expected 1 got {args.Count} {attr}: args: {args}" );
|
||||
throw new NotImplementedException( $"Attribute had more parameters than expected: expected 1 got {args.Count} {attr}: args: {args}" );
|
||||
}
|
||||
|
||||
string name = null;
|
||||
@@ -137,7 +223,7 @@ public partial class {classSymbol.Name}
|
||||
name = fieldSymbol.Name;
|
||||
}
|
||||
|
||||
return new UiElementAttributeData()
|
||||
return new UiElementAttributeData
|
||||
{
|
||||
Name = name ,
|
||||
};
|
||||
@@ -168,11 +254,16 @@ public partial class {classSymbol.Name}
|
||||
}
|
||||
}
|
||||
|
||||
struct UiElementAttributeData
|
||||
private struct UiElementAttributeData
|
||||
{
|
||||
public string Name;
|
||||
}
|
||||
|
||||
private struct AtUiComponentAttributeData
|
||||
{
|
||||
public string UxmlPath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
@@ -180,7 +271,8 @@ public partial class {classSymbol.Name}
|
||||
public class SyntaxReceiver : ISyntaxContextReceiver
|
||||
{
|
||||
|
||||
public List<ISymbol> Fields { get; } = new List<ISymbol>();
|
||||
public List<ISymbol> Fields { get; } = new List<ISymbol>();
|
||||
public List<INamedTypeSymbol> Classes { get; } = new List<INamedTypeSymbol>();
|
||||
|
||||
#region Implementation of ISyntaxContextReceiver
|
||||
|
||||
@@ -188,9 +280,19 @@ public partial class {classSymbol.Name}
|
||||
{
|
||||
switch ( context.Node )
|
||||
{
|
||||
case ClassDeclarationSyntax classDeclarationSyntax when classDeclarationSyntax.AttributeLists.Count > 0:
|
||||
if ( context.SemanticModel.GetDeclaredSymbol( classDeclarationSyntax ) is INamedTypeSymbol namedTypeSymbol
|
||||
&& Helpers.IsDerivedFrom( namedTypeSymbol.BaseType , "AtVisualElement" )
|
||||
&& namedTypeSymbol.GetAttributes()
|
||||
.Any( ad => ad.AttributeClass?.ToDisplayString() == Helpers.AtUiComponentAttribute ) )
|
||||
{
|
||||
Classes.Add( namedTypeSymbol );
|
||||
}
|
||||
|
||||
break;
|
||||
case FieldDeclarationSyntax fieldDeclarationSyntax when fieldDeclarationSyntax.AttributeLists.Count > 0:
|
||||
{
|
||||
foreach ( VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables )
|
||||
foreach ( var variable in fieldDeclarationSyntax.Declaration.Variables )
|
||||
{
|
||||
if ( context.SemanticModel.GetDeclaredSymbol( variable ) is IFieldSymbol symbol
|
||||
&& Helpers.IsDerivedFrom( symbol.ContainingType.BaseType , "AtVisualElement" )
|
||||
|
||||
Reference in New Issue
Block a user