12 changed files with 380 additions and 0 deletions
@ -0,0 +1,7 @@ |
|||
<Application xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="XamlNameReferenceGenerator.Sandbox.App"> |
|||
<Application.Styles> |
|||
<StyleInclude Source="avares://Citrus.Avalonia/Citrus.xaml" /> |
|||
</Application.Styles> |
|||
</Application> |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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<App>() |
|||
.UseReactiveUI() |
|||
.UsePlatformDetect() |
|||
.LogToDebug(); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="XamlNameReferenceGenerator.Sandbox.SignUpView"> |
|||
<StackPanel> |
|||
<TextBox 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" /> |
|||
|
|||
<Button Margin="0 10 0 5" |
|||
Content="Sign up" |
|||
x:Name="SignUpButton" /> |
|||
<TextBlock x:Name="CompoundValidation" |
|||
TextWrapping="Wrap" |
|||
Foreground="Red" |
|||
FontSize="12" /> |
|||
</StackPanel> |
|||
</Window> |
|||
@ -0,0 +1,29 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace XamlNameReferenceGenerator.Sandbox |
|||
{ |
|||
/// <summary>
|
|||
/// 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/
|
|||
/// </summary>
|
|||
[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."; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<OutputType>Exe</OutputType> |
|||
<TargetFramework>net5</TargetFramework> |
|||
<LangVersion>preview</LangVersion> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="Avalonia" Version="0.9.10" /> |
|||
<PackageReference Include="Avalonia.Desktop" Version="0.9.10" /> |
|||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.10" /> |
|||
<PackageReference Include="Citrus.Avalonia" Version="1.2.6" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Compile Update="**\*.xaml.cs"> |
|||
<DependentUpon>%(Filename)</DependentUpon> |
|||
</Compile> |
|||
<EmbeddedResource Include="**\*.xaml"> |
|||
<SubType>Designer</SubType> |
|||
</EmbeddedResource> |
|||
<AdditionalFiles Include="**\*.xaml" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="../XamlNameReferenceGenerator/XamlNameReferenceGenerator.csproj" |
|||
OutputItemType="Analyzer" |
|||
ReferenceOutputAssembly="false" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -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 |
|||
@ -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<string> 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; |
|||
} |
|||
} |
|||
} |
|||
@ -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 = @"// <auto-generated />
|
|||
|
|||
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 $@"// <auto-generated />
|
|||
|
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace {nameSpace} |
|||
{{ |
|||
public partial class {className} |
|||
{{ |
|||
{string.Join("\n", namedControls)} |
|||
}} |
|||
}} |
|||
";
|
|||
} |
|||
|
|||
private static IReadOnlyList<INamedTypeSymbol> 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<INamedTypeSymbol>(); |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -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<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>(); |
|||
|
|||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode) |
|||
{ |
|||
if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax && |
|||
classDeclarationSyntax.AttributeLists.Count > 0) |
|||
CandidateClasses.Add(classDeclarationSyntax); |
|||
} |
|||
} |
|||
} |
|||
@ -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<XmlNode> elementVisitor) |
|||
{ |
|||
foreach (XmlNode node in doc.ChildNodes) |
|||
{ |
|||
IterateNode(node, elementVisitor); |
|||
} |
|||
} |
|||
|
|||
private static void IterateNode(XmlNode node, Action<XmlNode> elementVisitor) |
|||
{ |
|||
elementVisitor(node); |
|||
foreach (XmlNode childNode in node.ChildNodes) |
|||
{ |
|||
IterateNode(childNode, elementVisitor); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<LangVersion>preview</LangVersion> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="Avalonia" Version="0.9.10" /> |
|||
<PackageReference Include="Avalonia.Desktop" Version="0.9.10" /> |
|||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.10" /> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0-3.final" PrivateAssets="all" /> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
Loading…
Reference in new issue