From edf0217591e068cab5cf0b698ee82256887f0e07 Mon Sep 17 00:00:00 2001 From: "Artyom V. Gorchakov" Date: Mon, 8 Feb 2021 01:46:12 +0300 Subject: [PATCH] feature: Support x:FieldModifier Directive (#20) * Refactoring, decomposition, more unit tests * Support x:FieldModifier * Use Xamarin.Forms API for x:FieldModifier * Use directive, bump Avalonia * CRLF dance * Use Fluent theme * Move Avalonia packages to Directory.Build.props * Bump test SDK version --- external/{.gitkeep.txt => .gitkeep} | 0 src/Avalonia.NameGenerator.Sandbox/App.xaml | 2 +- .../App.xaml.cs | 3 +- .../Avalonia.NameGenerator.Sandbox.csproj | 12 +- src/Avalonia.NameGenerator.Sandbox/Program.cs | 4 +- .../Views/SignUpView.xaml | 4 +- .../Views/SignUpView.xaml.cs | 3 +- .../Avalonia.NameGenerator.Tests.csproj | 10 +- .../FindControlNameGeneratorTests.cs | 41 ++++ .../GeneratedCode/AttachedProps.txt | 11 ++ .../GeneratedCode/Code.cs | 32 ++++ .../GeneratedCode/CustomControls.txt | 13 ++ .../GeneratedCode/DataTemplates.txt | 12 ++ .../GeneratedCode/FieldModifier.txt | 16 ++ .../GeneratedCode/NamedControl.txt | 11 ++ .../GeneratedCode/NamedControls.txt | 13 ++ .../GeneratedCode/NoNamedControls.txt | 11 ++ .../GeneratedCode/SignUpView.txt | 19 ++ .../GeneratedCode/xNamedControl.txt | 11 ++ .../GeneratedCode/xNamedControls.txt | 13 ++ .../MiniCompilerTests.cs | 14 +- .../NameResolverTests.cs | 180 ------------------ .../Views/FieldModifier.xml | 27 +++ .../Views/View.cs | 67 +++++++ .../XamlXNameResolverTests.cs | 121 ++++++++++++ src/Avalonia.NameGenerator.sln | 8 + .../Avalonia.NameGenerator.csproj | 4 +- .../DataTemplateTransformer.cs | 2 +- .../MiniCompiler.cs | 3 +- .../Compiler/NameDirectiveTransformer.cs | 29 +++ .../RoslynTypeSystem.cs | 2 +- .../Infrastructure/INameResolver.cs | 9 - .../NameDirectiveTransformer.cs | 29 --- .../Infrastructure/NameReceiver.cs | 54 ------ .../Infrastructure/NameResolver.cs | 32 ---- .../NameReferenceGenerator.cs | 36 +--- .../NameReferenceSyntaxReceiver.cs | 1 - .../Resolver/FindControlNameGenerator.cs | 31 +++ .../Resolver/INameGenerator.cs | 9 + .../Resolver/INameResolver.cs | 43 +++++ .../Resolver/XamlXNameResolver.cs | 102 ++++++++++ ...tory.build.props => Directory.Build.props} | 10 + 42 files changed, 684 insertions(+), 370 deletions(-) rename external/{.gitkeep.txt => .gitkeep} (100%) create mode 100644 src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/AttachedProps.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/Code.cs create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/CustomControls.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/DataTemplates.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/FieldModifier.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/NamedControl.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/NamedControls.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/NoNamedControls.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/SignUpView.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/xNamedControl.txt create mode 100644 src/Avalonia.NameGenerator.Tests/GeneratedCode/xNamedControls.txt delete mode 100644 src/Avalonia.NameGenerator.Tests/NameResolverTests.cs create mode 100644 src/Avalonia.NameGenerator.Tests/Views/FieldModifier.xml create mode 100644 src/Avalonia.NameGenerator.Tests/Views/View.cs create mode 100644 src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs rename src/Avalonia.NameGenerator/{Infrastructure => Compiler}/DataTemplateTransformer.cs (91%) rename src/Avalonia.NameGenerator/{Infrastructure => Compiler}/MiniCompiler.cs (95%) create mode 100644 src/Avalonia.NameGenerator/Compiler/NameDirectiveTransformer.cs rename src/Avalonia.NameGenerator/{Infrastructure => Compiler}/RoslynTypeSystem.cs (99%) delete mode 100644 src/Avalonia.NameGenerator/Infrastructure/INameResolver.cs delete mode 100644 src/Avalonia.NameGenerator/Infrastructure/NameDirectiveTransformer.cs delete mode 100644 src/Avalonia.NameGenerator/Infrastructure/NameReceiver.cs delete mode 100644 src/Avalonia.NameGenerator/Infrastructure/NameResolver.cs create mode 100644 src/Avalonia.NameGenerator/Resolver/FindControlNameGenerator.cs create mode 100644 src/Avalonia.NameGenerator/Resolver/INameGenerator.cs create mode 100644 src/Avalonia.NameGenerator/Resolver/INameResolver.cs create mode 100644 src/Avalonia.NameGenerator/Resolver/XamlXNameResolver.cs rename src/{Directory.build.props => Directory.Build.props} (62%) diff --git a/external/.gitkeep.txt b/external/.gitkeep similarity index 100% rename from external/.gitkeep.txt rename to external/.gitkeep diff --git a/src/Avalonia.NameGenerator.Sandbox/App.xaml b/src/Avalonia.NameGenerator.Sandbox/App.xaml index 832760aa12..889bbe1e66 100644 --- a/src/Avalonia.NameGenerator.Sandbox/App.xaml +++ b/src/Avalonia.NameGenerator.Sandbox/App.xaml @@ -2,6 +2,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Avalonia.NameGenerator.Sandbox.App"> - + \ No newline at end of file diff --git a/src/Avalonia.NameGenerator.Sandbox/App.xaml.cs b/src/Avalonia.NameGenerator.Sandbox/App.xaml.cs index 2edb66f525..12210d4bce 100644 --- a/src/Avalonia.NameGenerator.Sandbox/App.xaml.cs +++ b/src/Avalonia.NameGenerator.Sandbox/App.xaml.cs @@ -9,7 +9,8 @@ namespace Avalonia.NameGenerator.Sandbox public override void OnFrameworkInitializationCompleted() { - new SignUpView().Show(); + var view = new SignUpView(); + view.Show(); base.OnFrameworkInitializationCompleted(); } } diff --git a/src/Avalonia.NameGenerator.Sandbox/Avalonia.NameGenerator.Sandbox.csproj b/src/Avalonia.NameGenerator.Sandbox/Avalonia.NameGenerator.Sandbox.csproj index 30e9ae2347..a0218f4bec 100644 --- a/src/Avalonia.NameGenerator.Sandbox/Avalonia.NameGenerator.Sandbox.csproj +++ b/src/Avalonia.NameGenerator.Sandbox/Avalonia.NameGenerator.Sandbox.csproj @@ -4,13 +4,8 @@ net5 preview false + true - - - - - - %(Filename) @@ -18,11 +13,10 @@ Designer + - + diff --git a/src/Avalonia.NameGenerator.Sandbox/Program.cs b/src/Avalonia.NameGenerator.Sandbox/Program.cs index 84de29af16..39d3d0ff7f 100644 --- a/src/Avalonia.NameGenerator.Sandbox/Program.cs +++ b/src/Avalonia.NameGenerator.Sandbox/Program.cs @@ -6,10 +6,10 @@ namespace Avalonia.NameGenerator.Sandbox { public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); - public static AppBuilder BuildAvaloniaApp() + private static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UseReactiveUI() .UsePlatformDetect() - .LogToDebug(); + .LogToTrace(); } } \ No newline at end of file diff --git a/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml b/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml index 78c9dfc699..b300765827 100644 --- a/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml +++ b/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml @@ -10,9 +10,11 @@ UseFloatingWatermark="True" /> @@ -47,4 +49,4 @@ Foreground="Red" FontSize="12" /> - \ No newline at end of file + diff --git a/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml.cs b/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml.cs index d946bef821..c5798628ec 100644 --- a/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml.cs +++ b/src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml.cs @@ -1,5 +1,4 @@ -using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Avalonia.NameGenerator.Sandbox.Views diff --git a/src/Avalonia.NameGenerator.Tests/Avalonia.NameGenerator.Tests.csproj b/src/Avalonia.NameGenerator.Tests/Avalonia.NameGenerator.Tests.csproj index 9a2f8c86cb..bc73022bc8 100644 --- a/src/Avalonia.NameGenerator.Tests/Avalonia.NameGenerator.Tests.csproj +++ b/src/Avalonia.NameGenerator.Tests/Avalonia.NameGenerator.Tests.csproj @@ -4,19 +4,19 @@ net5 preview false + true - - - + + + - - + diff --git a/src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs b/src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs new file mode 100644 index 0000000000..460c597240 --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Avalonia.NameGenerator.Resolver; +using Avalonia.NameGenerator.Tests.GeneratedCode; +using Avalonia.NameGenerator.Tests.Views; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace Avalonia.NameGenerator.Tests +{ + public class FindControlNameGeneratorTests + { + [Theory] + [InlineData(Code.NamedControl, View.NamedControl)] + [InlineData(Code.NamedControls, View.NamedControls)] + [InlineData(Code.XNamedControl, View.XNamedControl)] + [InlineData(Code.XNamedControls, View.XNamedControls)] + [InlineData(Code.NoNamedControls, View.NoNamedControls)] + [InlineData(Code.CustomControls, View.CustomControls)] + [InlineData(Code.DataTemplates, View.DataTemplates)] + [InlineData(Code.SignUpView, View.SignUpView)] + [InlineData(Code.AttachedProps, View.AttachedProps)] + [InlineData(Code.FieldModifier, View.FieldModifier)] + public async Task Should_Generate_FindControl_Refs_From_Avalonia_Markup_File(string expectation, string markup) + { + var xaml = await View.Load(markup); + var compilation = + View.CreateAvaloniaCompilation() + .WithCustomTextBox(); + + var resolver = new XamlXNameResolver(compilation); + var generator = new FindControlNameGenerator(); + var code = generator + .GenerateNames("SampleView", "Sample.App", resolver.ResolveNames(xaml)) + .Replace("\r", string.Empty); + + var expected = await Code.Load(expectation); + CSharpSyntaxTree.ParseText(code); + Assert.Equal(expected.Replace("\r", string.Empty), code); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/AttachedProps.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/AttachedProps.txt new file mode 100644 index 0000000000..c4202f080d --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/AttachedProps.txt @@ -0,0 +1,11 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl("UserNameTextBox"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/Code.cs b/src/Avalonia.NameGenerator.Tests/GeneratedCode/Code.cs new file mode 100644 index 0000000000..1f44606788 --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/Code.cs @@ -0,0 +1,32 @@ +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Avalonia.NameGenerator.Tests.GeneratedCode +{ + public class Code + { + 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 static async Task Load(string generatedCodeResourceName) + { + var assembly = typeof(XamlXNameResolverTests).Assembly; + var fullResourceName = assembly + .GetManifestResourceNames() + .First(name => name.EndsWith(generatedCodeResourceName)); + + await using var stream = assembly.GetManifestResourceStream(fullResourceName); + using var reader = new StreamReader(stream!); + return await reader.ReadToEndAsync(); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/CustomControls.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/CustomControls.txt new file mode 100644 index 0000000000..b87d95048b --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/CustomControls.txt @@ -0,0 +1,13 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + internal global::Avalonia.ReactiveUI.RoutedViewHost ClrNamespaceRoutedViewHost => this.FindControl("ClrNamespaceRoutedViewHost"); + internal global::Avalonia.ReactiveUI.RoutedViewHost UriRoutedViewHost => this.FindControl("UriRoutedViewHost"); + internal global::Controls.CustomTextBox UserNameTextBox => this.FindControl("UserNameTextBox"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/DataTemplates.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/DataTemplates.txt new file mode 100644 index 0000000000..0e8e6f1f09 --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/DataTemplates.txt @@ -0,0 +1,12 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl("UserNameTextBox"); + internal global::Avalonia.Controls.ListBox NamedListBox => this.FindControl("NamedListBox"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/FieldModifier.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/FieldModifier.txt new file mode 100644 index 0000000000..17df7f2d82 --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/FieldModifier.txt @@ -0,0 +1,16 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + public global::Avalonia.Controls.TextBox FirstNameTextBox => this.FindControl("FirstNameTextBox"); + public global::Avalonia.Controls.TextBox LastNameTextBox => this.FindControl("LastNameTextBox"); + protected global::Avalonia.Controls.TextBox PasswordTextBox => this.FindControl("PasswordTextBox"); + private global::Avalonia.Controls.TextBox ConfirmPasswordTextBox => this.FindControl("ConfirmPasswordTextBox"); + internal global::Avalonia.Controls.Button SignUpButton => this.FindControl("SignUpButton"); + internal global::Avalonia.Controls.Button RegisterButton => this.FindControl("RegisterButton"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/NamedControl.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/NamedControl.txt new file mode 100644 index 0000000000..c4202f080d --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/NamedControl.txt @@ -0,0 +1,11 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl("UserNameTextBox"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/NamedControls.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/NamedControls.txt new file mode 100644 index 0000000000..e7a678d4a4 --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/NamedControls.txt @@ -0,0 +1,13 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl("UserNameTextBox"); + internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindControl("PasswordTextBox"); + internal global::Avalonia.Controls.Button SignUpButton => this.FindControl("SignUpButton"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/NoNamedControls.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/NoNamedControls.txt new file mode 100644 index 0000000000..7db25c4693 --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/NoNamedControls.txt @@ -0,0 +1,11 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/SignUpView.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/SignUpView.txt new file mode 100644 index 0000000000..76067e6e7b --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/SignUpView.txt @@ -0,0 +1,19 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + internal global::Controls.CustomTextBox UserNameTextBox => this.FindControl("UserNameTextBox"); + internal global::Avalonia.Controls.TextBlock UserNameValidation => this.FindControl("UserNameValidation"); + internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindControl("PasswordTextBox"); + internal global::Avalonia.Controls.TextBlock PasswordValidation => this.FindControl("PasswordValidation"); + internal global::Avalonia.Controls.ListBox AwesomeListView => this.FindControl("AwesomeListView"); + internal global::Avalonia.Controls.TextBox ConfirmPasswordTextBox => this.FindControl("ConfirmPasswordTextBox"); + internal global::Avalonia.Controls.TextBlock ConfirmPasswordValidation => this.FindControl("ConfirmPasswordValidation"); + internal global::Avalonia.Controls.Button SignUpButton => this.FindControl("SignUpButton"); + internal global::Avalonia.Controls.TextBlock CompoundValidation => this.FindControl("CompoundValidation"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/xNamedControl.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/xNamedControl.txt new file mode 100644 index 0000000000..c4202f080d --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/xNamedControl.txt @@ -0,0 +1,11 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl("UserNameTextBox"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/GeneratedCode/xNamedControls.txt b/src/Avalonia.NameGenerator.Tests/GeneratedCode/xNamedControls.txt new file mode 100644 index 0000000000..e7a678d4a4 --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/GeneratedCode/xNamedControls.txt @@ -0,0 +1,13 @@ +// + +using Avalonia.Controls; + +namespace Sample.App +{ + partial class SampleView + { + internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl("UserNameTextBox"); + internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindControl("PasswordTextBox"); + internal global::Avalonia.Controls.Button SignUpButton => this.FindControl("SignUpButton"); + } +} diff --git a/src/Avalonia.NameGenerator.Tests/MiniCompilerTests.cs b/src/Avalonia.NameGenerator.Tests/MiniCompilerTests.cs index ea9be2c1e0..2b59e8227a 100644 --- a/src/Avalonia.NameGenerator.Tests/MiniCompilerTests.cs +++ b/src/Avalonia.NameGenerator.Tests/MiniCompilerTests.cs @@ -1,9 +1,9 @@ using System; using System.ComponentModel; -using Avalonia.Controls; +using Avalonia.NameGenerator.Compiler; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Avalonia.NameGenerator.Infrastructure; +using Avalonia.NameGenerator.Tests.Views; using XamlX; using XamlX.Parsers; using Xunit; @@ -41,19 +41,15 @@ namespace Avalonia.NameGenerator.Tests public void Should_Resolve_Types_From_Simple_Avalonia_Markup() { var xaml = XDocumentXamlParser.Parse(AvaloniaXaml); - var compilation = CreateAvaloniaCompilation(); + var compilation = View.CreateAvaloniaCompilation(); MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation)).Transform(xaml); Assert.NotNull(xaml.Root); } - private static CSharpCompilation CreateAvaloniaCompilation(string name = "AvaloniaCompilation") => - CreateBasicCompilation(string.Empty, name) - .AddReferences(MetadataReference.CreateFromFile(typeof(TextBlock).Assembly.Location)); - - private static CSharpCompilation CreateBasicCompilation(string source, string name = "BasicCompilation") => + private static CSharpCompilation CreateBasicCompilation(string source) => CSharpCompilation - .Create(name, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) + .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)) diff --git a/src/Avalonia.NameGenerator.Tests/NameResolverTests.cs b/src/Avalonia.NameGenerator.Tests/NameResolverTests.cs deleted file mode 100644 index 5b8c31cc29..0000000000 --- a/src/Avalonia.NameGenerator.Tests/NameResolverTests.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Controls; -using Avalonia.ReactiveUI; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Avalonia.NameGenerator.Infrastructure; -using Xunit; - -namespace Avalonia.NameGenerator.Tests -{ - public class NameResolverTests - { - private const string NamedControl = "NamedControl.xml"; - private const string NamedControls = "NamedControls.xml"; - private const string XNamedControl = "xNamedControl.xml"; - private const string XNamedControls = "xNamedControls.xml"; - private const string NoNamedControls = "NoNamedControls.xml"; - private const string CustomControls = "CustomControls.xml"; - private const string DataTemplates = "DataTemplates.xml"; - private const string SignUpView = "SignUpView.xml"; - private const string AttachedProps = "AttachedProps.xml"; - - [Theory] - [InlineData(NamedControl)] - [InlineData(XNamedControl)] - [InlineData(AttachedProps)] - public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Control(string resource) - { - var xaml = await LoadEmbeddedResource(resource); - var compilation = CreateAvaloniaCompilation(); - var resolver = new NameResolver(compilation); - var controls = resolver.ResolveNames(xaml); - - Assert.NotEmpty(controls); - Assert.Equal(1, controls.Count); - Assert.Equal("UserNameTextBox", controls[0].Name); - Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName); - } - - [Theory] - [InlineData(NamedControls)] - [InlineData(XNamedControls)] - public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Controls(string resource) - { - var xaml = await LoadEmbeddedResource(resource); - var compilation = CreateAvaloniaCompilation(); - var resolver = new NameResolver(compilation); - var controls = resolver.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.Equal(typeof(TextBox).FullName, controls[0].TypeName); - Assert.Equal(typeof(TextBox).FullName, controls[1].TypeName); - Assert.Equal(typeof(Button).FullName, controls[2].TypeName); - } - - [Fact] - public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Custom_Controls() - { - var compilation = - CreateAvaloniaCompilation() - .AddSyntaxTrees( - CSharpSyntaxTree.ParseText( - "using Avalonia.Controls;" + - "namespace Controls {" + - " public class CustomTextBox : TextBox { }" + - " public class EvilControl { }" + - "}")); - - var xaml = await LoadEmbeddedResource(CustomControls); - var resolver = new NameResolver(compilation); - var controls = resolver.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.Equal(typeof(RoutedViewHost).FullName, controls[0].TypeName); - Assert.Equal(typeof(RoutedViewHost).FullName, controls[1].TypeName); - Assert.Equal("Controls.CustomTextBox", controls[2].TypeName); - } - - [Fact] - public async Task Should_Not_Resolve_Named_Controls_From_Avalonia_Markup_File_Without_Named_Controls() - { - var xaml = await LoadEmbeddedResource(NoNamedControls); - var compilation = CreateAvaloniaCompilation(); - var resolver = new NameResolver(compilation); - var controls = resolver.ResolveNames(xaml); - - Assert.Empty(controls); - } - - [Fact] - public async Task Should_Not_Resolve_Elements_From_DataTemplates() - { - var xaml = await LoadEmbeddedResource(DataTemplates); - var compilation = CreateAvaloniaCompilation(); - var resolver = new NameResolver(compilation); - var controls = resolver.ResolveNames(xaml); - - Assert.NotEmpty(controls); - Assert.Equal(2, controls.Count); - Assert.Equal("UserNameTextBox", controls[0].Name); - Assert.Equal("NamedListBox", controls[1].Name); - Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName); - Assert.Equal(typeof(ListBox).FullName, controls[1].TypeName); - } - - [Fact] - public async Task Should_Resolve_Names_From_Complex_Views() - { - var compilation = - CreateAvaloniaCompilation() - .AddSyntaxTrees( - CSharpSyntaxTree.ParseText( - "using Avalonia.Controls;" + - "namespace Controls {" + - " public class CustomTextBox : TextBox { }" + - "}")); - - var xaml = await LoadEmbeddedResource(SignUpView); - var resolver = new NameResolver(compilation); - var controls = resolver.ResolveNames(xaml); - - Assert.NotEmpty(controls); - Assert.Equal(9, 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("SignUpButton", controls[7].Name); - Assert.Equal("CompoundValidation", controls[8].Name); - } - - private static CSharpCompilation CreateAvaloniaCompilation(string name = "AvaloniaCompilation2") - { - var compilation = CSharpCompilation - .Create(name, 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")) - .Select(file => MetadataReference.CreateFromFile(file)) - .ToList(); - - return compilation.AddReferences(avaloniaAssemblyReferences); - } - - private static async Task LoadEmbeddedResource(string shortResourceName) - { - var assembly = typeof(NameResolverTests).Assembly; - var fullResourceName = assembly - .GetManifestResourceNames() - .First(name => name.EndsWith(shortResourceName)); - - await using var stream = assembly.GetManifestResourceStream(fullResourceName); - using var reader = new StreamReader(stream!); - return await reader.ReadToEndAsync(); - } - } -} \ No newline at end of file diff --git a/src/Avalonia.NameGenerator.Tests/Views/FieldModifier.xml b/src/Avalonia.NameGenerator.Tests/Views/FieldModifier.xml new file mode 100644 index 0000000000..5c7926777b --- /dev/null +++ b/src/Avalonia.NameGenerator.Tests/Views/FieldModifier.xml @@ -0,0 +1,27 @@ + + + + + + +