diff --git a/.gitignore b/.gitignore index da6f619..5e831d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /ExampleGenerator/obj /TestConsole/obj/ +/GetComponentGenerator/obj/ /ExampleGenerator/bin /TestConsole/bin +/GetComponentGenerator/bin diff --git a/ExampleGenerator/ExampleGenerator.csproj b/ExampleGenerator/ExampleGenerator.csproj index 360c7d0..08914b9 100644 --- a/ExampleGenerator/ExampleGenerator.csproj +++ b/ExampleGenerator/ExampleGenerator.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/ExampleGenerator/Unity/Ui/UIBackingClassGenerator.cs b/ExampleGenerator/Unity/Ui/UIBackingClassGenerator.cs index ba383ed..2ceef29 100644 --- a/ExampleGenerator/Unity/Ui/UIBackingClassGenerator.cs +++ b/ExampleGenerator/Unity/Ui/UIBackingClassGenerator.cs @@ -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 = $@"// +using System; + +[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] +internal class {Helpers.AtUiComponentAttribute} : Attribute +{{ + public {Helpers.AtUiComponentAttribute}(string uxmlPath) {{ }} +}} +"; + private static readonly string UiElementAttributeText = $@"// 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 group in receiver.Fields - .GroupBy( 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( 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 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 elementFields = fieldsList.Where( f => GetUiElementAttributeData( f , uiElementAttributeSymbol ) != null ).ToList(); + if ( elementFields.Any() ) + { - var source = new StringBuilder( $@"// - -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($@"// + +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 Fields { get; } = new List(); + public List Fields { get; } = new List(); + public List Classes { get; } = new List(); #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" ) diff --git a/ExampleGenerator/Unity/Components/GetComponentGenerator.cs b/GetComponentGenerator/GetComponentGenerator.cs similarity index 99% rename from ExampleGenerator/Unity/Components/GetComponentGenerator.cs rename to GetComponentGenerator/GetComponentGenerator.cs index 374d5bb..616b8cd 100644 --- a/ExampleGenerator/Unity/Components/GetComponentGenerator.cs +++ b/GetComponentGenerator/GetComponentGenerator.cs @@ -6,8 +6,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; - -namespace ExampleGenerator.Unity.Components +namespace GetComponentGenerator { [Generator] public class GetComponentGenerator : ISourceGenerator diff --git a/GetComponentGenerator/GetComponentGenerator.csproj b/GetComponentGenerator/GetComponentGenerator.csproj new file mode 100644 index 0000000..66bc8d7 --- /dev/null +++ b/GetComponentGenerator/GetComponentGenerator.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + true + true + + + + + + + + + + + + diff --git a/TestConsole/Program.cs b/TestConsole/Program.cs index 5637536..c8fddfb 100644 --- a/TestConsole/Program.cs +++ b/TestConsole/Program.cs @@ -2,30 +2,60 @@ using System.Diagnostics; +using TestConsole; -namespace ConsoleApp; - -partial class Program +namespace TestConsole { - static void Main( string[] args ) { HelloFrom( "Generated Code" ); } - - static partial void HelloFrom( string name ); -} - -public partial class Test1 : AtVisualElement -{ - // [UxmlTrait( "health" , 9 )] - // public int MyProperty { get; set; } - - // [UxmlTrait( "health2" , 8)] public int MyField; - // [UxmlTrait( "health1" , 8)] public int MyField2{get; set; } - // [UxmlTrait( "health1" , false)] public bool MyBoolField2; - // [UxmlTrait( "health1" , "hi")] public string MyStringField2; - public void Test123() + static partial class Program { - Debug.Write( "test" ); - // Test987(); + static void Main( string[] args ) + { + var t = new Test1(); + t.Test123(); + HelloFrom( "Generated Code" ); + } + + static partial void HelloFrom( string name ); + } + + [AtUiComponent("Test123")] + public partial class Test1 : AtVisualElement + { + // protected override string UxmlPath =>""; + // [UiElement] + public AtVisualElement test; + + public void Test123() + { + Console.WriteLine( "test" ); + Console.WriteLine( $"UxmlPath: '{UxmlPath}'" ); + QueryElements(); + } + } + + public abstract class AtVisualElement + { + protected abstract string UxmlPath { get; } + // protected virtual string UxmlPath { get; } + + protected virtual void QueryElements() + { + Console.WriteLine( "Nothing overwriting this..." ); + } } } -public abstract class AtVisualElement { } +namespace UnityEngine +{ + namespace UIElements + { + static class VisualElementExtensions + { + public static AtVisualElement Q( this AtVisualElement self, string name ) + { + Console.WriteLine( $"Querying for '{name}' inside type {self.GetType().Name}" ); + return new Test1(); + } + } + } +} \ No newline at end of file diff --git a/TestConsole/TestConsole.csproj b/TestConsole/TestConsole.csproj index 44ff54c..6f9e940 100644 --- a/TestConsole/TestConsole.csproj +++ b/TestConsole/TestConsole.csproj @@ -2,13 +2,14 @@ Exe - net6.0 + net8.0 enable enable + diff --git a/TestGenerators.sln b/TestGenerators.sln index 403e2af..2ac1f41 100644 --- a/TestGenerators.sln +++ b/TestGenerators.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleGenerator", "Example EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsole", "TestConsole\TestConsole.csproj", "{4B37526B-5EE6-453B-BF68-3D5B9E9BB417}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GetComponentGenerator", "GetComponentGenerator\GetComponentGenerator.csproj", "{F52CD30D-C7EB-42F2-B4C9-8F2AB202C1EA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {4B37526B-5EE6-453B-BF68-3D5B9E9BB417}.Debug|Any CPU.Build.0 = Debug|Any CPU {4B37526B-5EE6-453B-BF68-3D5B9E9BB417}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B37526B-5EE6-453B-BF68-3D5B9E9BB417}.Release|Any CPU.Build.0 = Release|Any CPU + {F52CD30D-C7EB-42F2-B4C9-8F2AB202C1EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F52CD30D-C7EB-42F2-B4C9-8F2AB202C1EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F52CD30D-C7EB-42F2-B4C9-8F2AB202C1EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F52CD30D-C7EB-42F2-B4C9-8F2AB202C1EA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal