Browse Source

Merge branch 'master' into flyout-api

Dan Walmsley 2 years ago
parent
commit
d53844e352
96 changed files with 2972 additions and 70 deletions
  1. 2 2
      .nuke/build.schema.json
  2. 21 0
      Avalonia.sln
  3. 16 1
      build/SourceGenerators.props
  4. 7 5
      nukebuild/Build.cs
  5. 5 0
      nukebuild/numerge.config
  6. 5 1
      packages/Avalonia/Avalonia.csproj
  7. 3 11
      samples/ControlCatalog/ControlCatalog.csproj
  8. 1 7
      samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
  9. 2 9
      samples/ControlCatalog/Pages/LabelsPage.axaml.cs
  10. 3 11
      samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs
  11. 3 10
      samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs
  12. 8 12
      samples/ControlCatalog/Pages/ThemePage.axaml.cs
  13. 7 0
      samples/Generators.Sandbox/App.xaml
  14. 20 0
      samples/Generators.Sandbox/App.xaml.cs
  15. 10 0
      samples/Generators.Sandbox/Controls/CustomTextBox.cs
  16. 45 0
      samples/Generators.Sandbox/Controls/SignUpView.xaml
  17. 54 0
      samples/Generators.Sandbox/Controls/SignUpView.xaml.cs
  18. 28 0
      samples/Generators.Sandbox/Generators.Sandbox.csproj
  19. 15 0
      samples/Generators.Sandbox/Program.cs
  20. 70 0
      samples/Generators.Sandbox/ViewModels/SignUpViewModel.cs
  21. 9 0
      samples/Generators.Sandbox/Views/SignUpView.xaml
  22. 28 0
      samples/Generators.Sandbox/Views/SignUpView.xaml.cs
  23. 32 0
      src/tools/Avalonia.Generators/Avalonia.Generators.csproj
  24. 22 0
      src/tools/Avalonia.Generators/Avalonia.Generators.props
  25. 9 0
      src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs
  26. 6 0
      src/tools/Avalonia.Generators/Common/Domain/IGlobPattern.cs
  27. 19 0
      src/tools/Avalonia.Generators/Common/Domain/INameResolver.cs
  28. 11 0
      src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs
  29. 18 0
      src/tools/Avalonia.Generators/Common/GlobPattern.cs
  30. 17 0
      src/tools/Avalonia.Generators/Common/GlobPatternGroup.cs
  31. 25 0
      src/tools/Avalonia.Generators/Common/ResolverExtensions.cs
  32. 92 0
      src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs
  33. 100 0
      src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs
  34. 17 0
      src/tools/Avalonia.Generators/Compiler/DataTemplateTransformer.cs
  35. 50 0
      src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
  36. 28 0
      src/tools/Avalonia.Generators/Compiler/NameDirectiveTransformer.cs
  37. 276 0
      src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs
  38. 36 0
      src/tools/Avalonia.Generators/GeneratorContextExtensions.cs
  39. 71 0
      src/tools/Avalonia.Generators/GeneratorOptions.cs
  40. 63 0
      src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameGenerator.cs
  41. 60 0
      src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameSourceGenerator.cs
  42. 11 0
      src/tools/Avalonia.Generators/NameGenerator/INameGenerator.cs
  43. 83 0
      src/tools/Avalonia.Generators/NameGenerator/InitializeComponentCodeGenerator.cs
  44. 31 0
      src/tools/Avalonia.Generators/NameGenerator/OnlyPropertiesCodeGenerator.cs
  45. 21 0
      src/tools/Avalonia.Generators/NameGenerator/Options.cs
  46. 8 0
      src/tools/Avalonia.Generators/Properties/launchSettings.json
  47. 209 0
      src/tools/Avalonia.Generators/README.md
  48. 26 0
      tests/Avalonia.Generators.Tests/Avalonia.Generators.Tests.csproj
  49. 31 0
      tests/Avalonia.Generators.Tests/GlobPatternTests.cs
  50. 28 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/AttachedProps.txt
  51. 36 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/AttachedPropsWithDevTools.txt
  52. 28 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/ControlWithoutWindow.txt
  53. 32 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/CustomControls.txt
  54. 30 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/DataTemplates.txt
  55. 38 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/FieldModifier.txt
  56. 35 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/InitializeComponentCode.cs
  57. 28 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/NamedControl.txt
  58. 32 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/NamedControls.txt
  59. 28 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/NoNamedControls.txt
  60. 46 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/SignUpView.txt
  61. 28 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/xNamedControl.txt
  62. 32 0
      tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/xNamedControls.txt
  63. 63 0
      tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs
  64. 59 0
      tests/Avalonia.Generators.Tests/MiniCompilerTests.cs
  65. 11 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/AttachedProps.txt
  66. 11 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/ControlWithoutWindow.txt
  67. 13 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/CustomControls.txt
  68. 12 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/DataTemplates.txt
  69. 16 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/FieldModifier.txt
  70. 11 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/NamedControl.txt
  71. 13 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/NamedControls.txt
  72. 11 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/NoNamedControls.txt
  73. 33 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/OnlyPropertiesCode.cs
  74. 20 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/SignUpView.txt
  75. 11 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/xNamedControl.txt
  76. 13 0
      tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/xNamedControls.txt
  77. 52 0
      tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs
  78. 10 0
      tests/Avalonia.Generators.Tests/Views/AttachedProps.xml
  79. 10 0
      tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml
  80. 11 0
      tests/Avalonia.Generators.Tests/Views/CustomControls.xml
  81. 18 0
      tests/Avalonia.Generators.Tests/Views/DataTemplates.xml
  82. 28 0
      tests/Avalonia.Generators.Tests/Views/FieldModifier.xml
  83. 7 0
      tests/Avalonia.Generators.Tests/Views/NamedControl.xml
  84. 14 0
      tests/Avalonia.Generators.Tests/Views/NamedControls.xml
  85. 6 0
      tests/Avalonia.Generators.Tests/Views/NoNamedControls.xml
  86. 52 0
      tests/Avalonia.Generators.Tests/Views/SignUpView.xml
  87. 76 0
      tests/Avalonia.Generators.Tests/Views/View.cs
  88. 13 0
      tests/Avalonia.Generators.Tests/Views/ViewWithGenericBaseView.xml
  89. 7 0
      tests/Avalonia.Generators.Tests/Views/xNamedControl.xml
  90. 14 0
      tests/Avalonia.Generators.Tests/Views/xNamedControls.xml
  91. 40 0
      tests/Avalonia.Generators.Tests/XamlXClassResolverTests.cs
  92. 141 0
      tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs
  93. 1 0
      tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs
  94. 9 1
      tests/Avalonia.RenderTests/TestBase.cs
  95. 6 0
      tests/Avalonia.UnitTests/TestWithServicesBase.cs
  96. 5 0
      tests/Avalonia.UnitTests/UnitTestApplication.cs

+ 2 - 2
.nuke/build.schema.json

@@ -84,11 +84,11 @@
               "GenerateCppHeaders",
               "Package",
               "RunCoreLibsTests",
-              "RunDesignerTests",
               "RunHtmlPreviewerTests",
               "RunLeakTests",
               "RunRenderTests",
               "RunTests",
+              "RunToolsTests",
               "ZipFiles"
             ]
           }
@@ -123,11 +123,11 @@
               "GenerateCppHeaders",
               "Package",
               "RunCoreLibsTests",
-              "RunDesignerTests",
               "RunHtmlPreviewerTests",
               "RunLeakTests",
               "RunRenderTests",
               "RunTests",
+              "RunToolsTests",
               "ZipFiles"
             ]
           }

+ 21 - 0
Avalonia.sln

@@ -244,8 +244,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepe
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Generators", "src\tools\Avalonia.Generators\Avalonia.Generators.csproj", "{DDA28789-C21A-4654-86CE-D01E81F095C5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Generators.Tests", "tests\Avalonia.Generators.Tests\Avalonia.Generators.Tests.csproj", "{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}"
+EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Fonts.Inter", "src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj", "{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Generators.Sandbox", "samples\Generators.Sandbox\Generators.Sandbox.csproj", "{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -573,10 +579,22 @@ Global
 		{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DDA28789-C21A-4654-86CE-D01E81F095C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DDA28789-C21A-4654-86CE-D01E81F095C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DDA28789-C21A-4654-86CE-D01E81F095C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DDA28789-C21A-4654-86CE-D01E81F095C5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F}.Release|Any CPU.Build.0 = Release|Any CPU
 		{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -643,7 +661,10 @@ Global
 		{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+		{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+		{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+		{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 16 - 1
build/SourceGenerators.props

@@ -1,5 +1,10 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup>
+  <PropertyGroup>
+    <IncludeDevGenerators Condition="'$(IncludeDevGenerators)' == ''">true</IncludeDevGenerators>
+    <IncludeAvaloniaGenerators Condition="'$(IncludeAvaloniaGenerators)' == ''">false</IncludeAvaloniaGenerators>
+  </PropertyGroup>
+
+  <ItemGroup Condition="'$(IncludeDevGenerators)' == 'true'">
     <ProjectReference 
       Include="$(MSBuildThisFileDirectory)/../src/tools/DevGenerators/DevGenerators.csproj"
       OutputItemType="Analyzer" 
@@ -7,4 +12,14 @@
       PrivateAssets="all" />
     <Compile Include="$(MSBuildThisFileDirectory)/../src/Shared/SourceGeneratorAttributes.cs" />
   </ItemGroup>
+
+  <ItemGroup Condition="'$(IncludeAvaloniaGenerators)' == 'true'">
+    <ProjectReference
+      Include="../../src/tools/Avalonia.Generators/Avalonia.Generators.csproj"
+      OutputItemType="Analyzer"
+      ReferenceOutputAssembly="false"
+      PrivateAssets="all" />
+  </ItemGroup>
+  <Import Project="$(MSBuildThisFileDirectory)/../src/tools/Avalonia.Generators/Avalonia.Generators.props"
+          Condition="'$(IncludeDevGenerators)' == 'true'" />
 </Project>

+ 7 - 5
nukebuild/Build.cs

@@ -220,16 +220,18 @@ partial class Build : NukeBuild
         .Executes(() =>
         {
             RunCoreTest("Avalonia.Skia.RenderTests");
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            if (Parameters.IsRunningOnWindows)
                 RunCoreTest("Avalonia.Direct2D1.RenderTests");
         });
 
-    Target RunDesignerTests => _ => _
-        .OnlyWhenStatic(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
+    Target RunToolsTests => _ => _
+        .OnlyWhenStatic(() => !Parameters.SkipTests)
         .DependsOn(Compile)
         .Executes(() =>
         {
-            RunCoreTest("Avalonia.DesignerSupport.Tests");
+            RunCoreTest("Avalonia.Generators.Tests");
+            if (Parameters.IsRunningOnWindows)
+                RunCoreTest("Avalonia.DesignerSupport.Tests");
         });
 
     Target RunLeakTests => _ => _
@@ -276,7 +278,7 @@ partial class Build : NukeBuild
     Target RunTests => _ => _
         .DependsOn(RunCoreLibsTests)
         .DependsOn(RunRenderTests)
-        .DependsOn(RunDesignerTests)
+        .DependsOn(RunToolsTests)
         .DependsOn(RunHtmlPreviewerTests)
         .DependsOn(RunLeakTests);
 

+ 5 - 0
nukebuild/numerge.config

@@ -11,6 +11,11 @@
           "Id": "Avalonia.Build.Tasks",
           "IgnoreMissingFrameworkBinaries": true,
           "DoNotMergeDependencies": true
+        },
+        {
+          "Id": "Avalonia.Generators",
+          "IgnoreMissingFrameworkBinaries": true,
+          "DoNotMergeDependencies": true
         }
       ]
     }

+ 5 - 1
packages/Avalonia/Avalonia.csproj

@@ -6,11 +6,15 @@
 
   <ItemGroup>
       <ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
-      <ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" >
+      <ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
         <PrivateAssets>all</PrivateAssets>
         <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
         <SetTargetFramework>TargetFramework=netstandard2.0</SetTargetFramework>
       </ProjectReference>
+      <ProjectReference Include="..\..\src\tools\Avalonia.Generators\Avalonia.Generators.csproj"
+                        ReferenceOutputAssembly="false"
+                        PrivateAssets="all"
+                        OutputItemType="Analyzer" />
   </ItemGroup>
 
   <PropertyGroup>

+ 3 - 11
samples/ControlCatalog/ControlCatalog.csproj

@@ -2,7 +2,8 @@
   <PropertyGroup>
     <TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <Nullable>enable</Nullable>    
+    <Nullable>enable</Nullable>
+    <IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
   </PropertyGroup>
   <ItemGroup>
     <Compile Update="**\*.xaml.cs">
@@ -35,14 +36,5 @@
   </ItemGroup>
 
   <Import Project="..\..\build\BuildTargets.targets" />
-
-  <ItemGroup>
-    <None Remove="Pages\CustomDrawing.xaml" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <AvaloniaResource Update="Pages\CustomDrawing.xaml">
-      <Generator></Generator>
-    </AvaloniaResource>
-  </ItemGroup>
+  <Import Project="..\..\build\SourceGenerators.props" />
 </Project>

+ 1 - 7
samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs

@@ -1,11 +1,10 @@
 using Avalonia.Controls;
 using Avalonia.Controls.Primitives;
-using Avalonia.Markup.Xaml;
 using Avalonia.Interactivity;
 
 namespace ControlCatalog.Pages
 {
-    public class FlyoutsPage : UserControl
+    public partial class FlyoutsPage : UserControl
     {
         public FlyoutsPage()
         {
@@ -28,11 +27,6 @@ namespace ControlCatalog.Pages
             }
         }
 
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
-
         private void SetXamlTexts()
         {
             var bfxt = this.Get<TextBlock>("ButtonFlyoutXamlText");

+ 2 - 9
samples/ControlCatalog/Pages/LabelsPage.axaml.cs

@@ -1,11 +1,9 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
+using Avalonia.Controls;
 using ControlCatalog.Models;
 
 namespace ControlCatalog.Pages
 {
-    public class LabelsPage : UserControl
+    public partial class LabelsPage : UserControl
     {
         private Person? _person;
 
@@ -25,11 +23,6 @@ namespace ControlCatalog.Pages
             };
         }
 
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
-
         public void DoSave()
         {
             

+ 3 - 11
samples/ControlCatalog/Pages/RefreshContainerPage.axaml.cs

@@ -1,18 +1,15 @@
-using System.Threading.Tasks;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
+using Avalonia.Controls;
 using ControlCatalog.ViewModels;
 
 namespace ControlCatalog.Pages
 {
-    public class RefreshContainerPage : UserControl
+    public partial class RefreshContainerPage : UserControl
     {
         private RefreshContainerViewModel _viewModel;
 
         public RefreshContainerPage()
         {
-            this.InitializeComponent();
+            InitializeComponent();
 
             _viewModel = new RefreshContainerViewModel();
 
@@ -27,10 +24,5 @@ namespace ControlCatalog.Pages
 
             deferral.Complete();
         }
-
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
-        }
     }
 }

+ 3 - 10
samples/ControlCatalog/Pages/RelativePanelPage.axaml.cs

@@ -1,19 +1,12 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
+using Avalonia.Controls;
 
 namespace ControlCatalog.Pages
 {
-    public class RelativePanelPage : UserControl
+    public partial class RelativePanelPage : UserControl
     {
         public RelativePanelPage()
         {
-            this.InitializeComponent();
-        }
-
-        private void InitializeComponent()
-        {
-            AvaloniaXamlLoader.Load(this);
+            InitializeComponent();
         }
     }
 }

+ 8 - 12
samples/ControlCatalog/Pages/ThemePage.axaml.cs

@@ -1,35 +1,31 @@
-using Avalonia;
-using Avalonia.Controls;
+using Avalonia.Controls;
 using Avalonia.Markup.Xaml;
 using Avalonia.Styling;
 
 namespace ControlCatalog.Pages
 {
-    public class ThemePage : UserControl
+    public partial class ThemePage : UserControl
     {
         public static ThemeVariant Pink { get; } = new("Pink", ThemeVariant.Light);
         
         public ThemePage()
         {
-            AvaloniaXamlLoader.Load(this);
+            InitializeComponent();
 
-            var selector = this.FindControl<ComboBox>("Selector")!;
-            var themeVariantScope = this.FindControl<ThemeVariantScope>("ThemeVariantScope")!;
-
-            selector.Items = new[]
+            Selector.Items = new[]
             {
                 ThemeVariant.Default,
                 ThemeVariant.Dark,
                 ThemeVariant.Light,
                 Pink
             };
-            selector.SelectedIndex = 0;
+            Selector.SelectedIndex = 0;
 
-            selector.SelectionChanged += (_, _) =>
+            Selector.SelectionChanged += (_, _) =>
             {
-                if (selector.SelectedItem is ThemeVariant theme)
+                if (Selector.SelectedItem is ThemeVariant theme)
                 {
-                    themeVariantScope.RequestedThemeVariant = theme;
+                    ThemeVariantScope.RequestedThemeVariant = theme;
                 }
             };
         }

+ 7 - 0
samples/Generators.Sandbox/App.xaml

@@ -0,0 +1,7 @@
+<Application xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="Generators.Sandbox.App">
+    <Application.Styles>
+        <FluentTheme />
+    </Application.Styles>
+</Application>

+ 20 - 0
samples/Generators.Sandbox/App.xaml.cs

@@ -0,0 +1,20 @@
+using Avalonia;
+using Avalonia.Markup.Xaml;
+using Generators.Sandbox.ViewModels;
+
+namespace Generators.Sandbox;
+
+public class App : Application
+{
+    public override void Initialize() => AvaloniaXamlLoader.Load(this);
+
+    public override void OnFrameworkInitializationCompleted()
+    {
+        var view = new Views.SignUpView
+        {
+            ViewModel = new SignUpViewModel()
+        };
+        view.Show();
+        base.OnFrameworkInitializationCompleted();
+    }
+}

+ 10 - 0
samples/Generators.Sandbox/Controls/CustomTextBox.cs

@@ -0,0 +1,10 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Styling;
+
+namespace Generators.Sandbox.Controls;
+
+public class CustomTextBox : TextBox, IStyleable
+{
+    Type IStyleable.StyleKey => typeof(TextBox);
+}

+ 45 - 0
samples/Generators.Sandbox/Controls/SignUpView.xaml

@@ -0,0 +1,45 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:controls="using:Generators.Sandbox.Controls"
+        x:Class="Generators.Sandbox.Controls.SignUpView">
+    <StackPanel>
+        <controls:CustomTextBox Margin="0 10 0 0"
+                 x:Name="UserNameTextBox"
+                 Watermark="Please, enter user name..."
+                 UseFloatingWatermark="True" />
+        <TextBlock x:Name="UserNameValidation" 
+                   Foreground="Red"
+                   FontSize="12" />
+        <TextBox Margin="0 10 0 0"
+                 x:Name="PasswordTextBox"
+                 Watermark="Please, enter your password..."
+                 UseFloatingWatermark="True"
+                 PasswordChar="*" />
+        <TextBlock x:Name="PasswordValidation" 
+                   Foreground="Red"
+                   FontSize="12" />
+        <TextBox Margin="0 10 0 0"
+                 x:Name="ConfirmPasswordTextBox"
+                 Watermark="Please, confirm the password..."
+                 UseFloatingWatermark="True"
+                 PasswordChar="*" />
+        <TextBlock x:Name="ConfirmPasswordValidation"
+                   TextWrapping="Wrap"
+                   Foreground="Red"
+                   FontSize="12" />
+        <TextBlock>
+            <TextBlock.Inlines>
+                <InlineCollection>
+                    <Run x:Name="SignUpButtonDescription" />
+                </InlineCollection>
+            </TextBlock.Inlines>
+        </TextBlock>
+        <Button Margin="0 10 0 5"
+                Content="Sign up"
+                x:Name="SignUpButton" />
+        <TextBlock x:Name="CompoundValidation"
+                   TextWrapping="Wrap"
+                   Foreground="Red"
+                   FontSize="12" />
+    </StackPanel>
+</UserControl>

+ 54 - 0
samples/Generators.Sandbox/Controls/SignUpView.xaml.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Reactive.Disposables;
+using Avalonia.ReactiveUI;
+using Generators.Sandbox.ViewModels;
+using ReactiveUI;
+using ReactiveUI.Validation.Extensions;
+using ReactiveUI.Validation.Formatters;
+
+namespace Generators.Sandbox.Controls;
+
+/// <summary>
+/// This is a sample view class with typed x:Name references generated using
+/// .NET 5 source generators. The class has to be partial because x:Name
+/// references are living in a separate partial class file. See also:
+/// https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/
+/// </summary>
+public partial class SignUpView : ReactiveUserControl<SignUpViewModel>
+{
+    public SignUpView()
+    {
+        // The InitializeComponent method is also generated automatically
+        // and lives in the autogenerated part of the partial class.
+        InitializeComponent();
+        this.WhenActivated(disposables =>
+        {
+            this.Bind(ViewModel, x => x.UserName, x => x.UserNameTextBox.Text)
+                .DisposeWith(disposables);
+            this.Bind(ViewModel, x => x.Password, x => x.PasswordTextBox.Text)
+                .DisposeWith(disposables);
+            this.Bind(ViewModel, x => x.ConfirmPassword, x => x.ConfirmPasswordTextBox.Text)
+                .DisposeWith(disposables);
+            this.BindCommand(ViewModel, x => x.SignUp, x => x.SignUpButton)
+                .DisposeWith(disposables);
+
+            this.BindValidation(ViewModel, x => x.UserName, x => x.UserNameValidation.Text)
+                .DisposeWith(disposables);
+            this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
+                .DisposeWith(disposables);
+            this.BindValidation(ViewModel, x => x.ConfirmPassword, x => x.ConfirmPasswordValidation.Text)
+                .DisposeWith(disposables);
+
+            var newLineFormatter = new SingleLineFormatter(Environment.NewLine);
+            this.BindValidation(ViewModel, x => x.CompoundValidation.Text, newLineFormatter)
+                .DisposeWith(disposables);
+
+            // The references to text boxes below are also auto generated.
+            // Use Ctrl+Click in order to view the generated sources. 
+            UserNameTextBox.Text = "Joseph!";
+            PasswordTextBox.Text = "1234";
+            ConfirmPasswordTextBox.Text = "1234";
+            SignUpButtonDescription.Text = "Press the button below to sign up.";
+        });
+    }
+}

+ 28 - 0
samples/Generators.Sandbox/Generators.Sandbox.csproj

@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <AvaloniaResource Include="**\*.xaml"/>
+    <!-- Note this AdditionalFiles directive. -->
+    <AdditionalFiles Include="**\*.xaml"/>
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="ReactiveUI.Validation" Version="3.0.22"/>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj"/>
+    <ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj"/>
+    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj"/>
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj"/>
+    <ProjectReference Include="..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj"/>
+  </ItemGroup>
+
+  <Import Project="..\..\build\BuildTargets.targets"/>
+  <Import Project="..\..\build\SourceGenerators.props"/>
+</Project>

+ 15 - 0
samples/Generators.Sandbox/Program.cs

@@ -0,0 +1,15 @@
+using Avalonia;
+using Avalonia.ReactiveUI;
+
+namespace Generators.Sandbox;
+
+internal static class Program
+{
+    public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+
+    private static AppBuilder BuildAvaloniaApp()
+        => AppBuilder.Configure<App>()
+            .UseReactiveUI()
+            .UsePlatformDetect()
+            .LogToTrace();
+}

+ 70 - 0
samples/Generators.Sandbox/ViewModels/SignUpViewModel.cs

@@ -0,0 +1,70 @@
+using System.Reactive;
+using ReactiveUI;
+using ReactiveUI.Validation.Extensions;
+using ReactiveUI.Validation.Helpers;
+
+namespace Generators.Sandbox.ViewModels;
+
+public class SignUpViewModel : ReactiveValidationObject
+{
+    private string _userName = string.Empty;
+    private string _password = string.Empty;
+    private string _confirmPassword = string.Empty;
+
+    public SignUpViewModel()
+    {
+        this.ValidationRule(
+            vm => vm.UserName,
+            name => !string.IsNullOrWhiteSpace(name),
+            "UserName is required.");
+
+        this.ValidationRule(
+            vm => vm.Password,
+            password => !string.IsNullOrWhiteSpace(password),
+            "Password is required.");
+
+        this.ValidationRule(
+            vm => vm.Password,
+            password => password?.Length > 2,
+            password => $"Password should be longer, current length: {password.Length}");
+
+        this.ValidationRule(
+            vm => vm.ConfirmPassword,
+            confirmation => !string.IsNullOrWhiteSpace(confirmation),
+            "Confirm password field is required.");
+
+        var passwordsObservable =
+            this.WhenAnyValue(
+                x => x.Password,
+                x => x.ConfirmPassword,
+                (password, confirmation) =>
+                    password == confirmation);
+
+        this.ValidationRule(
+            vm => vm.ConfirmPassword,
+            passwordsObservable,
+            "Passwords must match.");
+
+        SignUp = ReactiveCommand.Create(() => {}, this.IsValid());
+    }
+
+    public ReactiveCommand<Unit, Unit> SignUp { get; }
+
+    public string UserName
+    {
+        get => _userName;
+        set => this.RaiseAndSetIfChanged(ref _userName, value);
+    }
+
+    public string Password
+    {
+        get => _password;
+        set => this.RaiseAndSetIfChanged(ref _password, value);
+    }
+
+    public string ConfirmPassword
+    {
+        get => _confirmPassword;
+        set => this.RaiseAndSetIfChanged(ref _confirmPassword, value);
+    }
+}

+ 9 - 0
samples/Generators.Sandbox/Views/SignUpView.xaml

@@ -0,0 +1,9 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:controls="using:Generators.Sandbox.Controls"
+        x:Class="Generators.Sandbox.Views.SignUpView">
+    <StackPanel Margin="10">
+        <TextBlock Text="Sign Up" />
+        <controls:SignUpView x:Name="SignUpControl" />
+    </StackPanel>
+</Window>

+ 28 - 0
samples/Generators.Sandbox/Views/SignUpView.xaml.cs

@@ -0,0 +1,28 @@
+using System.Reactive.Disposables;
+using Avalonia.ReactiveUI;
+using Generators.Sandbox.ViewModels;
+using ReactiveUI;
+
+namespace Generators.Sandbox.Views;
+
+/// <summary>
+/// This is a sample view class with typed x:Name references generated using
+/// .NET 5 source generators. The class has to be partial because x:Name
+/// references are living in a separate partial class file. See also:
+/// https://devblogs.microsoft.com/dotnet/new-c-source-generator-samples/
+/// </summary>
+public partial class SignUpView : ReactiveWindow<SignUpViewModel>
+{
+    public SignUpView()
+    {
+        // The InitializeComponent method is also generated automatically
+        // and lives in the autogenerated part of the partial class.
+        InitializeComponent();
+        this.WhenActivated(disposables =>
+        {
+            this.WhenAnyValue(view => view.ViewModel)
+                .BindTo(this, view => view.SignUpControl.ViewModel)
+                .DisposeWith(disposables);
+        });
+    }
+}

+ 32 - 0
src/tools/Avalonia.Generators/Avalonia.Generators.csproj

@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <IncludeBuildOutput>false</IncludeBuildOutput>
+    <PackageId>Avalonia.Generators</PackageId>
+    <DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
+    <IsPackable>true</IsPackable>
+    <IsRoslynComponent>true</IsRoslynComponent>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Link="Compiler\XamlX\filename" Include="../../Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/src/XamlX/**/*.cs" />
+    <Compile Remove="../../Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/src/XamlX/**/SreTypeSystem.cs" />
+    <Compile Include="..\..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Include="Avalonia.Generators.props" Pack="true" PackagePath="buildTransitive/$(PackageId).props" />
+    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
+  </ItemGroup>
+
+  <ItemGroup Label="InternalsVisibleTo">
+    <InternalsVisibleTo Include="Avalonia.Generators.Tests, PublicKey=$(AvaloniaPublicKey)" />
+  </ItemGroup>
+
+  <Import Project="..\..\..\build\TrimmingEnable.props" />
+</Project>

+ 22 - 0
src/tools/Avalonia.Generators/Avalonia.Generators.props

@@ -0,0 +1,22 @@
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <AvaloniaNameGeneratorIsEnabled Condition="'$(AvaloniaNameGeneratorIsEnabled)' == ''">true</AvaloniaNameGeneratorIsEnabled>
+    <AvaloniaNameGeneratorBehavior Condition="'$(AvaloniaNameGeneratorBehavior)' == ''">InitializeComponent</AvaloniaNameGeneratorBehavior>
+    <AvaloniaNameGeneratorDefaultFieldModifier Condition="'$(AvaloniaNameGeneratorDefaultFieldModifier)' == ''">internal</AvaloniaNameGeneratorDefaultFieldModifier>
+    <AvaloniaNameGeneratorFilterByPath Condition="'$(AvaloniaNameGeneratorFilterByPath)' == ''">*</AvaloniaNameGeneratorFilterByPath>
+    <AvaloniaNameGeneratorFilterByNamespace Condition="'$(AvaloniaNameGeneratorFilterByNamespace)' == ''">*</AvaloniaNameGeneratorFilterByNamespace>
+  </PropertyGroup>
+  <ItemGroup>
+    <CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="SourceItemGroup"/>
+    <CompilerVisibleProperty Include="AvaloniaNameGeneratorIsEnabled" />
+    <CompilerVisibleProperty Include="AvaloniaNameGeneratorBehavior" />
+    <CompilerVisibleProperty Include="AvaloniaNameGeneratorDefaultFieldModifier" />
+    <CompilerVisibleProperty Include="AvaloniaNameGeneratorFilterByPath" />
+    <CompilerVisibleProperty Include="AvaloniaNameGeneratorFilterByNamespace" />
+  </ItemGroup>
+  <Target Name="_InjectAdditionalFiles" BeforeTargets="GenerateMSBuildEditorConfigFileShouldRun">
+    <ItemGroup>
+      <AdditionalFiles Include="@(AvaloniaXaml)" SourceItemGroup="AvaloniaXaml" />
+    </ItemGroup>
+  </Target>
+</Project>

+ 9 - 0
src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs

@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Common.Domain;
+
+internal interface ICodeGenerator
+{
+    string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names);
+}

+ 6 - 0
src/tools/Avalonia.Generators/Common/Domain/IGlobPattern.cs

@@ -0,0 +1,6 @@
+namespace Avalonia.Generators.Common.Domain;
+
+internal interface IGlobPattern
+{
+    bool Matches(string str);
+}

+ 19 - 0
src/tools/Avalonia.Generators/Common/Domain/INameResolver.cs

@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using XamlX.Ast;
+
+namespace Avalonia.Generators.Common.Domain;
+
+internal enum NamedFieldModifier
+{
+    Public = 0,
+    Private = 1,
+    Internal = 2,
+    Protected = 3,
+}
+
+internal interface INameResolver
+{
+    IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml);
+}
+
+internal record ResolvedName(string TypeName, string Name, string FieldModifier);

+ 11 - 0
src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs

@@ -0,0 +1,11 @@
+using XamlX.Ast;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Common.Domain;
+
+internal interface IViewResolver
+{
+    ResolvedView ResolveView(string xaml);
+}
+
+internal record ResolvedView(string ClassName, IXamlType XamlType, string Namespace, XamlDocument Xaml);

+ 18 - 0
src/tools/Avalonia.Generators/Common/GlobPattern.cs

@@ -0,0 +1,18 @@
+using System.Text.RegularExpressions;
+using Avalonia.Generators.Common.Domain;
+
+namespace Avalonia.Generators.Common;
+
+internal class GlobPattern : IGlobPattern
+{
+    private const RegexOptions GlobOptions = RegexOptions.IgnoreCase | RegexOptions.Singleline;
+    private readonly Regex _regex;
+
+    public GlobPattern(string pattern)
+    {
+        var expression = "^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$";
+        _regex = new Regex(expression, GlobOptions);
+    }
+
+    public bool Matches(string str) => _regex.IsMatch(str);
+}

+ 17 - 0
src/tools/Avalonia.Generators/Common/GlobPatternGroup.cs

@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Generators.Common.Domain;
+
+namespace Avalonia.Generators.Common;
+
+internal class GlobPatternGroup : IGlobPattern
+{
+    private readonly GlobPattern[] _patterns;
+
+    public GlobPatternGroup(IEnumerable<string> patterns) =>
+        _patterns = patterns
+            .Select(pattern => new GlobPattern(pattern))
+            .ToArray();
+
+    public bool Matches(string str) => _patterns.Any(pattern => pattern.Matches(str));
+}

+ 25 - 0
src/tools/Avalonia.Generators/Common/ResolverExtensions.cs

@@ -0,0 +1,25 @@
+using System.Linq;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Common;
+
+internal static class ResolverExtensions
+{
+    public static bool IsAvaloniaStyledElement(this IXamlType clrType) =>
+        clrType.HasStyledElementBaseType() ||
+        clrType.HasIStyledElementInterface();
+
+    private static bool HasStyledElementBaseType(this IXamlType clrType)
+    {
+        // Check for the base type since IStyledElement interface is removed.
+        // https://github.com/AvaloniaUI/Avalonia/pull/9553
+        if (clrType.FullName == "Avalonia.StyledElement")
+            return true;
+        return clrType.BaseType != null && IsAvaloniaStyledElement(clrType.BaseType);
+    }
+
+    private static bool HasIStyledElementInterface(this IXamlType clrType) =>
+        clrType.Interfaces.Any(abstraction =>
+            abstraction.IsInterface &&
+            abstraction.FullName == "Avalonia.IStyledElement");
+}

+ 92 - 0
src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs

@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Avalonia.Generators.Common.Domain;
+using XamlX;
+using XamlX.Ast;
+
+namespace Avalonia.Generators.Common;
+
+internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
+{
+    private readonly List<ResolvedName> _items = new();
+    private readonly string _defaultFieldModifier;
+
+    public XamlXNameResolver(NamedFieldModifier namedFieldModifier = NamedFieldModifier.Internal)
+    {
+        _defaultFieldModifier = namedFieldModifier.ToString().ToLowerInvariant();
+    }
+
+    public IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml)
+    {
+        _items.Clear();
+        xaml.Root.Visit(this);
+        xaml.Root.VisitChildren(this);
+        return _items;
+    }
+
+    IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
+    {
+        if (node is not XamlAstObjectNode objectNode)
+            return node;
+
+        var clrType = objectNode.Type.GetClrType();
+        if (!clrType.IsAvaloniaStyledElement())
+            return node;
+
+        foreach (var child in objectNode.Children)
+        {
+            if (child is XamlAstXamlPropertyValueNode propertyValueNode &&
+                propertyValueNode.Property is XamlAstNamePropertyReference namedProperty &&
+                namedProperty.Name == "Name" &&
+                propertyValueNode.Values.Count > 0 &&
+                propertyValueNode.Values[0] is XamlAstTextNode text)
+            {
+                var fieldModifier = TryGetFieldModifier(objectNode);
+                var typeName = $@"{clrType.Namespace}.{clrType.Name}";
+                var typeAgs = clrType.GenericArguments.Select(arg => arg.FullName).ToImmutableList();
+                var genericTypeName = typeAgs.Count == 0
+                    ? $"global::{typeName}"
+                    : $@"global::{typeName}<{string.Join(", ", typeAgs.Select(arg => $"global::{arg}"))}>";
+
+                var resolvedName = new ResolvedName(genericTypeName, text.Text, fieldModifier);
+                if (_items.Contains(resolvedName))
+                    continue;
+                _items.Add(resolvedName);
+            }
+        }
+
+        return node;
+    }
+
+    void IXamlAstVisitor.Push(IXamlAstNode node) { }
+
+    void IXamlAstVisitor.Pop() { }
+
+    private string TryGetFieldModifier(XamlAstObjectNode objectNode)
+    {
+        // We follow Xamarin.Forms API behavior in terms of x:FieldModifier here:
+        // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/field-modifiers
+        // However, by default we use 'internal' field modifier here for generated
+        // x:Name references for historical purposes and WPF compatibility.
+        //
+        var fieldModifierType = objectNode
+            .Children
+            .OfType<XamlAstXmlDirective>()
+            .Where(dir => dir.Name == "FieldModifier" && dir.Namespace == XamlNamespaces.Xaml2006)
+            .Select(dir => dir.Values[0])
+            .OfType<XamlAstTextNode>()
+            .Select(txt => txt.Text)
+            .FirstOrDefault();
+
+        return fieldModifierType?.ToLowerInvariant() switch
+        {
+            "private" => "private",
+            "public" => "public",
+            "protected" => "protected",
+            "internal" => "internal",
+            "notpublic" => "internal",
+            _ => _defaultFieldModifier
+        };
+    }
+}

+ 100 - 0
src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Generators.Common.Domain;
+using Avalonia.Generators.Compiler;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Parsers;
+
+namespace Avalonia.Generators.Common;
+
+internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
+{
+    private readonly RoslynTypeSystem _typeSystem;
+    private readonly MiniCompiler _compiler;
+    private readonly bool _checkTypeValidity;
+    private readonly Action<string> _onTypeInvalid;
+    private readonly Action<Exception> _onUnhandledError;
+
+    private ResolvedView _resolvedClass;
+    private XamlDocument _xaml;
+
+    public XamlXViewResolver(
+        RoslynTypeSystem typeSystem,
+        MiniCompiler compiler,
+        bool checkTypeValidity = false,
+        Action<string> onTypeInvalid = null,
+        Action<Exception> onUnhandledError = null)
+    {
+        _checkTypeValidity = checkTypeValidity;
+        _onTypeInvalid = onTypeInvalid;
+        _onUnhandledError = onUnhandledError;
+        _typeSystem = typeSystem;
+        _compiler = compiler;
+    }
+
+    public ResolvedView ResolveView(string xaml)
+    {
+        try
+        {
+            _resolvedClass = null;
+            _xaml = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
+            {
+                {XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
+            });
+
+            _compiler.Transform(_xaml);
+            _xaml.Root.Visit(this);
+            _xaml.Root.VisitChildren(this);
+            return _resolvedClass;
+        }
+        catch (Exception exception)
+        {
+            _onUnhandledError?.Invoke(exception);
+            return null;
+        }
+    }
+    
+    IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
+    {
+        if (node is not XamlAstObjectNode objectNode)
+            return node;
+
+        var clrType = objectNode.Type.GetClrType();
+        if (!clrType.IsAvaloniaStyledElement())
+            return node;
+
+        foreach (var child in objectNode.Children)
+        {
+            if (child is XamlAstXmlDirective directive &&
+                directive.Name == "Class" &&
+                directive.Namespace == XamlNamespaces.Xaml2006 &&
+                directive.Values[0] is XamlAstTextNode text)
+            {
+                if (_checkTypeValidity)
+                {
+                    var existingType = _typeSystem.FindType(text.Text);
+                    if (existingType == null)
+                    {
+                        _onTypeInvalid?.Invoke(text.Text);
+                        return node;
+                    }
+                }
+
+                var split = text.Text.Split('.');
+                var nameSpace = string.Join(".", split.Take(split.Length - 1));
+                var className = split.Last();
+
+                _resolvedClass = new ResolvedView(className, clrType, nameSpace, _xaml);
+                return node;
+            }
+        }
+
+        return node;
+    }
+
+    void IXamlAstVisitor.Push(IXamlAstNode node) { }
+
+    void IXamlAstVisitor.Pop() { }
+}

+ 17 - 0
src/tools/Avalonia.Generators/Compiler/DataTemplateTransformer.cs

@@ -0,0 +1,17 @@
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Generators.Compiler;
+
+internal class DataTemplateTransformer : IXamlAstTransformer
+{
+    public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+    {
+        if (node is XamlAstObjectNode objectNode &&
+            objectNode.Type is XamlAstXmlTypeReference typeReference &&
+            (typeReference.Name == "DataTemplate" ||
+             typeReference.Name == "ControlTemplate"))
+            objectNode.Children.Clear();
+        return node;
+    }
+}

+ 50 - 0
src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using XamlX.Compiler;
+using XamlX.Emit;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Compiler;
+
+internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
+{
+    public const string AvaloniaXmlnsDefinitionAttribute = "Avalonia.Metadata.XmlnsDefinitionAttribute";
+
+    public static MiniCompiler CreateDefault(RoslynTypeSystem typeSystem, params string[] additionalTypes)
+    {
+        var mappings = new XamlLanguageTypeMappings(typeSystem);
+        foreach (var additionalType in additionalTypes)
+            mappings.XmlnsAttributes.Add(typeSystem.GetType(additionalType));
+
+        var configuration = new TransformerConfiguration(
+            typeSystem,
+            typeSystem.Assemblies.First(),
+            mappings);
+        return new MiniCompiler(configuration);
+    }
+        
+    private MiniCompiler(TransformerConfiguration configuration)
+        : base(configuration, new XamlLanguageEmitMappings<object, IXamlEmitResult>(), false)
+    {
+        Transformers.Add(new NameDirectiveTransformer());
+        Transformers.Add(new DataTemplateTransformer());
+        Transformers.Add(new KnownDirectivesTransformer());
+        Transformers.Add(new XamlIntrinsicsTransformer());
+        Transformers.Add(new XArgumentsTransformer());
+        Transformers.Add(new TypeReferenceResolver());
+    }
+
+    protected override XamlEmitContext<object, IXamlEmitResult> InitCodeGen(
+        IFileSource file,
+        Func<string, IXamlType,
+        IXamlTypeBuilder<object>> createSubType,
+        Func<string, IXamlType, IEnumerable<IXamlType>,
+        IXamlTypeBuilder<object>> createDelegateType,
+        object codeGen,
+        XamlRuntimeContext<object, IXamlEmitResult> context,
+        bool needContextLocal) =>
+        throw new NotSupportedException();
+}

+ 28 - 0
src/tools/Avalonia.Generators/Compiler/NameDirectiveTransformer.cs

@@ -0,0 +1,28 @@
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Generators.Compiler;
+
+internal class NameDirectiveTransformer : IXamlAstTransformer
+{
+    public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+    {
+        if (node is not XamlAstObjectNode objectNode)
+            return node;
+
+        for (var index = 0; index < objectNode.Children.Count; index++)
+        {
+            var child = objectNode.Children[index];
+            if (child is XamlAstXmlDirective directive &&
+                directive.Namespace == XamlNamespaces.Xaml2006 &&
+                directive.Name == "Name")
+                objectNode.Children[index] = new XamlAstXamlPropertyValueNode(
+                    directive,
+                    new XamlAstNamePropertyReference(directive, objectNode.Type, "Name", objectNode.Type),
+                    directive.Values);
+        }
+
+        return node;
+    }
+}

+ 276 - 0
src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs

@@ -0,0 +1,276 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.Compiler;
+
+internal class RoslynTypeSystem : IXamlTypeSystem
+{
+    private readonly List<IXamlAssembly> _assemblies = new();
+
+    public RoslynTypeSystem(CSharpCompilation compilation)
+    {
+        _assemblies.Add(new RoslynAssembly(compilation.Assembly));
+
+        var assemblySymbols = compilation
+            .References
+            .Select(compilation.GetAssemblyOrModuleSymbol)
+            .OfType<IAssemblySymbol>()
+            .Select(assembly => new RoslynAssembly(assembly))
+            .ToList();
+
+        _assemblies.AddRange(assemblySymbols);
+    }
+
+    public IEnumerable<IXamlAssembly> Assemblies => _assemblies;
+
+    public IXamlAssembly FindAssembly(string name) =>
+        Assemblies
+            .FirstOrDefault(a => string.Equals(a.Name, name, StringComparison.OrdinalIgnoreCase));
+
+    public IXamlType FindType(string name) =>
+        _assemblies
+            .Select(assembly => assembly.FindType(name))
+            .FirstOrDefault(type => type != null);
+
+    public IXamlType FindType(string name, string assembly) =>
+        _assemblies
+            .Select(assemblyInstance => assemblyInstance.FindType(name))
+            .FirstOrDefault(type => type != null);
+}
+    
+internal class RoslynAssembly : IXamlAssembly
+{
+    private readonly IAssemblySymbol _symbol;
+
+    public RoslynAssembly(IAssemblySymbol symbol) => _symbol = symbol;
+
+    public bool Equals(IXamlAssembly other) =>
+        other is RoslynAssembly roslynAssembly &&
+        SymbolEqualityComparer.Default.Equals(_symbol, roslynAssembly._symbol);
+
+    public string Name => _symbol.Name;
+
+    public IReadOnlyList<IXamlCustomAttribute> CustomAttributes =>
+        _symbol.GetAttributes()
+            .Select(data => new RoslynAttribute(data, this))
+            .ToList();
+
+    public IXamlType FindType(string fullName)
+    {
+        var type = _symbol.GetTypeByMetadataName(fullName);
+        return type is null ? null : new RoslynType(type, this);
+    }
+}
+
+internal class RoslynAttribute : IXamlCustomAttribute
+{
+    private readonly AttributeData _data;
+    private readonly RoslynAssembly _assembly;
+
+    public RoslynAttribute(AttributeData data, RoslynAssembly assembly)
+    {
+        _data = data;
+        _assembly = assembly;
+    }
+
+    public bool Equals(IXamlCustomAttribute other) =>
+        other is RoslynAttribute attribute &&
+        _data == attribute._data;
+
+    public IXamlType Type => new RoslynType(_data.AttributeClass, _assembly);
+
+    public List<object> Parameters =>
+        _data.ConstructorArguments
+            .Select(argument => argument.Value)
+            .ToList();
+
+    public Dictionary<string, object> Properties =>
+        _data.NamedArguments.ToDictionary(
+            pair => pair.Key,
+            pair => pair.Value.Value);
+}
+    
+internal class RoslynType : IXamlType
+{
+    private static readonly SymbolDisplayFormat SymbolDisplayFormat = new SymbolDisplayFormat(
+        typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
+        genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters |
+                         SymbolDisplayGenericsOptions.IncludeTypeConstraints |
+                         SymbolDisplayGenericsOptions.IncludeVariance);
+
+    private readonly RoslynAssembly _assembly;
+    private readonly INamedTypeSymbol _symbol;
+
+    public RoslynType(INamedTypeSymbol symbol, RoslynAssembly assembly)
+    {
+        _symbol = symbol;
+        _assembly = assembly;
+    }
+
+    public bool Equals(IXamlType other) =>
+        other is RoslynType roslynType && 
+        SymbolEqualityComparer.Default.Equals(_symbol, roslynType._symbol);
+
+    public object Id => _symbol;
+        
+    public string Name => _symbol.Name;
+
+    public string Namespace => _symbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat);
+
+    public string FullName => $"{Namespace}.{Name}";
+
+    public IXamlAssembly Assembly => _assembly;
+
+    public IReadOnlyList<IXamlProperty> Properties =>
+        _symbol.GetMembers()
+            .Where(member => member.Kind == SymbolKind.Property)
+            .OfType<IPropertySymbol>()
+            .Select(property => new RoslynProperty(property, _assembly))
+            .ToList();
+
+    public IReadOnlyList<IXamlEventInfo> Events { get; } = new List<IXamlEventInfo>();
+        
+    public IReadOnlyList<IXamlField> Fields { get; } = new List<IXamlField>();
+        
+    public IReadOnlyList<IXamlMethod> Methods { get; } = new List<IXamlMethod>();
+
+    public IReadOnlyList<IXamlConstructor> Constructors =>
+        _symbol.Constructors
+            .Select(method => new RoslynConstructor(method, _assembly))
+            .ToList();
+        
+    public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
+
+    public IReadOnlyList<IXamlType> GenericArguments { get; private set; } = new List<IXamlType>();
+
+    public bool IsAssignableFrom(IXamlType type) => type == this;
+
+    public IXamlType MakeGenericType(IReadOnlyList<IXamlType> typeArguments)
+    {
+        GenericArguments = typeArguments;
+        return this;
+    }
+
+    public IXamlType GenericTypeDefinition => this;
+        
+    public bool IsArray => false;
+
+    public IXamlType ArrayElementType { get; } = null;
+        
+    public IXamlType MakeArrayType(int dimensions) => null;
+
+    public IXamlType BaseType => _symbol.BaseType == null ? null : new RoslynType(_symbol.BaseType, _assembly);
+
+    public bool IsValueType { get; } = false;
+
+    public bool IsEnum { get; } = false;
+
+    public IReadOnlyList<IXamlType> Interfaces =>
+        _symbol.AllInterfaces
+            .Select(abstraction => new RoslynType(abstraction, _assembly))
+            .ToList();
+
+    public bool IsInterface => _symbol.IsAbstract;
+        
+    public IXamlType GetEnumUnderlyingType() => null;
+
+    public IReadOnlyList<IXamlType> GenericParameters { get; } = new List<IXamlType>();
+}
+
+internal class RoslynConstructor : IXamlConstructor
+{
+    private readonly IMethodSymbol _symbol;
+    private readonly RoslynAssembly _assembly;
+
+    public RoslynConstructor(IMethodSymbol symbol, RoslynAssembly assembly)
+    {
+        _symbol = symbol;
+        _assembly = assembly;
+    }
+
+    public bool Equals(IXamlConstructor other) =>
+        other is RoslynConstructor roslynConstructor &&
+        SymbolEqualityComparer.Default.Equals(_symbol, roslynConstructor._symbol);
+
+    public bool IsPublic => true;
+        
+    public bool IsStatic => false;
+
+    public IReadOnlyList<IXamlType> Parameters =>
+        _symbol.Parameters
+            .Select(parameter => parameter.Type)
+            .OfType<INamedTypeSymbol>()
+            .Select(type => new RoslynType(type, _assembly))
+            .ToList();
+}
+
+internal class RoslynProperty : IXamlProperty
+{
+    private readonly IPropertySymbol _symbol;
+    private readonly RoslynAssembly _assembly;
+
+    public RoslynProperty(IPropertySymbol symbol, RoslynAssembly assembly)
+    {
+        _symbol = symbol;
+        _assembly = assembly;
+    }
+
+    public bool Equals(IXamlProperty other) =>
+        other is RoslynProperty roslynProperty &&
+        SymbolEqualityComparer.Default.Equals(_symbol, roslynProperty._symbol);
+
+    public string Name => _symbol.Name;
+
+    public IXamlType PropertyType =>
+        _symbol.Type is INamedTypeSymbol namedTypeSymbol
+            ? new RoslynType(namedTypeSymbol, _assembly)
+            : null;
+
+    public IXamlMethod Getter => _symbol.GetMethod == null ? null : new RoslynMethod(_symbol.GetMethod, _assembly);
+        
+    public IXamlMethod Setter => _symbol.SetMethod == null ? null : new RoslynMethod(_symbol.SetMethod, _assembly); 
+        
+    public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
+
+    public IReadOnlyList<IXamlType> IndexerParameters { get; } = new List<IXamlType>();
+}
+
+internal class RoslynMethod : IXamlMethod
+{
+    private readonly IMethodSymbol _symbol;
+    private readonly RoslynAssembly _assembly;
+
+    public RoslynMethod(IMethodSymbol symbol, RoslynAssembly assembly)
+    {
+        _symbol = symbol;
+        _assembly = assembly;
+    }
+
+    public bool Equals(IXamlMethod other) =>
+        other is RoslynMethod roslynMethod &&
+        SymbolEqualityComparer.Default.Equals(roslynMethod._symbol, _symbol);
+
+    public string Name => _symbol.Name;
+
+    public bool IsPublic => true;
+
+    public bool IsStatic => false;
+
+    public IXamlType ReturnType => new RoslynType((INamedTypeSymbol) _symbol.ReturnType, _assembly);
+
+    public IReadOnlyList<IXamlType> Parameters =>
+        _symbol.Parameters.Select(parameter => parameter.Type)
+            .OfType<INamedTypeSymbol>()
+            .Select(type => new RoslynType(type, _assembly))
+            .ToList();
+        
+    public IXamlType DeclaringType => new RoslynType((INamedTypeSymbol)_symbol.ReceiverType, _assembly);
+        
+    public IXamlMethod MakeGenericMethod(IReadOnlyList<IXamlType> typeArguments) => null;
+
+    public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
+}

+ 36 - 0
src/tools/Avalonia.Generators/GeneratorContextExtensions.cs

@@ -0,0 +1,36 @@
+using System;
+using Microsoft.CodeAnalysis;
+
+namespace Avalonia.Generators;
+
+internal static class GeneratorContextExtensions
+{
+    private const string UnhandledErrorDescriptorId = "AXN0002";
+    private const string InvalidTypeDescriptorId = "AXN0001";
+
+    public static string GetMsBuildProperty(
+        this GeneratorExecutionContext context,
+        string name,
+        string defaultValue = "")
+    {
+        context.AnalyzerConfigOptions.GlobalOptions.TryGetValue($"build_property.{name}", out var value);
+        return value ?? defaultValue;
+    }
+
+    public static void ReportNameGeneratorUnhandledError(this GeneratorExecutionContext context, Exception error) =>
+        context.Report(UnhandledErrorDescriptorId,
+            "Unhandled exception occured while generating typed Name references. " +
+            "Please file an issue: https://github.com/avaloniaui/Avalonia.Generators",
+            error.ToString());
+
+    public static void ReportNameGeneratorInvalidType(this GeneratorExecutionContext context, string typeName) =>
+        context.Report(InvalidTypeDescriptorId,
+            $"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " +
+            $"The type '{typeName}' does not exist in the assembly.");
+
+    private static void Report(this GeneratorExecutionContext context, string id, string title, string message = null) =>
+        context.ReportDiagnostic(
+            Diagnostic.Create(
+                new DiagnosticDescriptor(id, title, message ?? title, "Usage", DiagnosticSeverity.Error, true),
+                Location.None));
+}

+ 71 - 0
src/tools/Avalonia.Generators/GeneratorOptions.cs

@@ -0,0 +1,71 @@
+using System;
+using Avalonia.Generators.Common.Domain;
+using Avalonia.Generators.NameGenerator;
+using Microsoft.CodeAnalysis;
+
+namespace Avalonia.Generators;
+
+// When update these enum values, don't forget to update Avalonia.Generators.props.
+internal enum BuildProperties
+{
+    AvaloniaNameGeneratorIsEnabled = 0,
+    AvaloniaNameGeneratorBehavior = 1,
+    AvaloniaNameGeneratorDefaultFieldModifier = 2,
+    AvaloniaNameGeneratorFilterByPath = 3,
+    AvaloniaNameGeneratorFilterByNamespace = 4,
+    AvaloniaNameGeneratorViewFileNamingStrategy = 5,
+    
+    // TODO add other generators properties here.
+}
+
+internal class GeneratorOptions
+{
+    private readonly GeneratorExecutionContext _context;
+
+    public GeneratorOptions(GeneratorExecutionContext context) => _context = context;
+
+    public bool AvaloniaNameGeneratorIsEnabled => GetBoolProperty(
+        BuildProperties.AvaloniaNameGeneratorIsEnabled,
+        true);
+    
+    public Behavior AvaloniaNameGeneratorBehavior => GetEnumProperty(
+        BuildProperties.AvaloniaNameGeneratorBehavior,
+        Behavior.InitializeComponent);
+
+    public NamedFieldModifier AvaloniaNameGeneratorClassFieldModifier => GetEnumProperty(
+        BuildProperties.AvaloniaNameGeneratorDefaultFieldModifier,
+        NamedFieldModifier.Internal);
+
+    public ViewFileNamingStrategy AvaloniaNameGeneratorViewFileNamingStrategy => GetEnumProperty(
+        BuildProperties.AvaloniaNameGeneratorViewFileNamingStrategy,
+        ViewFileNamingStrategy.NamespaceAndClassName);
+
+    public string[] AvaloniaNameGeneratorFilterByPath => GetStringArrayProperty(
+        BuildProperties.AvaloniaNameGeneratorFilterByPath,
+        "*");
+
+    public string[] AvaloniaNameGeneratorFilterByNamespace => GetStringArrayProperty(
+        BuildProperties.AvaloniaNameGeneratorFilterByNamespace,
+        "*");
+
+    private string[] GetStringArrayProperty(BuildProperties name, string defaultValue)
+    {
+        var key = name.ToString();
+        var value = _context.GetMsBuildProperty(key, defaultValue);
+        return value.Contains(";") ? value.Split(';') : new[] {value};
+    }
+
+    private TEnum GetEnumProperty<TEnum>(BuildProperties name, TEnum defaultValue) where TEnum : struct
+    {
+        var key = name.ToString();
+        var value = _context.GetMsBuildProperty(key, defaultValue.ToString());
+        return Enum.TryParse(value, true, out TEnum behavior) ? behavior : defaultValue;
+    }
+    
+    private bool GetBoolProperty(BuildProperties name, bool defaultValue)
+    {
+        var key = name.ToString();
+        var value = _context.GetMsBuildProperty(key, defaultValue.ToString());
+        return bool.TryParse(value, out var result) ? result : defaultValue;
+    }
+}

+ 63 - 0
src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameGenerator.cs

@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Generators.Common.Domain;
+using Microsoft.CodeAnalysis;
+
+namespace Avalonia.Generators.NameGenerator;
+
+internal class AvaloniaNameGenerator : INameGenerator
+{
+    private readonly ViewFileNamingStrategy _naming;
+    private readonly IGlobPattern _pathPattern;
+    private readonly IGlobPattern _namespacePattern;
+    private readonly IViewResolver _classes;
+    private readonly INameResolver _names;
+    private readonly ICodeGenerator _code;
+
+    public AvaloniaNameGenerator(
+        ViewFileNamingStrategy naming,
+        IGlobPattern pathPattern,
+        IGlobPattern namespacePattern,
+        IViewResolver classes,
+        INameResolver names,
+        ICodeGenerator code)
+    {
+        _naming = naming;
+        _pathPattern = pathPattern;
+        _namespacePattern = namespacePattern;
+        _classes = classes;
+        _names = names;
+        _code = code;
+    }
+
+    public IReadOnlyList<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles)
+    {
+        var resolveViews =
+            from file in additionalFiles
+            where (file.Path.EndsWith(".xaml") ||
+                   file.Path.EndsWith(".paml") ||
+                   file.Path.EndsWith(".axaml")) &&
+                  _pathPattern.Matches(file.Path)
+            let xaml = file.GetText()!.ToString()
+            let view = _classes.ResolveView(xaml)
+            where view != null && _namespacePattern.Matches(view.Namespace)
+            select view;
+
+        var query =
+            from view in resolveViews
+            let names = _names.ResolveNames(view.Xaml)
+            let code = _code.GenerateCode(view.ClassName, view.Namespace, view.XamlType, names)
+            let fileName = ResolveViewFileName(view, _naming)
+            select new GeneratedPartialClass(fileName, code);
+
+        return query.ToList();
+    }
+
+    private static string ResolveViewFileName(ResolvedView view, ViewFileNamingStrategy strategy) => strategy switch
+    {
+        ViewFileNamingStrategy.ClassName => $"{view.ClassName}.g.cs",
+        ViewFileNamingStrategy.NamespaceAndClassName => $"{view.Namespace}.{view.ClassName}.g.cs",
+        _ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, "Unknown naming strategy!")
+    };
+}

+ 60 - 0
src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameSourceGenerator.cs

@@ -0,0 +1,60 @@
+using System;
+using Avalonia.Generators.Common;
+using Avalonia.Generators.Common.Domain;
+using Avalonia.Generators.Compiler;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Avalonia.Generators.NameGenerator;
+
+[Generator]
+public class AvaloniaNameSourceGenerator : ISourceGenerator
+{
+    public void Initialize(GeneratorInitializationContext context) { }
+
+    public void Execute(GeneratorExecutionContext context)
+    {
+        try
+        {
+            var generator = CreateNameGenerator(context);
+            if (generator is null)
+            {
+                return;
+            }
+
+            var partials = generator.GenerateNameReferences(context.AdditionalFiles);
+            foreach (var (fileName, content) in partials) context.AddSource(fileName, content);
+        }
+        catch (Exception exception)
+        {
+            context.ReportNameGeneratorUnhandledError(exception);
+        }
+    }
+
+    private static INameGenerator CreateNameGenerator(GeneratorExecutionContext context)
+    {
+        var options = new GeneratorOptions(context);
+        if (!options.AvaloniaNameGeneratorIsEnabled)
+        {
+            return null;
+        }
+
+        var types = new RoslynTypeSystem((CSharpCompilation)context.Compilation);
+        ICodeGenerator generator = options.AvaloniaNameGeneratorBehavior switch {
+            Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(),
+            Behavior.InitializeComponent => new InitializeComponentCodeGenerator(types),
+            _ => throw new ArgumentOutOfRangeException()
+        };
+
+        var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
+        return new AvaloniaNameGenerator(
+            options.AvaloniaNameGeneratorViewFileNamingStrategy,
+            new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath),
+            new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace),
+            new XamlXViewResolver(types, compiler, true,
+                type => context.ReportNameGeneratorInvalidType(type),
+                error => context.ReportNameGeneratorUnhandledError(error)),
+            new XamlXNameResolver(options.AvaloniaNameGeneratorClassFieldModifier),
+            generator);
+    }
+}

+ 11 - 0
src/tools/Avalonia.Generators/NameGenerator/INameGenerator.cs

@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+
+namespace Avalonia.Generators.NameGenerator;
+
+internal interface INameGenerator
+{
+    IReadOnlyList<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles);
+}
+
+internal record GeneratedPartialClass(string FileName, string Content);

+ 83 - 0
src/tools/Avalonia.Generators/NameGenerator/InitializeComponentCodeGenerator.cs

@@ -0,0 +1,83 @@
+using System.Collections.Generic;
+using Avalonia.Generators.Common.Domain;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.NameGenerator;
+
+internal class InitializeComponentCodeGenerator: ICodeGenerator
+{
+    private readonly bool _diagnosticsAreConnected;
+    private const string AttachDevToolsCodeBlock = @"
+#if DEBUG
+            if (attachDevTools)
+            {
+                this.AttachDevTools();
+            }
+#endif
+";
+    private const string AttachDevToolsParameterDocumentation
+        = @"        /// <param name=""attachDevTools"">Should the dev tools be attached.</param>
+";
+
+    public InitializeComponentCodeGenerator(IXamlTypeSystem types)
+    {
+        _diagnosticsAreConnected = types.FindAssembly("Avalonia.Diagnostics") != null;
+    }
+
+    public string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names)
+    {
+        var properties = new List<string>();
+        var initializations = new List<string>();
+        foreach (var resolvedName in names)
+        {
+            var (typeName, name, fieldModifier) = resolvedName;
+            properties.Add($"        {fieldModifier} {typeName} {name};");
+            initializations.Add($"            {name} = this.FindNameScope()?.Find<{typeName}>(\"{name}\");");
+        }
+
+        var attachDevTools = _diagnosticsAreConnected && IsWindow(xamlType);
+
+        return $@"// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace {nameSpace}
+{{
+    partial class {className}
+    {{
+{string.Join("\n", properties)}
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name=""loadXaml"">Should the XAML be loaded into the component.</param>
+{(attachDevTools ? AttachDevToolsParameterDocumentation : string.Empty)}
+        public void InitializeComponent(bool loadXaml = true{(attachDevTools ? ", bool attachDevTools = true" : string.Empty)})
+        {{
+            if (loadXaml)
+            {{
+                AvaloniaXamlLoader.Load(this);
+            }}
+{(attachDevTools ? AttachDevToolsCodeBlock : string.Empty)}
+{string.Join("\n", initializations)}
+        }}
+    }}
+}}
+";
+    }
+        
+    private static bool IsWindow(IXamlType xamlType)
+    {
+        var type = xamlType;
+        bool isWindow;
+        do
+        {
+            isWindow = type.FullName == "Avalonia.Controls.Window";
+            type = type.BaseType;
+        } while (!isWindow && type != null);
+
+        return isWindow;
+    }
+}

+ 31 - 0
src/tools/Avalonia.Generators/NameGenerator/OnlyPropertiesCodeGenerator.cs

@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Generators.Common.Domain;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Generators.NameGenerator;
+
+internal class OnlyPropertiesCodeGenerator : ICodeGenerator
+{
+    public string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names)
+    {
+        var namedControls = names
+            .Select(info => "        " +
+                            $"{info.FieldModifier} {info.TypeName} {info.Name} => " +
+                            $"this.FindNameScope()?.Find<{info.TypeName}>(\"{info.Name}\");")
+            .ToList();
+        var lines = string.Join("\n", namedControls);
+        return $@"// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace {nameSpace}
+{{
+    partial class {className}
+    {{
+{lines}
+    }}
+}}
+";
+    }
+}

+ 21 - 0
src/tools/Avalonia.Generators/NameGenerator/Options.cs

@@ -0,0 +1,21 @@
+namespace Avalonia.Generators.NameGenerator;
+
+internal enum Options
+{
+    Public = 0,
+    Private = 1,
+    Internal = 2,
+    Protected = 3,
+}
+
+internal enum Behavior
+{
+    OnlyProperties = 0,
+    InitializeComponent = 1,
+}
+
+internal enum ViewFileNamingStrategy
+{
+    ClassName = 0,
+    NamespaceAndClassName = 1,
+}

+ 8 - 0
src/tools/Avalonia.Generators/Properties/launchSettings.json

@@ -0,0 +1,8 @@
+{
+  "profiles": {
+    "Profile 1": {
+      "commandName": "DebugRoslynComponent",
+      "targetProject": "..\\..\\..\\samples\\Generators.Sandbox\\Generators.Sandbox.csproj"
+    }
+  }
+}

+ 209 - 0
src/tools/Avalonia.Generators/README.md

@@ -0,0 +1,209 @@
+[![NuGet Stats](https://img.shields.io/nuget/v/XamlNameReferenceGenerator.svg)](https://www.nuget.org/packages/XamlNameReferenceGenerator) [![downloads](https://img.shields.io/nuget/dt/XamlNameReferenceGenerator)](https://www.nuget.org/packages/XamlNameReferenceGenerator) ![Build](https://github.com/avaloniaui/Avalonia.NameGenerator/workflows/Build/badge.svg) ![License](https://img.shields.io/github/license/avaloniaui/Avalonia.NameGenerator.svg) ![Size](https://img.shields.io/github/repo-size/avaloniaui/Avalonia.NameGenerator.svg)
+
+### C# `SourceGenerator` for Typed Avalonia `x:Name` References 
+
+This is a [C# `SourceGenerator`](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/) built for generating strongly-typed references to controls with `x:Name` (or just `Name`) attributes declared in XAML (or, in `.axaml`). The source generator will look for the `xaml` (or `axaml`) file with the same name as your partial C# class that is a subclass of `Avalonia.INamed` and parses the XAML markup, finds all XAML tags with `x:Name` attributes and generates the C# code.
+
+### Getting Started
+
+In order to get started, just install the NuGet package:
+
+```
+dotnet add package XamlNameReferenceGenerator
+```
+
+Or, if you are using [submodules](https://git-scm.com/docs/git-submodule), you can reference the generator as such:
+
+```xml
+<ItemGroup>
+    <!-- Remember to ensure XAML files are included via <AdditionalFiles>,
+         otherwise C# source generator won't see XAML files. -->
+    <AdditionalFiles Include="**\*.xaml"/>
+    <ProjectReference Include="..\Avalonia.NameGenerator\Avalonia.NameGenerator.csproj"
+                      OutputItemType="Analyzer"
+                      ReferenceOutputAssembly="false" />
+</ItemGroup>
+```
+
+### Usage
+
+After installing the NuGet package, declare your view class as `partial`. Typed C# references to Avalonia controls declared in XAML files will be generated for classes referenced by the `x:Class` directive in XAML files. For example, for the following XAML markup:
+
+```xml
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.SignUpView">
+    <TextBox x:Name="UserNameTextBox" x:FieldModifier="public" />
+</Window>
+```
+
+A new C# partial class named `SignUpView` with a single `public` property named `UserNameTextBox` of type `TextBox` will be generated in the `Sample.App` namespace. We won't see the generated file, but we'll be able to access the generated property as shown below:
+
+```cs
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    public partial class SignUpView : Window
+    {
+        public SignUpView()
+        {
+            // This method is generated. Call it before accessing any
+            // of the generated properties. The 'UserNameTextBox'
+            // property is also generated.
+            InitializeComponent();
+            UserNameTextBox.Text = "Joseph";
+        }
+    }
+}
+```
+
+<img src="https://hsto.org/getpro/habr/post_images/d9f/4aa/a1e/d9f4aaa1eb450f5dd2fca66631bc16a0.gif" />
+
+### Why do I need this?
+
+The typed `x:Name` references might be useful if you decide to use e.g. [ReactiveUI code-behind bindings](https://www.reactiveui.net/docs/handbook/data-binding/):
+
+```cs
+// UserNameValidation and PasswordValidation are auto generated.
+public partial class SignUpView : ReactiveWindow<SignUpViewModel>
+{
+    public SignUpView()
+    {
+        InitializeComponent();
+        this.WhenActivated(disposables =>
+        {
+            this.BindValidation(ViewModel, x => x.UserName, x => x.UserNameValidation.Text)
+                .DisposeWith(disposables);
+            this.BindValidation(ViewModel, x => x.Password, x => x.PasswordValidation.Text)
+                .DisposeWith(disposables);
+        });
+    }
+}
+```
+
+### Advanced Usage
+
+> Never keep a method named `InitializeComponent` in your code-behind view class if you are using the generator with `AvaloniaNameGeneratorBehavior` set to `InitializeComponent` (this is the default value). The private `InitializeComponent` method declared in your code-behind class hides the `InitializeComponent` method generated by `Avalonia.NameGenerator`, see [Issue 69](https://github.com/AvaloniaUI/Avalonia.NameGenerator/issues/69). If you wish to use your own `InitializeComponent` method (not the generated one), set `AvaloniaNameGeneratorBehavior` to `OnlyProperties`.
+
+The `x:Name` generator can be configured via MsBuild properties that you can put into your C# project file (`.csproj`). Using such options, you can configure the generator behavior, the default field modifier, namespace and path filters. The generator supports the following options:
+
+- `AvaloniaNameGeneratorBehavior`  
+    Possible values: `OnlyProperties`, `InitializeComponent`  
+    Default value: `InitializeComponent`  
+    Determines if the generator should generate get-only properties, or the `InitializeComponent` method.
+
+- `AvaloniaNameGeneratorDefaultFieldModifier`  
+    Possible values: `internal`, `public`, `private`, `protected`  
+    Default value: `internal`  
+    The default field modifier that should be used when there is no `x:FieldModifier` directive specified.
+
+- `AvaloniaNameGeneratorFilterByPath`  
+    Posssible format: `glob_pattern`, `glob_pattern;glob_pattern`  
+    Default value: `*`  
+    The generator will process only XAML files with paths matching the specified glob pattern(s).  
+    Example: `*/Views/*View.xaml`, `*View.axaml;*Control.axaml`
+
+- `AvaloniaNameGeneratorFilterByNamespace`  
+    Posssible format: `glob_pattern`, `glob_pattern;glob_pattern`  
+    Default value: `*`  
+    The generator will process only XAML files with base classes' namespaces matching the specified glob pattern(s).  
+    Example: `MyApp.Presentation.*`, `MyApp.Presentation.Views;MyApp.Presentation.Controls`
+
+- `AvaloniaNameGeneratorViewFileNamingStrategy`  
+    Possible values: `ClassName`, `NamespaceAndClassName`  
+    Default value: `NamespaceAndClassName`  
+    Determines how the automatically generated view files should be [named](https://github.com/AvaloniaUI/Avalonia.NameGenerator/issues/92).
+
+The default values are given by:
+
+```xml
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <AvaloniaNameGeneratorBehavior>InitializeComponent</AvaloniaNameGeneratorBehavior>
+        <AvaloniaNameGeneratorDefaultFieldModifier>internal</AvaloniaNameGeneratorDefaultFieldModifier>
+        <AvaloniaNameGeneratorFilterByPath>*</AvaloniaNameGeneratorFilterByPath>
+        <AvaloniaNameGeneratorFilterByNamespace>*</AvaloniaNameGeneratorFilterByNamespace>
+        <AvaloniaNameGeneratorViewFileNamingStrategy>NamespaceAndClassName</AvaloniaNameGeneratorViewFileNamingStrategy>
+    </PropertyGroup>
+    <!-- ... -->
+</Project>
+```
+
+![](https://user-images.githubusercontent.com/6759207/107812261-7ddfea00-6d80-11eb-9c7e-67bf95d0f0d4.gif)
+
+### What do the generated sources look like?
+
+For [`SignUpView`](https://github.com/avaloniaui/Avalonia.NameGenerator/blob/main/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml), we get the following generated output when the source generator is in the `InitializeComponent` mode:
+
+```cs
+// <auto-generated />
+
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.NameGenerator.Sandbox.Controls.CustomTextBox UserNameTextBox;
+        public global::Avalonia.Controls.TextBlock UserNameValidation;
+        private global::Avalonia.Controls.TextBox PasswordTextBox;
+        internal global::Avalonia.Controls.TextBlock PasswordValidation;
+        internal global::Avalonia.Controls.ListBox AwesomeListView;
+        internal global::Avalonia.Controls.TextBox ConfirmPasswordTextBox;
+        internal global::Avalonia.Controls.TextBlock ConfirmPasswordValidation;
+        internal global::Avalonia.Controls.Button SignUpButton;
+        internal global::Avalonia.Controls.TextBlock CompoundValidation;
+
+        public void InitializeComponent(bool loadXaml = true, bool attachDevTools = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+// This will be added only if you install Avalonia.Diagnostics.
+#if DEBUG
+            if (attachDevTools)
+            {
+                this.AttachDevTools();
+            } 
+#endif
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.NameGenerator.Sandbox.Controls.CustomTextBox>("UserNameTextBox");
+            UserNameValidation = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("UserNameValidation");
+            PasswordTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+            PasswordValidation = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("PasswordValidation");
+            AwesomeListView = this.FindNameScope()?.Find<global::Avalonia.Controls.ListBox>("AwesomeListView");
+            ConfirmPasswordTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("ConfirmPasswordTextBox");
+            ConfirmPasswordValidation = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("ConfirmPasswordValidation");
+            SignUpButton = this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+            CompoundValidation = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("CompoundValidation");
+        }
+    }
+}
+```
+
+If you enable the `OnlyProperties` source generator mode, you get:
+
+```cs
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Avalonia.NameGenerator.Sandbox.Views
+{
+    partial class SignUpView
+    {
+        internal global::Avalonia.NameGenerator.Sandbox.Controls.CustomTextBox UserNameTextBox => this.FindNameScope()?.Find<global::Avalonia.NameGenerator.Sandbox.Controls.CustomTextBox>("UserNameTextBox");
+        public global::Avalonia.Controls.TextBlock UserNameValidation => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("UserNameValidation");
+        private global::Avalonia.Controls.TextBox PasswordTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+        internal global::Avalonia.Controls.TextBlock PasswordValidation => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("PasswordValidation");
+        internal global::Avalonia.Controls.TextBox ConfirmPasswordTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("ConfirmPasswordTextBox");
+        internal global::Avalonia.Controls.TextBlock ConfirmPasswordValidation => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("ConfirmPasswordValidation");
+        internal global::Avalonia.Controls.Button SignUpButton => this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+        internal global::Avalonia.Controls.TextBlock CompoundValidation => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("CompoundValidation");
+    }
+}
+```

+ 26 - 0
tests/Avalonia.Generators.Tests/Avalonia.Generators.Tests.csproj

@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net6.0</TargetFramework>
+    <RootNamespace>Avalonia.Generators.Tests</RootNamespace>
+    <IsTestProject>true</IsTestProject>
+  </PropertyGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
+    <ProjectReference Include="..\..\src\tools\Avalonia.Generators\Avalonia.Generators.csproj" />
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
+    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Views\*.xml" />
+    <EmbeddedResource Include="OnlyProperties\GeneratedCode\*.txt" />
+    <EmbeddedResource Include="InitializeComponent\GeneratedInitializeComponent\*.txt" />
+    <EmbeddedResource Include="InitializeComponent\GeneratedDevTools\*.txt" />
+  </ItemGroup>
+  <Import Project="..\..\build\UnitTests.NetCore.targets" />
+  <Import Project="..\..\build\XUnit.props" />
+  <Import Project="..\..\build\SharedVersion.props" />
+</Project>

+ 31 - 0
tests/Avalonia.Generators.Tests/GlobPatternTests.cs

@@ -0,0 +1,31 @@
+using Avalonia.Generators.Common;
+using Xunit;
+
+namespace Avalonia.Generators.Tests;
+
+public class GlobPatternTests
+{
+    [Theory]
+    [InlineData("*", "anything", true)]
+    [InlineData("", "anything", false)]
+    [InlineData("Views/*", "Views/SignUpView.xaml", true)]
+    [InlineData("Views/*", "Extensions/SignUpView.xaml", false)]
+    [InlineData("*SignUpView*", "Extensions/SignUpView.xaml", true)]
+    [InlineData("*SignUpView.paml", "Extensions/SignUpView.xaml", false)]
+    [InlineData("*.xaml", "Extensions/SignUpView.xaml", true)]
+    public void Should_Match_Glob_Expressions(string pattern, string value, bool matches)
+    {
+        Assert.Equal(matches, new GlobPattern(pattern).Matches(value));
+    }
+
+    [Theory]
+    [InlineData("Views/SignUpView.xaml", true, new[] { "*.xaml", "Extensions/*" })]
+    [InlineData("Extensions/SignUpView.paml", true, new[] { "*.xaml", "Extensions/*" })]
+    [InlineData("Extensions/SignUpView.paml", false, new[] { "*.xaml", "Views/*" })]
+    [InlineData("anything", true, new[] { "*", "*" })]
+    [InlineData("anything", false, new[] { "", "" })]
+    public void Should_Match_Glob_Pattern_Groups(string value, bool matches, string[] patterns)
+    {
+        Assert.Equal(matches, new GlobPatternGroup(patterns).Matches(value));
+    }
+}

+ 28 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/AttachedProps.txt

@@ -0,0 +1,28 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+        }
+    }
+}

+ 36 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/AttachedPropsWithDevTools.txt

@@ -0,0 +1,36 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+        /// <param name="attachDevTools">Should the dev tools be attached.</param>
+
+        public void InitializeComponent(bool loadXaml = true, bool attachDevTools = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+#if DEBUG
+            if (attachDevTools)
+            {
+                this.AttachDevTools();
+            }
+#endif
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+        }
+    }
+}

+ 28 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/ControlWithoutWindow.txt

@@ -0,0 +1,28 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+        }
+    }
+}

+ 32 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/CustomControls.txt

@@ -0,0 +1,32 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.ReactiveUI.RoutedViewHost ClrNamespaceRoutedViewHost;
+        internal global::Avalonia.ReactiveUI.RoutedViewHost UriRoutedViewHost;
+        internal global::Controls.CustomTextBox UserNameTextBox;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            ClrNamespaceRoutedViewHost = this.FindNameScope()?.Find<global::Avalonia.ReactiveUI.RoutedViewHost>("ClrNamespaceRoutedViewHost");
+            UriRoutedViewHost = this.FindNameScope()?.Find<global::Avalonia.ReactiveUI.RoutedViewHost>("UriRoutedViewHost");
+            UserNameTextBox = this.FindNameScope()?.Find<global::Controls.CustomTextBox>("UserNameTextBox");
+        }
+    }
+}

+ 30 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/DataTemplates.txt

@@ -0,0 +1,30 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox;
+        internal global::Avalonia.Controls.ListBox NamedListBox;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+            NamedListBox = this.FindNameScope()?.Find<global::Avalonia.Controls.ListBox>("NamedListBox");
+        }
+    }
+}

+ 38 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/FieldModifier.txt

@@ -0,0 +1,38 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        public global::Avalonia.Controls.TextBox FirstNameTextBox;
+        public global::Avalonia.Controls.TextBox LastNameTextBox;
+        protected global::Avalonia.Controls.TextBox PasswordTextBox;
+        private global::Avalonia.Controls.TextBox ConfirmPasswordTextBox;
+        internal global::Avalonia.Controls.Button SignUpButton;
+        internal global::Avalonia.Controls.Button RegisterButton;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            FirstNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("FirstNameTextBox");
+            LastNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("LastNameTextBox");
+            PasswordTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+            ConfirmPasswordTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("ConfirmPasswordTextBox");
+            SignUpButton = this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+            RegisterButton = this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("RegisterButton");
+        }
+    }
+}

+ 35 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/InitializeComponentCode.cs

@@ -0,0 +1,35 @@
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Avalonia.Generators.Tests.InitializeComponent.GeneratedInitializeComponent;
+
+public static class InitializeComponentCode
+{
+    public const string NamedControl = "NamedControl.txt";
+    public const string NamedControls = "NamedControls.txt";
+    public const string XNamedControl = "xNamedControl.txt";
+    public const string XNamedControls = "xNamedControls.txt";
+    public const string NoNamedControls = "NoNamedControls.txt";
+    public const string CustomControls = "CustomControls.txt";
+    public const string DataTemplates = "DataTemplates.txt";
+    public const string SignUpView = "SignUpView.txt";
+    public const string FieldModifier = "FieldModifier.txt";
+    public const string AttachedProps = "AttachedProps.txt";
+    public const string AttachedPropsWithDevTools = "AttachedPropsWithDevTools.txt";
+    public const string ControlWithoutWindow = "ControlWithoutWindow.txt";
+        
+    public static async Task<string> Load(string generatedCodeResourceName)
+    {
+        var assembly = typeof(XamlXNameResolverTests).Assembly;
+        var fullResourceName = assembly
+            .GetManifestResourceNames()
+            .First(name => name.Contains("InitializeComponent") &&
+                           name.Contains("GeneratedInitializeComponent") &&
+                           name.EndsWith(generatedCodeResourceName));
+
+        await using var stream = assembly.GetManifestResourceStream(fullResourceName);
+        using var reader = new StreamReader(stream!);
+        return await reader.ReadToEndAsync();
+    }
+}

+ 28 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/NamedControl.txt

@@ -0,0 +1,28 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+        }
+    }
+}

+ 32 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/NamedControls.txt

@@ -0,0 +1,32 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox;
+        internal global::Avalonia.Controls.TextBox PasswordTextBox;
+        internal global::Avalonia.Controls.Button SignUpButton;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+            PasswordTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+            SignUpButton = this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+        }
+    }
+}

+ 28 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/NoNamedControls.txt

@@ -0,0 +1,28 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+
+        }
+    }
+}

+ 46 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/SignUpView.txt

@@ -0,0 +1,46 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Controls.CustomTextBox UserNameTextBox;
+        internal global::Avalonia.Controls.TextBlock UserNameValidation;
+        internal global::Avalonia.Controls.TextBox PasswordTextBox;
+        internal global::Avalonia.Controls.TextBlock PasswordValidation;
+        internal global::Avalonia.Controls.ListBox AwesomeListView;
+        internal global::Avalonia.Controls.TextBox ConfirmPasswordTextBox;
+        internal global::Avalonia.Controls.TextBlock ConfirmPasswordValidation;
+        internal global::Avalonia.Controls.Documents.Run SignUpButtonDescription;
+        internal global::Avalonia.Controls.Button SignUpButton;
+        internal global::Avalonia.Controls.TextBlock CompoundValidation;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Controls.CustomTextBox>("UserNameTextBox");
+            UserNameValidation = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("UserNameValidation");
+            PasswordTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+            PasswordValidation = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("PasswordValidation");
+            AwesomeListView = this.FindNameScope()?.Find<global::Avalonia.Controls.ListBox>("AwesomeListView");
+            ConfirmPasswordTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("ConfirmPasswordTextBox");
+            ConfirmPasswordValidation = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("ConfirmPasswordValidation");
+            SignUpButtonDescription = this.FindNameScope()?.Find<global::Avalonia.Controls.Documents.Run>("SignUpButtonDescription");
+            SignUpButton = this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+            CompoundValidation = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("CompoundValidation");
+        }
+    }
+}

+ 28 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/xNamedControl.txt

@@ -0,0 +1,28 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+        }
+    }
+}

+ 32 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/GeneratedInitializeComponent/xNamedControls.txt

@@ -0,0 +1,32 @@
+// <auto-generated />
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox;
+        internal global::Avalonia.Controls.TextBox PasswordTextBox;
+        internal global::Avalonia.Controls.Button SignUpButton;
+
+        /// <summary>
+        /// Wires up the controls and optionally loads XAML markup and attaches dev tools (if Avalonia.Diagnostics package is referenced).
+        /// </summary>
+        /// <param name="loadXaml">Should the XAML be loaded into the component.</param>
+
+        public void InitializeComponent(bool loadXaml = true)
+        {
+            if (loadXaml)
+            {
+                AvaloniaXamlLoader.Load(this);
+            }
+
+            UserNameTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+            PasswordTextBox = this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+            SignUpButton = this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+        }
+    }
+}

+ 63 - 0
tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs

@@ -0,0 +1,63 @@
+using System.Threading.Tasks;
+using Avalonia.Generators.Common;
+using Avalonia.Generators.Compiler;
+using Avalonia.Generators.NameGenerator;
+using Avalonia.Generators.Tests.InitializeComponent.GeneratedInitializeComponent;
+using Avalonia.Generators.Tests.OnlyProperties.GeneratedCode;
+using Avalonia.Generators.Tests.Views;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace Avalonia.Generators.Tests.InitializeComponent;
+
+public class InitializeComponentTests
+{
+    [Theory]
+    [InlineData(InitializeComponentCode.NamedControl, View.NamedControl, false)]
+    [InlineData(InitializeComponentCode.NamedControls, View.NamedControls, false)]
+    [InlineData(InitializeComponentCode.XNamedControl, View.XNamedControl, false)]
+    [InlineData(InitializeComponentCode.XNamedControls, View.XNamedControls, false)]
+    [InlineData(InitializeComponentCode.NoNamedControls, View.NoNamedControls, false)]
+    [InlineData(InitializeComponentCode.CustomControls, View.CustomControls, false)]
+    [InlineData(InitializeComponentCode.DataTemplates, View.DataTemplates, false)]
+    [InlineData(InitializeComponentCode.SignUpView, View.SignUpView, false)]
+    [InlineData(InitializeComponentCode.FieldModifier, View.FieldModifier, false)]
+    [InlineData(InitializeComponentCode.AttachedPropsWithDevTools, View.AttachedProps, true)]
+    [InlineData(InitializeComponentCode.AttachedProps, View.AttachedProps, false)]
+    [InlineData(InitializeComponentCode.ControlWithoutWindow, View.ControlWithoutWindow, true)]
+    [InlineData(InitializeComponentCode.ControlWithoutWindow, View.ControlWithoutWindow, false)]
+    public async Task Should_Generate_FindControl_Refs_From_Avalonia_Markup_File(
+        string expectation,
+        string markup,
+        bool devToolsMode)
+    {
+        var excluded = devToolsMode ? null : "Avalonia.Diagnostics";
+        var compilation =
+            View.CreateAvaloniaCompilation(excluded)
+                .WithCustomTextBox();
+
+        var types = new RoslynTypeSystem(compilation);
+        var classResolver = new XamlXViewResolver(
+            types,
+            MiniCompiler.CreateDefault(
+                new RoslynTypeSystem(compilation),
+                MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
+
+        var xaml = await View.Load(markup);
+        var classInfo = classResolver.ResolveView(xaml);
+        var nameResolver = new XamlXNameResolver();
+        var names = nameResolver.ResolveNames(classInfo.Xaml);
+
+        var generator = new InitializeComponentCodeGenerator(types);
+
+        var code = generator
+            .GenerateCode("SampleView", "Sample.App",  classInfo.XamlType, names)
+            .Replace("\r", string.Empty);
+
+        var expected = await InitializeComponentCode.Load(expectation);
+            
+            
+        CSharpSyntaxTree.ParseText(code);
+        Assert.Equal(expected.Replace("\r", string.Empty), code);
+    }
+}

+ 59 - 0
tests/Avalonia.Generators.Tests/MiniCompilerTests.cs

@@ -0,0 +1,59 @@
+using System;
+using System.ComponentModel;
+using Avalonia.Generators.Compiler;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Avalonia.Generators.Tests.Views;
+using XamlX;
+using XamlX.Parsers;
+using Xunit;
+
+namespace Avalonia.Generators.Tests;
+
+public class MiniCompilerTests
+{
+    private const string AvaloniaXaml = "<TextBlock xmlns='clr-namespace:Avalonia.Controls;assembly=Avalonia' />";
+    private const string MiniClass = "namespace Example { public class Valid { public int Foo() => 21; } }";
+    private const string MiniInvalidXaml = "<Invalid xmlns='clr-namespace:Example;assembly=Example' />";
+    private const string MiniValidXaml = "<Valid xmlns='clr-namespace:Example;assembly=Example' />";
+
+    [Fact]
+    public void Should_Resolve_Types_From_Simple_Valid_Xaml_Markup()
+    {
+        var xaml = XDocumentXamlParser.Parse(MiniValidXaml);
+        var compilation = CreateBasicCompilation(MiniClass);
+        MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation)).Transform(xaml);
+
+        Assert.NotNull(xaml.Root);
+    }
+
+    [Fact]
+    public void Should_Throw_When_Unable_To_Resolve_Types_From_Simple_Invalid_Markup()
+    {
+        var xaml = XDocumentXamlParser.Parse(MiniInvalidXaml);
+        var compilation = CreateBasicCompilation(MiniClass);
+        var compiler = MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation));
+
+        Assert.Throws<XamlParseException>(() => compiler.Transform(xaml));
+    }
+
+    [Fact]
+    public void Should_Resolve_Types_From_Simple_Avalonia_Markup()
+    {
+        var xaml = XDocumentXamlParser.Parse(AvaloniaXaml);
+        var compilation = View.CreateAvaloniaCompilation();
+        MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation)).Transform(xaml);
+
+        Assert.NotNull(xaml.Root);
+    }
+
+    private static CSharpCompilation CreateBasicCompilation(string source) =>
+        CSharpCompilation
+            .Create("BasicLib", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(IServiceProvider).Assembly.Location))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(ITypeDescriptorContext).Assembly.Location))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(ISupportInitialize).Assembly.Location))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(TypeConverterAttribute).Assembly.Location))
+            .AddSyntaxTrees(CSharpSyntaxTree.ParseText(source));
+}

+ 11 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/AttachedProps.txt

@@ -0,0 +1,11 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+    }
+}

+ 11 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/ControlWithoutWindow.txt

@@ -0,0 +1,11 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+    }
+}

+ 13 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/CustomControls.txt

@@ -0,0 +1,13 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.ReactiveUI.RoutedViewHost ClrNamespaceRoutedViewHost => this.FindNameScope()?.Find<global::Avalonia.ReactiveUI.RoutedViewHost>("ClrNamespaceRoutedViewHost");
+        internal global::Avalonia.ReactiveUI.RoutedViewHost UriRoutedViewHost => this.FindNameScope()?.Find<global::Avalonia.ReactiveUI.RoutedViewHost>("UriRoutedViewHost");
+        internal global::Controls.CustomTextBox UserNameTextBox => this.FindNameScope()?.Find<global::Controls.CustomTextBox>("UserNameTextBox");
+    }
+}

+ 12 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/DataTemplates.txt

@@ -0,0 +1,12 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+        internal global::Avalonia.Controls.ListBox NamedListBox => this.FindNameScope()?.Find<global::Avalonia.Controls.ListBox>("NamedListBox");
+    }
+}

+ 16 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/FieldModifier.txt

@@ -0,0 +1,16 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        public global::Avalonia.Controls.TextBox FirstNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("FirstNameTextBox");
+        public global::Avalonia.Controls.TextBox LastNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("LastNameTextBox");
+        protected global::Avalonia.Controls.TextBox PasswordTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+        private global::Avalonia.Controls.TextBox ConfirmPasswordTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("ConfirmPasswordTextBox");
+        internal global::Avalonia.Controls.Button SignUpButton => this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+        internal global::Avalonia.Controls.Button RegisterButton => this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("RegisterButton");
+    }
+}

+ 11 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/NamedControl.txt

@@ -0,0 +1,11 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+    }
+}

+ 13 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/NamedControls.txt

@@ -0,0 +1,13 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+        internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+        internal global::Avalonia.Controls.Button SignUpButton => this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+    }
+}

+ 11 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/NoNamedControls.txt

@@ -0,0 +1,11 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+
+    }
+}

+ 33 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/OnlyPropertiesCode.cs

@@ -0,0 +1,33 @@
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace Avalonia.Generators.Tests.OnlyProperties.GeneratedCode;
+
+public static class OnlyPropertiesCode
+{
+    public const string NamedControl = "NamedControl.txt";
+    public const string NamedControls = "NamedControls.txt";
+    public const string XNamedControl = "xNamedControl.txt";
+    public const string XNamedControls = "xNamedControls.txt";
+    public const string NoNamedControls = "NoNamedControls.txt";
+    public const string CustomControls = "CustomControls.txt";
+    public const string DataTemplates = "DataTemplates.txt";
+    public const string SignUpView = "SignUpView.txt";
+    public const string AttachedProps = "AttachedProps.txt";
+    public const string FieldModifier = "FieldModifier.txt";
+    public const string ControlWithoutWindow = "ControlWithoutWindow.txt";
+        
+    public static async Task<string> Load(string generatedCodeResourceName)
+    {
+        var assembly = typeof(XamlXNameResolverTests).Assembly;
+        var fullResourceName = assembly
+            .GetManifestResourceNames()
+            .First(name => name.Contains("OnlyProperties") && name.EndsWith(generatedCodeResourceName));
+            
+        await using var stream = assembly.GetManifestResourceStream(fullResourceName);
+        using var reader = new StreamReader(stream!);
+        return await reader.ReadToEndAsync();
+    }
+}

+ 20 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/SignUpView.txt

@@ -0,0 +1,20 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Controls.CustomTextBox UserNameTextBox => this.FindNameScope()?.Find<global::Controls.CustomTextBox>("UserNameTextBox");
+        internal global::Avalonia.Controls.TextBlock UserNameValidation => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("UserNameValidation");
+        internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+        internal global::Avalonia.Controls.TextBlock PasswordValidation => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("PasswordValidation");
+        internal global::Avalonia.Controls.ListBox AwesomeListView => this.FindNameScope()?.Find<global::Avalonia.Controls.ListBox>("AwesomeListView");
+        internal global::Avalonia.Controls.TextBox ConfirmPasswordTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("ConfirmPasswordTextBox");
+        internal global::Avalonia.Controls.TextBlock ConfirmPasswordValidation => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("ConfirmPasswordValidation");
+        internal global::Avalonia.Controls.Documents.Run SignUpButtonDescription => this.FindNameScope()?.Find<global::Avalonia.Controls.Documents.Run>("SignUpButtonDescription");
+        internal global::Avalonia.Controls.Button SignUpButton => this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+        internal global::Avalonia.Controls.TextBlock CompoundValidation => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBlock>("CompoundValidation");
+    }
+}

+ 11 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/xNamedControl.txt

@@ -0,0 +1,11 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+    }
+}

+ 13 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/GeneratedCode/xNamedControls.txt

@@ -0,0 +1,13 @@
+// <auto-generated />
+
+using Avalonia.Controls;
+
+namespace Sample.App
+{
+    partial class SampleView
+    {
+        internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("UserNameTextBox");
+        internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindNameScope()?.Find<global::Avalonia.Controls.TextBox>("PasswordTextBox");
+        internal global::Avalonia.Controls.Button SignUpButton => this.FindNameScope()?.Find<global::Avalonia.Controls.Button>("SignUpButton");
+    }
+}

+ 52 - 0
tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs

@@ -0,0 +1,52 @@
+using System.Threading.Tasks;
+using Avalonia.Generators.Common;
+using Avalonia.Generators.Compiler;
+using Avalonia.Generators.NameGenerator;
+using Avalonia.Generators.Tests.OnlyProperties.GeneratedCode;
+using Avalonia.Generators.Tests.Views;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace Avalonia.Generators.Tests.OnlyProperties;
+
+public class OnlyPropertiesTests
+{
+    [Theory]
+    [InlineData(OnlyPropertiesCode.NamedControl, View.NamedControl)]
+    [InlineData(OnlyPropertiesCode.NamedControls, View.NamedControls)]
+    [InlineData(OnlyPropertiesCode.XNamedControl, View.XNamedControl)]
+    [InlineData(OnlyPropertiesCode.XNamedControls, View.XNamedControls)]
+    [InlineData(OnlyPropertiesCode.NoNamedControls, View.NoNamedControls)]
+    [InlineData(OnlyPropertiesCode.CustomControls, View.CustomControls)]
+    [InlineData(OnlyPropertiesCode.DataTemplates, View.DataTemplates)]
+    [InlineData(OnlyPropertiesCode.SignUpView, View.SignUpView)]
+    [InlineData(OnlyPropertiesCode.AttachedProps, View.AttachedProps)]
+    [InlineData(OnlyPropertiesCode.FieldModifier, View.FieldModifier)]
+    [InlineData(OnlyPropertiesCode.ControlWithoutWindow, View.ControlWithoutWindow)]
+    public async Task Should_Generate_FindControl_Refs_From_Avalonia_Markup_File(string expectation, string markup)
+    {
+        var compilation =
+            View.CreateAvaloniaCompilation()
+                .WithCustomTextBox();
+
+        var classResolver = new XamlXViewResolver(
+            new RoslynTypeSystem(compilation),
+            MiniCompiler.CreateDefault(
+                new RoslynTypeSystem(compilation),
+                MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
+
+        var xaml = await View.Load(markup);
+        var classInfo = classResolver.ResolveView(xaml);
+        var nameResolver = new XamlXNameResolver();
+        var names = nameResolver.ResolveNames(classInfo.Xaml);
+
+        var generator = new OnlyPropertiesCodeGenerator();
+        var code = generator
+            .GenerateCode("SampleView", "Sample.App",  classInfo.XamlType, names)
+            .Replace("\r", string.Empty);
+
+        var expected = await OnlyPropertiesCode.Load(expectation);
+        CSharpSyntaxTree.ParseText(code);
+        Assert.Equal(expected.Replace("\r", string.Empty), code);
+    }
+}

+ 10 - 0
tests/Avalonia.Generators.Tests/Views/AttachedProps.xml

@@ -0,0 +1,10 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:custom="clr-namespace:Avalonia.ReactiveUI;assembly=Avalonia.ReactiveUI"
+        xmlns:rxui="http://reactiveui.net"
+        x:Class="Sample.App.AttachedProps"
+        Design.Width="300">
+    <TextBox Name="UserNameTextBox"
+             Watermark="Username input"
+             UseFloatingWatermark="True" />
+</Window>

+ 10 - 0
tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml

@@ -0,0 +1,10 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:custom="clr-namespace:Avalonia.ReactiveUI;assembly=Avalonia.ReactiveUI"
+        xmlns:rxui="http://reactiveui.net"
+        x:Class="Sample.App.ControlWithoutWindow"
+        Design.Width="300">
+    <TextBox Name="UserNameTextBox"
+             Watermark="Username input"
+             UseFloatingWatermark="True" />
+</UserControl>

+ 11 - 0
tests/Avalonia.Generators.Tests/Views/CustomControls.xml

@@ -0,0 +1,11 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:custom="clr-namespace:Avalonia.ReactiveUI;assembly=Avalonia.ReactiveUI"
+        xmlns:controls="clr-namespace:Controls"
+        x:Class="Sample.App.CustomControls"
+        xmlns:rxui="http://reactiveui.net">
+    <custom:RoutedViewHost Name="ClrNamespaceRoutedViewHost" />
+    <rxui:RoutedViewHost Name="UriRoutedViewHost" />
+    <controls:CustomTextBox Name="UserNameTextBox" />
+    <controls:EvilControl Name="EvilName" />
+</Window>

+ 18 - 0
tests/Avalonia.Generators.Tests/Views/DataTemplates.xml

@@ -0,0 +1,18 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.DataTemplates">
+    <StackPanel>
+        <TextBox x:Name="UserNameTextBox"
+                 Watermark="Username input"
+                 UseFloatingWatermark="True" />
+        <ListBox Name="NamedListBox">
+            <ListBox.ItemTemplate>
+                <DataTemplate x:Name="NamedDataTemplate">
+                    <TextBox x:Name="TemplatedTextBox"
+                             Watermark="Templated input"
+                             UseFloatingWatermark="True" />
+                </DataTemplate>
+            </ListBox.ItemTemplate>
+        </ListBox>
+    </StackPanel>
+</Window>

+ 28 - 0
tests/Avalonia.Generators.Tests/Views/FieldModifier.xml

@@ -0,0 +1,28 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.FieldModifier">
+    <StackPanel>
+        <TextBox Name="FirstNameTextBox"
+                 x:FieldModifier="Public"
+                 Watermark="Username input"
+                 UseFloatingWatermark="True" />
+        <TextBox Name="LastNameTextBox"
+                 x:FieldModifier="public"
+                 Watermark="Username input"
+                 UseFloatingWatermark="True" />
+        <TextBox Name="PasswordTextBox"
+                 x:FieldModifier="protected"
+                 Watermark="Password input"
+                 UseFloatingWatermark="True" />
+        <TextBox Name="ConfirmPasswordTextBox"
+                 x:FieldModifier="private"
+                 Watermark="Password input"
+                 UseFloatingWatermark="True" />
+        <Button Name="SignUpButton"
+                x:FieldModifier="NotPublic"
+                Content="Sign up" />
+        <Button Name="RegisterButton"
+                x:FieldModifier="Nonsense"
+                Content="Register" />
+    </StackPanel>
+</Window>

+ 7 - 0
tests/Avalonia.Generators.Tests/Views/NamedControl.xml

@@ -0,0 +1,7 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.NamedControl">
+    <TextBox Name="UserNameTextBox"
+             Watermark="Username input"
+             UseFloatingWatermark="True" />
+</Window>

+ 14 - 0
tests/Avalonia.Generators.Tests/Views/NamedControls.xml

@@ -0,0 +1,14 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.NamedControls">
+    <StackPanel>
+        <TextBox Name="UserNameTextBox"
+                 Watermark="Username input"
+                 UseFloatingWatermark="True" />
+        <TextBox Name="PasswordTextBox"
+                 Watermark="Password input"
+                 UseFloatingWatermark="True" />
+        <Button Name="SignUpButton"
+                Content="Sign up" />
+    </StackPanel>
+</Window>

+ 6 - 0
tests/Avalonia.Generators.Tests/Views/NoNamedControls.xml

@@ -0,0 +1,6 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.NoNamedControls">
+    <TextBox Watermark="Username input"
+             UseFloatingWatermark="True" />
+</Window>

+ 52 - 0
tests/Avalonia.Generators.Tests/Views/SignUpView.xml

@@ -0,0 +1,52 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:controls="clr-namespace:Controls"
+        x:Class="Sample.App.SignUpView">
+    <StackPanel>
+        <controls:CustomTextBox Margin="0 10 0 0"
+                                Name="UserNameTextBox"
+                                Watermark="Please, enter user name..."
+                                UseFloatingWatermark="True" />
+        <TextBlock Name="UserNameValidation"
+                   Foreground="Red"
+                   FontSize="12" />
+        <TextBox Margin="0 10 0 0"
+                 Name="PasswordTextBox"
+                 Watermark="Please, enter your password..."
+                 UseFloatingWatermark="True"
+                 PasswordChar="*" />
+        <TextBlock Name="PasswordValidation"
+                   Foreground="Red"
+                   FontSize="12" />
+        <ListBox x:Name="AwesomeListView">
+            <ListBox.ItemTemplate>
+                <DataTemplate>
+                    <TextBlock x:Name="MeaningLessName" Text="{Binding}" />
+                </DataTemplate>
+            </ListBox.ItemTemplate>
+        </ListBox>
+        <TextBox Margin="0 10 0 0"
+                 x:Name="ConfirmPasswordTextBox"
+                 Watermark="Please, confirm the password..."
+                 UseFloatingWatermark="True"
+                 PasswordChar="*" />
+        <TextBlock x:Name="ConfirmPasswordValidation"
+                   TextWrapping="Wrap"
+                   Foreground="Red"
+                   FontSize="12" />
+        <TextBlock>
+            <TextBlock.Inlines>
+                <InlineCollection>
+                    <Run x:Name="SignUpButtonDescription" />
+                </InlineCollection>
+            </TextBlock.Inlines>
+        </TextBlock>
+        <Button Margin="0 10 0 5"
+                Content="Sign up"
+                x:Name="SignUpButton" />
+        <TextBlock x:Name="CompoundValidation"
+                   TextWrapping="Wrap"
+                   Foreground="Red"
+                   FontSize="12" />
+    </StackPanel>
+</Window>

+ 76 - 0
tests/Avalonia.Generators.Tests/Views/View.cs

@@ -0,0 +1,76 @@
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Avalonia.Generators.Tests.Views;
+
+public static class View
+{
+    public const string NamedControl = "NamedControl.xml";
+    public const string NamedControls = "NamedControls.xml";
+    public const string XNamedControl = "xNamedControl.xml";
+    public const string XNamedControls = "xNamedControls.xml";
+    public const string NoNamedControls = "NoNamedControls.xml";
+    public const string CustomControls = "CustomControls.xml";
+    public const string DataTemplates = "DataTemplates.xml";
+    public const string SignUpView = "SignUpView.xml";
+    public const string AttachedProps = "AttachedProps.xml";
+    public const string FieldModifier = "FieldModifier.xml";
+    public const string ControlWithoutWindow = "ControlWithoutWindow.xml";
+    public const string ViewWithGenericBaseView = "ViewWithGenericBaseView.xml";
+
+    public static async Task<string> Load(string viewName)
+    {
+        var assembly = typeof(XamlXNameResolverTests).Assembly;
+        var fullResourceName = assembly
+            .GetManifestResourceNames()
+            .First(name => name.EndsWith(viewName));
+
+        await using var stream = assembly.GetManifestResourceStream(fullResourceName);
+        using var reader = new StreamReader(stream!);
+        return await reader.ReadToEndAsync();
+    }
+
+    public static CSharpCompilation CreateAvaloniaCompilation(string excludedPattern = null)
+    {
+        var compilation = CSharpCompilation
+            .Create("AvaloniaLib", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(IServiceProvider).Assembly.Location))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(ITypeDescriptorContext).Assembly.Location))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(ISupportInitialize).Assembly.Location))
+            .AddReferences(MetadataReference.CreateFromFile(typeof(TypeConverterAttribute).Assembly.Location));
+
+        var avaloniaAssemblyLocation = typeof(TextBlock).Assembly.Location;
+        var avaloniaAssemblyDirectory = Path.GetDirectoryName(avaloniaAssemblyLocation);
+        var avaloniaAssemblyReferences = Directory
+            .EnumerateFiles(avaloniaAssemblyDirectory!)
+            .Where(file => file.EndsWith(".dll") &&
+                           file.Contains("Avalonia") &&
+                           (string.IsNullOrWhiteSpace(excludedPattern) || !file.Contains(excludedPattern)))
+            .Select(file => MetadataReference.CreateFromFile(file))
+            .ToList();
+
+        return compilation.AddReferences(avaloniaAssemblyReferences);
+    }
+
+    public static CSharpCompilation WithCustomTextBox(this CSharpCompilation compilation) =>
+        compilation.AddSyntaxTrees(
+            CSharpSyntaxTree.ParseText(
+                "using Avalonia.Controls;" +
+                "namespace Controls {" +
+                "  public class CustomTextBox : TextBox { }" +
+                "  public class EvilControl { }" +
+                "}"));
+
+    public static CSharpCompilation WithBaseView(this CSharpCompilation compilation) =>
+        compilation.AddSyntaxTrees(
+            CSharpSyntaxTree.ParseText(
+                "using Avalonia.Controls;" +
+                "namespace Sample.App { public class BaseView<TViewModel> : UserControl { } }"));
+}

+ 13 - 0
tests/Avalonia.Generators.Tests/Views/ViewWithGenericBaseView.xml

@@ -0,0 +1,13 @@
+<local:BaseView
+	    xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.ViewWithGenericBaseView"
+        Design.Width="300"
+		xmlns:sys="clr-namespace:System"
+		x:TypeArguments="sys:String"
+	    x:Name="Root"
+		xmlns:local="clr-namespace:Sample.App">
+	<Grid>
+		<local:BaseView x:Name="NotAsRootNode" x:TypeArguments="sys:Int32" />
+	</Grid>
+</local:BaseView>

+ 7 - 0
tests/Avalonia.Generators.Tests/Views/xNamedControl.xml

@@ -0,0 +1,7 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.xNamedControl">
+    <TextBox x:Name="UserNameTextBox"
+             Watermark="Username input"
+             UseFloatingWatermark="True" />
+</Window>

+ 14 - 0
tests/Avalonia.Generators.Tests/Views/xNamedControls.xml

@@ -0,0 +1,14 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="Sample.App.xNamedControls">
+    <StackPanel>
+        <TextBox x:Name="UserNameTextBox"
+                 Watermark="Username input"
+                 UseFloatingWatermark="True" />
+        <TextBox x:Name="PasswordTextBox"
+                 Watermark="Password input"
+                 UseFloatingWatermark="True" />
+        <Button x:Name="SignUpButton"
+                Content="Sign up" />
+    </StackPanel>
+</Window>

+ 40 - 0
tests/Avalonia.Generators.Tests/XamlXClassResolverTests.cs

@@ -0,0 +1,40 @@
+using System.Threading.Tasks;
+using Avalonia.Generators.Common;
+using Avalonia.Generators.Compiler;
+using Avalonia.Generators.Tests.Views;
+using Xunit;
+
+namespace Avalonia.Generators.Tests;
+
+public class XamlXClassResolverTests
+{
+    [Theory]
+    [InlineData("Sample.App", "NamedControl", View.NamedControl)]
+    [InlineData("Sample.App", "AttachedProps", View.AttachedProps)]
+    [InlineData("Sample.App", "CustomControls", View.CustomControls)]
+    [InlineData("Sample.App", "DataTemplates", View.DataTemplates)]
+    [InlineData("Sample.App", "FieldModifier", View.FieldModifier)]
+    [InlineData("Sample.App", "NamedControls", View.NamedControls)]
+    [InlineData("Sample.App", "NoNamedControls", View.NoNamedControls)]
+    [InlineData("Sample.App", "SignUpView", View.SignUpView)]
+    [InlineData("Sample.App", "xNamedControl", View.XNamedControl)]
+    [InlineData("Sample.App", "xNamedControls", View.XNamedControls)]
+    [InlineData("Sample.App", "ViewWithGenericBaseView", View.ViewWithGenericBaseView)]
+    public async Task Should_Resolve_Base_Class_From_Xaml_File(string nameSpace, string className, string markup)
+    {
+        var xaml = await View.Load(markup);
+        var compilation = View
+            .CreateAvaloniaCompilation()
+            .WithCustomTextBox()
+            .WithBaseView();
+
+        var types = new RoslynTypeSystem(compilation);
+        var resolver = new XamlXViewResolver(
+            types,
+            MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
+
+        var resolvedClass = resolver.ResolveView(xaml);
+        Assert.Equal(className, resolvedClass.ClassName);
+        Assert.Equal(nameSpace, resolvedClass.Namespace);
+    }
+}

+ 141 - 0
tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs

@@ -0,0 +1,141 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Generators.Common;
+using Avalonia.Generators.Common.Domain;
+using Avalonia.Generators.Compiler;
+using Avalonia.ReactiveUI;
+using Avalonia.Generators.Tests.Views;
+using Xunit;
+
+namespace Avalonia.Generators.Tests;
+
+public class XamlXNameResolverTests
+{
+    [Theory]
+    [InlineData(View.NamedControl)]
+    [InlineData(View.XNamedControl)]
+    [InlineData(View.AttachedProps)]
+    public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Control(string resource)
+    {
+        var xaml = await View.Load(resource);
+        var controls = ResolveNames(xaml);
+
+        Assert.NotEmpty(controls);
+        Assert.Equal(1, controls.Count);
+        Assert.Equal("UserNameTextBox", controls[0].Name);
+        Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
+    }
+
+    [Theory]
+    [InlineData(View.NamedControls)]
+    [InlineData(View.XNamedControls)]
+    public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Controls(string resource)
+    {
+        var xaml = await View.Load(resource);
+        var controls = ResolveNames(xaml);
+
+        Assert.NotEmpty(controls);
+        Assert.Equal(3, controls.Count);
+        Assert.Equal("UserNameTextBox", controls[0].Name);
+        Assert.Equal("PasswordTextBox", controls[1].Name);
+        Assert.Equal("SignUpButton", controls[2].Name);
+        Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
+        Assert.Contains(typeof(TextBox).FullName!, controls[1].TypeName);
+        Assert.Contains(typeof(Button).FullName!, controls[2].TypeName);
+    }
+
+    [Fact]
+    public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Custom_Controls()
+    {
+        var xaml = await View.Load(View.CustomControls);
+        var controls = ResolveNames(xaml);
+
+        Assert.NotEmpty(controls);
+        Assert.Equal(3, controls.Count);
+        Assert.Equal("ClrNamespaceRoutedViewHost", controls[0].Name);
+        Assert.Equal("UriRoutedViewHost", controls[1].Name);
+        Assert.Equal("UserNameTextBox", controls[2].Name);
+        Assert.Contains(typeof(RoutedViewHost).FullName!, controls[0].TypeName);
+        Assert.Contains(typeof(RoutedViewHost).FullName!, controls[1].TypeName);
+        Assert.Contains("Controls.CustomTextBox", controls[2].TypeName);
+    }
+
+    [Fact]
+    public async Task Should_Resolve_Types_From_Avalonia_Markup_File_When_Types_Contains_Generic_Arguments()
+    {
+        var xaml = await View.Load(View.ViewWithGenericBaseView);
+        var controls = ResolveNames(xaml);
+        Assert.Equal(2, controls.Count);
+        
+        var currentControl = controls[0];
+        Assert.Equal("Root", currentControl.Name);
+        Assert.Equal("global::Sample.App.BaseView<global::System.String>", currentControl.TypeName);
+
+        currentControl = controls[1];
+        Assert.Equal("NotAsRootNode", currentControl.Name);
+        Assert.Contains("Sample.App.BaseView", currentControl.TypeName);
+        Assert.Equal("global::Sample.App.BaseView<global::System.Int32>", currentControl.TypeName);
+    }
+
+    [Fact]
+    public async Task Should_Not_Resolve_Named_Controls_From_Avalonia_Markup_File_Without_Named_Controls()
+    {
+        var xaml = await View.Load(View.NoNamedControls);
+        var controls = ResolveNames(xaml);
+
+        Assert.Empty(controls);
+    }
+
+    [Fact]
+    public async Task Should_Not_Resolve_Elements_From_DataTemplates()
+    {
+        var xaml = await View.Load(View.DataTemplates);
+        var controls = ResolveNames(xaml);
+
+        Assert.NotEmpty(controls);
+        Assert.Equal(2, controls.Count);
+        Assert.Equal("UserNameTextBox", controls[0].Name);
+        Assert.Equal("NamedListBox", controls[1].Name);
+        Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
+        Assert.Contains(typeof(ListBox).FullName!, controls[1].TypeName);
+    }
+
+    [Fact]
+    public async Task Should_Resolve_Names_From_Complex_Views()
+    {
+        var xaml = await View.Load(View.SignUpView);
+        var controls = ResolveNames(xaml);
+
+        Assert.NotEmpty(controls);
+        Assert.Equal(10, controls.Count);
+        Assert.Equal("UserNameTextBox", controls[0].Name);
+        Assert.Equal("UserNameValidation", controls[1].Name);
+        Assert.Equal("PasswordTextBox", controls[2].Name);
+        Assert.Equal("PasswordValidation", controls[3].Name);
+        Assert.Equal("AwesomeListView", controls[4].Name);
+        Assert.Equal("ConfirmPasswordTextBox", controls[5].Name);
+        Assert.Equal("ConfirmPasswordValidation", controls[6].Name);
+        Assert.Equal("SignUpButtonDescription", controls[7].Name);
+        Assert.Equal("SignUpButton", controls[8].Name);
+        Assert.Equal("CompoundValidation", controls[9].Name);
+    }
+
+    private static IReadOnlyList<ResolvedName> ResolveNames(string xaml)
+    {
+        var compilation =
+            View.CreateAvaloniaCompilation()
+                .WithCustomTextBox()
+                .WithBaseView();
+
+        var classResolver = new XamlXViewResolver(
+            new RoslynTypeSystem(compilation),
+            MiniCompiler.CreateDefault(
+                new RoslynTypeSystem(compilation),
+                MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
+
+        var classInfo = classResolver.ResolveView(xaml);
+        var nameResolver = new XamlXNameResolver();
+        return nameResolver.ResolveNames(classInfo.Xaml);
+    }
+}

+ 1 - 0
tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs

@@ -140,6 +140,7 @@ namespace Avalonia.IntegrationTests.Appium
                         .First(x => x.Text == newWindowTitle);
                     var (close, _, _) = ((AppiumWebElement)newWindow).GetChromeButtons();
                     close!.Click();
+                    Thread.Sleep(1000);
                 });
             }
         }

+ 9 - 1
tests/Avalonia.RenderTests/TestBase.cs

@@ -31,7 +31,7 @@ namespace Avalonia.Skia.RenderTests
 namespace Avalonia.Direct2D1.RenderTests
 #endif
 {
-    public class TestBase
+    public class TestBase : IDisposable
     {
 #if AVALONIA_SKIA
         private static string s_fontUri = "resm:Avalonia.Skia.RenderTests.Assets?assembly=Avalonia.Skia.RenderTests#Noto Mono";
@@ -282,5 +282,13 @@ namespace Avalonia.Direct2D1.RenderTests
                 });
             }
         }
+
+        public void Dispose()
+        {
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                Dispatcher.UIThread.RunJobs();
+            }
+        }
     }
 }

+ 6 - 0
tests/Avalonia.UnitTests/TestWithServicesBase.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Avalonia.Threading;
 
 namespace Avalonia.UnitTests
 {
@@ -17,6 +18,11 @@ namespace Avalonia.UnitTests
 
         public void Dispose()
         {
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                Dispatcher.UIThread.RunJobs();
+            }
+            
             _scope.Dispose();
         }
     }

+ 5 - 0
tests/Avalonia.UnitTests/UnitTestApplication.cs

@@ -46,6 +46,11 @@ namespace Avalonia.UnitTests
             Dispatcher.UIThread.UpdateServices();
             return Disposable.Create(() =>
             {
+                if (Dispatcher.UIThread.CheckAccess())
+                {
+                    Dispatcher.UIThread.RunJobs();
+                }
+
                 scope.Dispose();
                 Dispatcher.UIThread.UpdateServices();
             });