Browse Source
* Refactoring, decomposition, more unit tests * Support x:FieldModifier * Use Xamarin.Forms API for x:FieldModifier * Use <RestoreSources /> directive, bump Avalonia * CRLF dance * Use Fluent theme * Move Avalonia packages to Directory.Build.props * Bump test SDK versionpull/10407/head 0.1.9
committed by
GitHub
42 changed files with 684 additions and 370 deletions
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("UserNameTextBox"); |
||||
|
} |
||||
|
} |
||||
@ -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<string> 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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
internal global::Avalonia.ReactiveUI.RoutedViewHost ClrNamespaceRoutedViewHost => this.FindControl<global::Avalonia.ReactiveUI.RoutedViewHost>("ClrNamespaceRoutedViewHost"); |
||||
|
internal global::Avalonia.ReactiveUI.RoutedViewHost UriRoutedViewHost => this.FindControl<global::Avalonia.ReactiveUI.RoutedViewHost>("UriRoutedViewHost"); |
||||
|
internal global::Controls.CustomTextBox UserNameTextBox => this.FindControl<global::Controls.CustomTextBox>("UserNameTextBox"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("UserNameTextBox"); |
||||
|
internal global::Avalonia.Controls.ListBox NamedListBox => this.FindControl<global::Avalonia.Controls.ListBox>("NamedListBox"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
public global::Avalonia.Controls.TextBox FirstNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("FirstNameTextBox"); |
||||
|
public global::Avalonia.Controls.TextBox LastNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("LastNameTextBox"); |
||||
|
protected global::Avalonia.Controls.TextBox PasswordTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("PasswordTextBox"); |
||||
|
private global::Avalonia.Controls.TextBox ConfirmPasswordTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("ConfirmPasswordTextBox"); |
||||
|
internal global::Avalonia.Controls.Button SignUpButton => this.FindControl<global::Avalonia.Controls.Button>("SignUpButton"); |
||||
|
internal global::Avalonia.Controls.Button RegisterButton => this.FindControl<global::Avalonia.Controls.Button>("RegisterButton"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("UserNameTextBox"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("UserNameTextBox"); |
||||
|
internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("PasswordTextBox"); |
||||
|
internal global::Avalonia.Controls.Button SignUpButton => this.FindControl<global::Avalonia.Controls.Button>("SignUpButton"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
internal global::Controls.CustomTextBox UserNameTextBox => this.FindControl<global::Controls.CustomTextBox>("UserNameTextBox"); |
||||
|
internal global::Avalonia.Controls.TextBlock UserNameValidation => this.FindControl<global::Avalonia.Controls.TextBlock>("UserNameValidation"); |
||||
|
internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("PasswordTextBox"); |
||||
|
internal global::Avalonia.Controls.TextBlock PasswordValidation => this.FindControl<global::Avalonia.Controls.TextBlock>("PasswordValidation"); |
||||
|
internal global::Avalonia.Controls.ListBox AwesomeListView => this.FindControl<global::Avalonia.Controls.ListBox>("AwesomeListView"); |
||||
|
internal global::Avalonia.Controls.TextBox ConfirmPasswordTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("ConfirmPasswordTextBox"); |
||||
|
internal global::Avalonia.Controls.TextBlock ConfirmPasswordValidation => this.FindControl<global::Avalonia.Controls.TextBlock>("ConfirmPasswordValidation"); |
||||
|
internal global::Avalonia.Controls.Button SignUpButton => this.FindControl<global::Avalonia.Controls.Button>("SignUpButton"); |
||||
|
internal global::Avalonia.Controls.TextBlock CompoundValidation => this.FindControl<global::Avalonia.Controls.TextBlock>("CompoundValidation"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("UserNameTextBox"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
// <auto-generated /> |
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Sample.App |
||||
|
{ |
||||
|
partial class SampleView |
||||
|
{ |
||||
|
internal global::Avalonia.Controls.TextBox UserNameTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("UserNameTextBox"); |
||||
|
internal global::Avalonia.Controls.TextBox PasswordTextBox => this.FindControl<global::Avalonia.Controls.TextBox>("PasswordTextBox"); |
||||
|
internal global::Avalonia.Controls.Button SignUpButton => this.FindControl<global::Avalonia.Controls.Button>("SignUpButton"); |
||||
|
} |
||||
|
} |
||||
@ -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<string> 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(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,27 @@ |
|||||
|
<Window xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<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> |
||||
@ -0,0 +1,67 @@ |
|||||
|
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.NameGenerator.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 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() |
||||
|
{ |
||||
|
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")) |
||||
|
.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 { }" + |
||||
|
"}")); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,121 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.NameGenerator.Resolver; |
||||
|
using Avalonia.ReactiveUI; |
||||
|
using Avalonia.NameGenerator.Tests.Views; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.NameGenerator.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 compilation = View.CreateAvaloniaCompilation(); |
||||
|
var resolver = new XamlXNameResolver(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(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 compilation = View.CreateAvaloniaCompilation(); |
||||
|
var resolver = new XamlXNameResolver(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 = |
||||
|
View.CreateAvaloniaCompilation() |
||||
|
.WithCustomTextBox(); |
||||
|
|
||||
|
var xaml = await View.Load(View.CustomControls); |
||||
|
var resolver = new XamlXNameResolver(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 View.Load(View.NoNamedControls); |
||||
|
var compilation = View.CreateAvaloniaCompilation(); |
||||
|
var resolver = new XamlXNameResolver(compilation); |
||||
|
var controls = resolver.ResolveNames(xaml); |
||||
|
|
||||
|
Assert.Empty(controls); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Not_Resolve_Elements_From_DataTemplates() |
||||
|
{ |
||||
|
var xaml = await View.Load(View.DataTemplates); |
||||
|
var compilation = View.CreateAvaloniaCompilation(); |
||||
|
var resolver = new XamlXNameResolver(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 = |
||||
|
View.CreateAvaloniaCompilation() |
||||
|
.WithCustomTextBox(); |
||||
|
|
||||
|
var xaml = await View.Load(View.SignUpView); |
||||
|
var resolver = new XamlXNameResolver(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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,7 +1,7 @@ |
|||||
using XamlX.Ast; |
using XamlX.Ast; |
||||
using XamlX.Transform; |
using XamlX.Transform; |
||||
|
|
||||
namespace Avalonia.NameGenerator.Infrastructure |
namespace Avalonia.NameGenerator.Compiler |
||||
{ |
{ |
||||
internal class DataTemplateTransformer : IXamlAstTransformer |
internal class DataTemplateTransformer : IXamlAstTransformer |
||||
{ |
{ |
||||
@ -1,12 +1,11 @@ |
|||||
using System; |
using System; |
||||
using System.Collections.Generic; |
|
||||
using XamlX.Compiler; |
using XamlX.Compiler; |
||||
using XamlX.Emit; |
using XamlX.Emit; |
||||
using XamlX.Transform; |
using XamlX.Transform; |
||||
using XamlX.Transform.Transformers; |
using XamlX.Transform.Transformers; |
||||
using XamlX.TypeSystem; |
using XamlX.TypeSystem; |
||||
|
|
||||
namespace Avalonia.NameGenerator.Infrastructure |
namespace Avalonia.NameGenerator.Compiler |
||||
{ |
{ |
||||
internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult> |
internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult> |
||||
{ |
{ |
||||
@ -0,0 +1,29 @@ |
|||||
|
using XamlX; |
||||
|
using XamlX.Ast; |
||||
|
using XamlX.Transform; |
||||
|
|
||||
|
namespace Avalonia.NameGenerator.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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,9 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
|
|
||||
namespace Avalonia.NameGenerator.Infrastructure |
|
||||
{ |
|
||||
internal interface INameResolver |
|
||||
{ |
|
||||
IReadOnlyList<(string TypeName, string Name)> ResolveNames(string xaml); |
|
||||
} |
|
||||
} |
|
||||
@ -1,29 +0,0 @@ |
|||||
using XamlX; |
|
||||
using XamlX.Ast; |
|
||||
using XamlX.Transform; |
|
||||
|
|
||||
namespace Avalonia.NameGenerator.Infrastructure |
|
||||
{ |
|
||||
internal class NameDirectiveTransformer : IXamlAstTransformer |
|
||||
{ |
|
||||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
|
||||
{ |
|
||||
if (node is XamlAstObjectNode objectNode) |
|
||||
{ |
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,54 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using XamlX.Ast; |
|
||||
|
|
||||
namespace Avalonia.NameGenerator.Infrastructure |
|
||||
{ |
|
||||
internal sealed class NameReceiver : IXamlAstVisitor |
|
||||
{ |
|
||||
private readonly List<(string TypeName, string Name)> _items = new List<(string TypeName, string Name)>(); |
|
||||
|
|
||||
public IReadOnlyList<(string TypeName, string Name)> Controls => _items; |
|
||||
|
|
||||
public IXamlAstNode Visit(IXamlAstNode node) |
|
||||
{ |
|
||||
if (node is XamlAstObjectNode objectNode) |
|
||||
{ |
|
||||
var clrType = objectNode.Type.GetClrType(); |
|
||||
var isAvaloniaControl = clrType |
|
||||
.Interfaces |
|
||||
.Any(abstraction => abstraction.IsInterface && |
|
||||
abstraction.FullName == "Avalonia.Controls.IControl"); |
|
||||
|
|
||||
if (!isAvaloniaControl) |
|
||||
{ |
|
||||
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 typeNamePair = ($@"{clrType.Namespace}.{clrType.Name}", text.Text); |
|
||||
if (!_items.Contains(typeNamePair)) |
|
||||
{ |
|
||||
_items.Add(typeNamePair); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return node; |
|
||||
} |
|
||||
|
|
||||
return node; |
|
||||
} |
|
||||
|
|
||||
public void Push(IXamlAstNode node) { } |
|
||||
|
|
||||
public void Pop() { } |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
using System.Collections.Generic; |
|
||||
using Microsoft.CodeAnalysis.CSharp; |
|
||||
using XamlX; |
|
||||
using XamlX.Parsers; |
|
||||
|
|
||||
namespace Avalonia.NameGenerator.Infrastructure |
|
||||
{ |
|
||||
internal class NameResolver : INameResolver |
|
||||
{ |
|
||||
private const string AvaloniaXmlnsAttribute = "Avalonia.Metadata.XmlnsDefinitionAttribute"; |
|
||||
private readonly CSharpCompilation _compilation; |
|
||||
|
|
||||
public NameResolver(CSharpCompilation compilation) => _compilation = compilation; |
|
||||
|
|
||||
public IReadOnlyList<(string TypeName, string Name)> ResolveNames(string xaml) |
|
||||
{ |
|
||||
var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string> |
|
||||
{ |
|
||||
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008} |
|
||||
}); |
|
||||
|
|
||||
MiniCompiler |
|
||||
.CreateDefault(new RoslynTypeSystem(_compilation), AvaloniaXmlnsAttribute) |
|
||||
.Transform(parsed); |
|
||||
|
|
||||
var visitor = new NameReceiver(); |
|
||||
parsed.Root.Visit(visitor); |
|
||||
parsed.Root.VisitChildren(visitor); |
|
||||
return visitor.Controls; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,31 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Avalonia.NameGenerator.Resolver |
||||
|
{ |
||||
|
internal class FindControlNameGenerator : INameGenerator |
||||
|
{ |
||||
|
public string GenerateNames(string className, string nameSpace, IEnumerable<ResolvedName> names) |
||||
|
{ |
||||
|
var namedControls = names |
||||
|
.Select(info => " " + |
||||
|
$"{info.FieldModifier} global::{info.TypeName} {info.Name} => " + |
||||
|
$"this.FindControl<global::{info.TypeName}>(\"{info.Name}\");") |
||||
|
.ToList(); |
||||
|
var lines = string.Join("\n", namedControls); |
||||
|
return $@"// <auto-generated />
|
||||
|
|
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace {nameSpace} |
||||
|
{{ |
||||
|
partial class {className} |
||||
|
{{ |
||||
|
{lines} |
||||
|
}} |
||||
|
}} |
||||
|
";
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.NameGenerator.Resolver |
||||
|
{ |
||||
|
internal interface INameGenerator |
||||
|
{ |
||||
|
string GenerateNames(string className, string nameSpace, IEnumerable<ResolvedName> names); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.NameGenerator.Resolver |
||||
|
{ |
||||
|
internal interface INameResolver |
||||
|
{ |
||||
|
IReadOnlyList<ResolvedName> ResolveNames(string xaml); |
||||
|
} |
||||
|
|
||||
|
internal class ResolvedName |
||||
|
{ |
||||
|
public string TypeName { get; } |
||||
|
public string Name { get; } |
||||
|
public string FieldModifier { get; } |
||||
|
|
||||
|
public ResolvedName(string typeName, string name, string fieldModifier) |
||||
|
{ |
||||
|
TypeName = typeName; |
||||
|
Name = name; |
||||
|
FieldModifier = fieldModifier; |
||||
|
} |
||||
|
|
||||
|
public override bool Equals(object obj) |
||||
|
{ |
||||
|
if (obj is not ResolvedName name) |
||||
|
return false; |
||||
|
return name.Name == Name && |
||||
|
name.TypeName == TypeName && |
||||
|
name.FieldModifier == FieldModifier; |
||||
|
} |
||||
|
|
||||
|
public override int GetHashCode() |
||||
|
{ |
||||
|
unchecked |
||||
|
{ |
||||
|
var hashCode = TypeName != null ? TypeName.GetHashCode() : 0; |
||||
|
hashCode = (hashCode * 397) ^ (Name != null ? Name.GetHashCode() : 0); |
||||
|
hashCode = (hashCode * 397) ^ (FieldModifier != null ? FieldModifier.GetHashCode() : 0); |
||||
|
return hashCode; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.NameGenerator.Compiler; |
||||
|
using Microsoft.CodeAnalysis.CSharp; |
||||
|
using XamlX; |
||||
|
using XamlX.Ast; |
||||
|
using XamlX.Parsers; |
||||
|
|
||||
|
namespace Avalonia.NameGenerator.Resolver |
||||
|
{ |
||||
|
internal class XamlXNameResolver : INameResolver, IXamlAstVisitor |
||||
|
{ |
||||
|
private const string AvaloniaXmlnsAttribute = "Avalonia.Metadata.XmlnsDefinitionAttribute"; |
||||
|
private readonly List<ResolvedName> _items = new(); |
||||
|
private readonly MiniCompiler _compiler; |
||||
|
|
||||
|
public XamlXNameResolver(CSharpCompilation compilation) => |
||||
|
_compiler = MiniCompiler |
||||
|
.CreateDefault( |
||||
|
new RoslynTypeSystem(compilation), |
||||
|
AvaloniaXmlnsAttribute); |
||||
|
|
||||
|
public IReadOnlyList<ResolvedName> ResolveNames(string xaml) |
||||
|
{ |
||||
|
var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string> |
||||
|
{ |
||||
|
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008} |
||||
|
}); |
||||
|
|
||||
|
_compiler.Transform(parsed); |
||||
|
parsed.Root.Visit(this); |
||||
|
parsed.Root.VisitChildren(this); |
||||
|
return _items; |
||||
|
} |
||||
|
|
||||
|
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node) |
||||
|
{ |
||||
|
if (node is not XamlAstObjectNode objectNode) |
||||
|
return node; |
||||
|
|
||||
|
var clrType = objectNode.Type.GetClrType(); |
||||
|
var isAvaloniaControl = clrType |
||||
|
.Interfaces |
||||
|
.Any(abstraction => abstraction.IsInterface && |
||||
|
abstraction.FullName == "Avalonia.Controls.IControl"); |
||||
|
|
||||
|
if (!isAvaloniaControl) |
||||
|
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 resolvedName = new ResolvedName(typeName, text.Text, fieldModifier); |
||||
|
if (_items.Contains(resolvedName)) |
||||
|
continue; |
||||
|
_items.Add(resolvedName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return node; |
||||
|
} |
||||
|
|
||||
|
void IXamlAstVisitor.Push(IXamlAstNode node) { } |
||||
|
|
||||
|
void IXamlAstVisitor.Pop() { } |
||||
|
|
||||
|
private static 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", |
||||
|
_ => "internal" |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue