diff --git a/XamlNameReferenceGenerator.Sandbox/App.xaml b/XamlNameReferenceGenerator.Sandbox/App.xaml
new file mode 100644
index 0000000000..5179864e9c
--- /dev/null
+++ b/XamlNameReferenceGenerator.Sandbox/App.xaml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator.Sandbox/App.xaml.cs b/XamlNameReferenceGenerator.Sandbox/App.xaml.cs
new file mode 100644
index 0000000000..47c497a600
--- /dev/null
+++ b/XamlNameReferenceGenerator.Sandbox/App.xaml.cs
@@ -0,0 +1,16 @@
+using Avalonia;
+using Avalonia.Markup.Xaml;
+
+namespace XamlNameReferenceGenerator.Sandbox
+{
+ public class App : Application
+ {
+ public override void Initialize() => AvaloniaXamlLoader.Load(this);
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ new SignUpView().Show();
+ base.OnFrameworkInitializationCompleted();
+ }
+ }
+}
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator.Sandbox/Program.cs b/XamlNameReferenceGenerator.Sandbox/Program.cs
new file mode 100644
index 0000000000..a5719183cb
--- /dev/null
+++ b/XamlNameReferenceGenerator.Sandbox/Program.cs
@@ -0,0 +1,17 @@
+using Avalonia;
+using Avalonia.Logging.Serilog;
+using Avalonia.ReactiveUI;
+
+namespace XamlNameReferenceGenerator.Sandbox
+{
+ class Program
+ {
+ public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UseReactiveUI()
+ .UsePlatformDetect()
+ .LogToDebug();
+ }
+}
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator.Sandbox/SignUpView.xaml b/XamlNameReferenceGenerator.Sandbox/SignUpView.xaml
new file mode 100644
index 0000000000..45e1a23daf
--- /dev/null
+++ b/XamlNameReferenceGenerator.Sandbox/SignUpView.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator.Sandbox/SignUpView.xaml.cs b/XamlNameReferenceGenerator.Sandbox/SignUpView.xaml.cs
new file mode 100644
index 0000000000..cb517e0c83
--- /dev/null
+++ b/XamlNameReferenceGenerator.Sandbox/SignUpView.xaml.cs
@@ -0,0 +1,29 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace XamlNameReferenceGenerator.Sandbox
+{
+ ///
+ /// This is a sample view class with typed x:Name references generated at compile-time using
+ /// .NET 5 source generators. The class should be marked with [GenerateTypedNameReferences],
+ /// this attribute is also compile-time generated. 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/
+ ///
+ [GenerateTypedNameReferences]
+ public partial class SignUpView : Window
+ {
+ public SignUpView()
+ {
+ AvaloniaXamlLoader.Load(this);
+ UserNameTextBox.Text = "Joseph";
+ UserNameValidation.Text = "User name is valid.";
+ PasswordTextBox.Text = "qwerty";
+ PasswordValidation.Text = "Password is valid.";
+ ConfirmPasswordTextBox.Text = "qwerty";
+ ConfirmPasswordValidation.Text = "Password confirmation is valid.";
+ SignUpButton.Content = "Sign up please!";
+ CompoundValidation.Text = "Everything is okay.";
+ }
+ }
+}
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator.Sandbox/XamlNameReferenceGenerator.Sandbox.csproj b/XamlNameReferenceGenerator.Sandbox/XamlNameReferenceGenerator.Sandbox.csproj
new file mode 100644
index 0000000000..05a9929f1c
--- /dev/null
+++ b/XamlNameReferenceGenerator.Sandbox/XamlNameReferenceGenerator.Sandbox.csproj
@@ -0,0 +1,27 @@
+
+
+ Exe
+ net5
+ preview
+
+
+
+
+
+
+
+
+
+ %(Filename)
+
+
+ Designer
+
+
+
+
+
+
+
diff --git a/XamlNameReferenceGenerator.sln b/XamlNameReferenceGenerator.sln
new file mode 100644
index 0000000000..5d55b96e20
--- /dev/null
+++ b/XamlNameReferenceGenerator.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlNameReferenceGenerator", "XamlNameReferenceGenerator\XamlNameReferenceGenerator.csproj", "{684ADF3B-8DFC-4F4A-93AD-F856561FCC39}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlNameReferenceGenerator.Sandbox", "XamlNameReferenceGenerator.Sandbox\XamlNameReferenceGenerator.Sandbox.csproj", "{C90BB1C6-5C33-494A-96FA-FEE7B34CA83C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {684ADF3B-8DFC-4F4A-93AD-F856561FCC39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {684ADF3B-8DFC-4F4A-93AD-F856561FCC39}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {684ADF3B-8DFC-4F4A-93AD-F856561FCC39}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {684ADF3B-8DFC-4F4A-93AD-F856561FCC39}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C90BB1C6-5C33-494A-96FA-FEE7B34CA83C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C90BB1C6-5C33-494A-96FA-FEE7B34CA83C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C90BB1C6-5C33-494A-96FA-FEE7B34CA83C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C90BB1C6-5C33-494A-96FA-FEE7B34CA83C}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/XamlNameReferenceGenerator/NameReferenceDebugger.cs b/XamlNameReferenceGenerator/NameReferenceDebugger.cs
new file mode 100644
index 0000000000..8588fb96ac
--- /dev/null
+++ b/XamlNameReferenceGenerator/NameReferenceDebugger.cs
@@ -0,0 +1,32 @@
+using System;
+using System.IO;
+
+namespace XamlNameReferenceGenerator
+{
+ internal class NameReferenceDebugger
+ {
+ private readonly string _path;
+
+ public NameReferenceDebugger(string path) => _path = path;
+
+ public string Debug(Func function)
+ {
+ if (File.Exists(_path))
+ File.Delete(_path);
+
+ string sourceCode;
+ try
+ {
+ sourceCode = function();
+ File.WriteAllText(_path, sourceCode);
+ }
+ catch (Exception exception)
+ {
+ File.WriteAllText(_path, exception.ToString());
+ sourceCode = string.Empty;
+ }
+
+ return sourceCode;
+ }
+ }
+}
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator/NameReferenceGenerator.cs b/XamlNameReferenceGenerator/NameReferenceGenerator.cs
new file mode 100644
index 0000000000..88862c1052
--- /dev/null
+++ b/XamlNameReferenceGenerator/NameReferenceGenerator.cs
@@ -0,0 +1,110 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using System.Text;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace XamlNameReferenceGenerator
+{
+ [Generator]
+ public class NameReferenceGenerator : ISourceGenerator
+ {
+ private const string AttributeName = "XamlNameReferenceGenerator.GenerateTypedNameReferencesAttribute";
+ private const string AttributeFile = "GenerateTypedNameReferencesAttribute";
+ private const string AttributeCode = @"//
+
+using System;
+
+namespace XamlNameReferenceGenerator
+{
+ [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
+ sealed class GenerateTypedNameReferencesAttribute : Attribute { }
+}
+";
+ private const string DebugPath = @"C:\Users\prizr\Documents\GitHub\XamlNameReferenceGenerator\debug.txt";
+ private static readonly NameReferenceXamlParser XamlParser = new NameReferenceXamlParser();
+ private static readonly NameReferenceDebugger Debugger = new NameReferenceDebugger(DebugPath);
+ private static readonly SymbolDisplayFormat SymbolDisplayFormat = new SymbolDisplayFormat(
+ typeQualificationStyle:SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
+ genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters |
+ SymbolDisplayGenericsOptions.IncludeTypeConstraints |
+ SymbolDisplayGenericsOptions.IncludeVariance);
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(() => new NameReferenceSyntaxReceiver());
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));
+ if (!(context.SyntaxReceiver is NameReferenceSyntaxReceiver receiver))
+ return;
+
+ var symbols = UnpackAnnotatedTypes((CSharpCompilation) context.Compilation, receiver);
+ foreach (var typeSymbol in symbols)
+ {
+ var relevantXamlFile = context.AdditionalFiles
+ .First(text =>
+ text.Path.EndsWith($"{typeSymbol.Name}.xaml") ||
+ text.Path.EndsWith($"{typeSymbol.Name}.axaml"));
+
+ var sourceCode = Debugger.Debug(() => GenerateSourceCode(typeSymbol, relevantXamlFile));
+ context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
+ }
+ }
+
+ private static string GenerateSourceCode(INamedTypeSymbol classSymbol, AdditionalText xamlFile)
+ {
+ var className = classSymbol.Name;
+ var nameSpace = classSymbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat);
+ var namedControls = XamlParser
+ .GetNamedControls(xamlFile)
+ .Select(info => " " +
+ $"public {info.TypeName} {info.Name} => " +
+ $"this.FindControl<{info.TypeName}>(\"{info.Name}\");");
+ return $@"//
+
+using System;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace {nameSpace}
+{{
+ public partial class {className}
+ {{
+{string.Join("\n", namedControls)}
+ }}
+}}
+";
+ }
+
+ private static IReadOnlyList UnpackAnnotatedTypes(
+ CSharpCompilation existingCompilation,
+ NameReferenceSyntaxReceiver nameReferenceSyntaxReceiver)
+ {
+ var options = (CSharpParseOptions)existingCompilation.SyntaxTrees[0].Options;
+ var compilation = existingCompilation.AddSyntaxTrees(
+ CSharpSyntaxTree.ParseText(
+ SourceText.From(AttributeCode, Encoding.UTF8),
+ options));
+
+ var attributeSymbol = compilation.GetTypeByMetadataName(AttributeName);
+ var typeSymbols = new List();
+ foreach (var candidateClass in nameReferenceSyntaxReceiver.CandidateClasses)
+ {
+ var model = compilation.GetSemanticModel(candidateClass.SyntaxTree);
+ var typeSymbol = (INamedTypeSymbol) model.GetDeclaredSymbol(candidateClass);
+ var containsAttribute = typeSymbol!
+ .GetAttributes()
+ .Any(attr => attr.AttributeClass!.Equals(attributeSymbol, SymbolEqualityComparer.Default));
+
+ if (containsAttribute)
+ typeSymbols.Add(typeSymbol);
+ }
+
+ return typeSymbols;
+ }
+ }
+}
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator/NameReferenceSyntaxReceiver.cs b/XamlNameReferenceGenerator/NameReferenceSyntaxReceiver.cs
new file mode 100644
index 0000000000..f601a6f4d8
--- /dev/null
+++ b/XamlNameReferenceGenerator/NameReferenceSyntaxReceiver.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace XamlNameReferenceGenerator
+{
+ internal class NameReferenceSyntaxReceiver : ISyntaxReceiver
+ {
+ public List CandidateClasses { get; } = new List();
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&
+ classDeclarationSyntax.AttributeLists.Count > 0)
+ CandidateClasses.Add(classDeclarationSyntax);
+ }
+ }
+}
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator/NameReferenceXamlParser.cs b/XamlNameReferenceGenerator/NameReferenceXamlParser.cs
new file mode 100644
index 0000000000..0ea5c38129
--- /dev/null
+++ b/XamlNameReferenceGenerator/NameReferenceXamlParser.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using Microsoft.CodeAnalysis;
+
+namespace XamlNameReferenceGenerator
+{
+ internal class NameReferenceXamlParser
+ {
+ public List<(string TypeName, string Name)> GetNamedControls(AdditionalText additionalText)
+ {
+ var xaml = additionalText.GetText()!.ToString();
+ var document = new XmlDocument();
+ document.LoadXml(xaml);
+
+ var namespaceManager = new XmlNamespaceManager(document.NameTable);
+ namespaceManager.AddNamespace("x", "http://schemas.microsoft.com/winfx/2006/xaml");
+
+ var names = new List<(string TypeName, string Name)>();
+ IterateThroughAllNodes(document, node =>
+ {
+ var type = node.Name;
+ var name = node.Attributes?["x:Name"]?.Value ??
+ node.Attributes?["Name"]?.Value;
+ if (!string.IsNullOrWhiteSpace(name))
+ names.Add((type, name));
+ });
+ return names;
+ }
+
+ private static void IterateThroughAllNodes(XmlDocument doc, Action elementVisitor)
+ {
+ foreach (XmlNode node in doc.ChildNodes)
+ {
+ IterateNode(node, elementVisitor);
+ }
+ }
+
+ private static void IterateNode(XmlNode node, Action elementVisitor)
+ {
+ elementVisitor(node);
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ IterateNode(childNode, elementVisitor);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/XamlNameReferenceGenerator/XamlNameReferenceGenerator.csproj b/XamlNameReferenceGenerator/XamlNameReferenceGenerator.csproj
new file mode 100644
index 0000000000..b6db147a73
--- /dev/null
+++ b/XamlNameReferenceGenerator/XamlNameReferenceGenerator.csproj
@@ -0,0 +1,13 @@
+
+
+ netstandard2.0
+ preview
+
+
+
+
+
+
+
+
+
\ No newline at end of file