Browse Source

refactor: Use file scoped namespaces (#75)

* Use file scoped namespaces everywhere
* Use .NET 6
pull/10407/head
Artyom V. Gorchakov 4 years ago
committed by GitHub
parent
commit
cb79cd8f8f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/ci-build.yml
  2. 21
      src/Avalonia.NameGenerator.Sandbox/App.xaml.cs
  3. 5
      src/Avalonia.NameGenerator.Sandbox/Avalonia.NameGenerator.Sandbox.csproj
  4. 8
      src/Avalonia.NameGenerator.Sandbox/Controls/CustomTextBox.cs
  5. 19
      src/Avalonia.NameGenerator.Sandbox/Program.cs
  6. 70
      src/Avalonia.NameGenerator.Sandbox/ViewModels/SignUpViewModel.cs
  7. 29
      src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml
  8. 67
      src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml.cs
  9. 2
      src/Avalonia.NameGenerator.Tests/Avalonia.NameGenerator.Tests.csproj
  10. 47
      src/Avalonia.NameGenerator.Tests/GlobPatternTests.cs
  11. 53
      src/Avalonia.NameGenerator.Tests/InitializeComponent/GeneratedInitializeComponent/InitializeComponentCode.cs
  12. 85
      src/Avalonia.NameGenerator.Tests/InitializeComponent/InitializeComponentTests.cs
  13. 91
      src/Avalonia.NameGenerator.Tests/MiniCompilerTests.cs
  14. 47
      src/Avalonia.NameGenerator.Tests/OnlyProperties/GeneratedCode/OnlyPropertiesCode.cs
  15. 69
      src/Avalonia.NameGenerator.Tests/OnlyProperties/OnlyPropertiesTests.cs
  16. 105
      src/Avalonia.NameGenerator.Tests/Views/View.cs
  17. 53
      src/Avalonia.NameGenerator.Tests/XamlXClassResolverTests.cs
  18. 215
      src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs
  19. 21
      src/Avalonia.NameGenerator/Compiler/DataTemplateTransformer.cs
  20. 63
      src/Avalonia.NameGenerator/Compiler/MiniCompiler.cs
  21. 37
      src/Avalonia.NameGenerator/Compiler/NameDirectiveTransformer.cs
  22. 391
      src/Avalonia.NameGenerator/Compiler/RoslynTypeSystem.cs
  23. 9
      src/Avalonia.NameGenerator/Domain/ICodeGenerator.cs
  24. 9
      src/Avalonia.NameGenerator/Domain/IGlobPattern.cs
  25. 13
      src/Avalonia.NameGenerator/Domain/INameGenerator.cs
  26. 13
      src/Avalonia.NameGenerator/Domain/INameResolver.cs
  27. 14
      src/Avalonia.NameGenerator/Domain/IViewResolver.cs
  28. 7
      src/Avalonia.NameGenerator/Domain/IsExternalInit.cs
  29. 133
      src/Avalonia.NameGenerator/Generator.cs
  30. 81
      src/Avalonia.NameGenerator/Generator/AvaloniaNameGenerator.cs
  31. 23
      src/Avalonia.NameGenerator/Generator/GlobPattern.cs
  32. 19
      src/Avalonia.NameGenerator/Generator/GlobPatternGroup.cs
  33. 65
      src/Avalonia.NameGenerator/Generator/InitializeComponentCodeGenerator.cs
  34. 23
      src/Avalonia.NameGenerator/Generator/OnlyPropertiesCodeGenerator.cs
  35. 141
      src/Avalonia.NameGenerator/Generator/XamlXNameResolver.cs
  36. 131
      src/Avalonia.NameGenerator/Generator/XamlXViewResolver.cs
  37. 47
      src/Avalonia.NameGenerator/GeneratorContextExtensions.cs
  38. 135
      src/Avalonia.NameGenerator/GeneratorOptions.cs

2
.github/workflows/ci-build.yml

@ -26,7 +26,7 @@ jobs:
- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.301
dotnet-version: 6.0.101
- name: NBGV
id: nbgv

21
src/Avalonia.NameGenerator.Sandbox/App.xaml.cs

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

5
src/Avalonia.NameGenerator.Sandbox/Avalonia.NameGenerator.Sandbox.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5</TargetFramework>
<TargetFramework>net6</TargetFramework>
<LangVersion>preview</LangVersion>
<IsPackable>false</IsPackable>
<InstallAvalonia>true</InstallAvalonia>
@ -19,4 +19,7 @@
<ItemGroup>
<ProjectReference Include="..\Avalonia.NameGenerator\Avalonia.NameGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
</ItemGroup>
</Project>

8
src/Avalonia.NameGenerator.Sandbox/Controls/CustomTextBox.cs

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

19
src/Avalonia.NameGenerator.Sandbox/Program.cs

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

70
src/Avalonia.NameGenerator.Sandbox/ViewModels/SignUpViewModel.cs

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

29
src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml

@ -2,36 +2,23 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Avalonia.NameGenerator.Sandbox.Controls"
x:Class="Avalonia.NameGenerator.Sandbox.Views.SignUpView">
<StackPanel>
<!-- Regular name directives. -->
<StackPanel Margin="10">
<TextBlock Text="Sign Up" />
<controls:CustomTextBox Margin="0 10 0 0"
Name="UserNameTextBox"
Watermark="Please, enter user name..."
UseFloatingWatermark="True" />
<TextBlock Name="UserNameValidation"
x:Name="UserNameTextBox"
Watermark="Please, enter user name..."
UseFloatingWatermark="True" />
<TextBlock x:Name="UserNameValidation"
Foreground="Red"
x:FieldModifier="public"
FontSize="12" />
<TextBox Margin="0 10 0 0"
Name="PasswordTextBox"
x:FieldModifier="private"
x:Name="PasswordTextBox"
Watermark="Please, enter your password..."
UseFloatingWatermark="True"
PasswordChar="*" />
<TextBlock Name="PasswordValidation"
<TextBlock x:Name="PasswordValidation"
Foreground="Red"
FontSize="12" />
<!-- DataTemplates should be removed. -->
<ListBox x:Name="AwesomeListView">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="MeaningLessName" Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- x:Name directives. -->
<TextBox Margin="0 10 0 0"
x:Name="ConfirmPasswordTextBox"
Watermark="Please, confirm the password..."

67
src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml.cs

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

2
src/Avalonia.NameGenerator.Tests/Avalonia.NameGenerator.Tests.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5</TargetFramework>
<TargetFramework>net6</TargetFramework>
<LangVersion>preview</LangVersion>
<IsPackable>false</IsPackable>
<InstallAvalonia>true</InstallAvalonia>

47
src/Avalonia.NameGenerator.Tests/GlobPatternTests.cs

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

53
src/Avalonia.NameGenerator.Tests/InitializeComponent/GeneratedInitializeComponent/InitializeComponentCode.cs

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

85
src/Avalonia.NameGenerator.Tests/InitializeComponent/InitializeComponentTests.cs

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

91
src/Avalonia.NameGenerator.Tests/MiniCompilerTests.cs

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

47
src/Avalonia.NameGenerator.Tests/OnlyProperties/GeneratedCode/OnlyPropertiesCode.cs

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

69
src/Avalonia.NameGenerator.Tests/OnlyProperties/OnlyPropertiesTests.cs

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

105
src/Avalonia.NameGenerator.Tests/Views/View.cs

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

53
src/Avalonia.NameGenerator.Tests/XamlXClassResolverTests.cs

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

215
src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs

@ -8,116 +8,115 @@ using Avalonia.ReactiveUI;
using Avalonia.NameGenerator.Tests.Views;
using Xunit;
namespace Avalonia.NameGenerator.Tests
namespace Avalonia.NameGenerator.Tests;
public class XamlXNameResolverTests
{
public class XamlXNameResolverTests
[Theory]
[InlineData(View.NamedControl)]
[InlineData(View.XNamedControl)]
[InlineData(View.AttachedProps)]
public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Control(string resource)
{
var xaml = await View.Load(resource);
var controls = ResolveNames(xaml);
Assert.NotEmpty(controls);
Assert.Equal(1, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.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 controls = ResolveNames(xaml);
Assert.NotEmpty(controls);
Assert.Equal(3, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.Equal("PasswordTextBox", controls[1].Name);
Assert.Equal("SignUpButton", controls[2].Name);
Assert.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()
{
[Theory]
[InlineData(View.NamedControl)]
[InlineData(View.XNamedControl)]
[InlineData(View.AttachedProps)]
public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Control(string resource)
{
var xaml = await View.Load(resource);
var controls = ResolveNames(xaml);
Assert.NotEmpty(controls);
Assert.Equal(1, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.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 controls = ResolveNames(xaml);
Assert.NotEmpty(controls);
Assert.Equal(3, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.Equal("PasswordTextBox", controls[1].Name);
Assert.Equal("SignUpButton", controls[2].Name);
Assert.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 xaml = await View.Load(View.CustomControls);
var controls = ResolveNames(xaml);
Assert.NotEmpty(controls);
Assert.Equal(3, controls.Count);
Assert.Equal("ClrNamespaceRoutedViewHost", controls[0].Name);
Assert.Equal("UriRoutedViewHost", controls[1].Name);
Assert.Equal("UserNameTextBox", controls[2].Name);
Assert.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 controls = ResolveNames(xaml);
Assert.Empty(controls);
}
[Fact]
public async Task Should_Not_Resolve_Elements_From_DataTemplates()
{
var xaml = await View.Load(View.DataTemplates);
var controls = ResolveNames(xaml);
Assert.NotEmpty(controls);
Assert.Equal(2, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.Equal("NamedListBox", controls[1].Name);
Assert.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 xaml = await View.Load(View.SignUpView);
var controls = 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 IReadOnlyList<ResolvedName> ResolveNames(string xaml)
{
var compilation =
View.CreateAvaloniaCompilation()
.WithCustomTextBox();
var classResolver = new XamlXViewResolver(
var xaml = await View.Load(View.CustomControls);
var controls = ResolveNames(xaml);
Assert.NotEmpty(controls);
Assert.Equal(3, controls.Count);
Assert.Equal("ClrNamespaceRoutedViewHost", controls[0].Name);
Assert.Equal("UriRoutedViewHost", controls[1].Name);
Assert.Equal("UserNameTextBox", controls[2].Name);
Assert.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 controls = ResolveNames(xaml);
Assert.Empty(controls);
}
[Fact]
public async Task Should_Not_Resolve_Elements_From_DataTemplates()
{
var xaml = await View.Load(View.DataTemplates);
var controls = ResolveNames(xaml);
Assert.NotEmpty(controls);
Assert.Equal(2, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.Equal("NamedListBox", controls[1].Name);
Assert.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 xaml = await View.Load(View.SignUpView);
var controls = 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 IReadOnlyList<ResolvedName> ResolveNames(string xaml)
{
var compilation =
View.CreateAvaloniaCompilation()
.WithCustomTextBox();
var classResolver = new XamlXViewResolver(
new RoslynTypeSystem(compilation),
MiniCompiler.CreateDefault(
new RoslynTypeSystem(compilation),
MiniCompiler.CreateDefault(
new RoslynTypeSystem(compilation),
MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
var classInfo = classResolver.ResolveView(xaml);
var nameResolver = new XamlXNameResolver();
return nameResolver.ResolveNames(classInfo.Xaml);
}
MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
var classInfo = classResolver.ResolveView(xaml);
var nameResolver = new XamlXNameResolver();
return nameResolver.ResolveNames(classInfo.Xaml);
}
}

21
src/Avalonia.NameGenerator/Compiler/DataTemplateTransformer.cs

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

63
src/Avalonia.NameGenerator/Compiler/MiniCompiler.cs

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

37
src/Avalonia.NameGenerator/Compiler/NameDirectiveTransformer.cs

@ -2,28 +2,27 @@
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.NameGenerator.Compiler
namespace Avalonia.NameGenerator.Compiler;
internal class NameDirectiveTransformer : IXamlAstTransformer
{
internal class NameDirectiveTransformer : IXamlAstTransformer
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
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);
}
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;
}
}

391
src/Avalonia.NameGenerator/Compiler/RoslynTypeSystem.cs

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

9
src/Avalonia.NameGenerator/Domain/ICodeGenerator.cs

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

9
src/Avalonia.NameGenerator/Domain/IGlobPattern.cs

@ -1,7 +1,6 @@
namespace Avalonia.NameGenerator.Domain
namespace Avalonia.NameGenerator.Domain;
public interface IGlobPattern
{
public interface IGlobPattern
{
bool Matches(string str);
}
bool Matches(string str);
}

13
src/Avalonia.NameGenerator/Domain/INameGenerator.cs

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

13
src/Avalonia.NameGenerator/Domain/INameResolver.cs

@ -1,12 +1,11 @@
using System.Collections.Generic;
using XamlX.Ast;
namespace Avalonia.NameGenerator.Domain
namespace Avalonia.NameGenerator.Domain;
internal interface INameResolver
{
internal interface INameResolver
{
IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml);
}
IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml);
}
internal record ResolvedName(string TypeName, string Name, string FieldModifier);
}
internal record ResolvedName(string TypeName, string Name, string FieldModifier);

14
src/Avalonia.NameGenerator/Domain/IViewResolver.cs

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

7
src/Avalonia.NameGenerator/Domain/IsExternalInit.cs

@ -1,5 +1,4 @@
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
}
namespace System.Runtime.CompilerServices;
internal static class IsExternalInit { }

133
src/Avalonia.NameGenerator/Generator.cs

@ -8,79 +8,78 @@ using Microsoft.CodeAnalysis.CSharp;
[assembly: InternalsVisibleTo("Avalonia.NameGenerator.Tests")]
namespace Avalonia.NameGenerator
namespace Avalonia.NameGenerator;
[Generator]
public class AvaloniaNameSourceGenerator : ISourceGenerator
{
[Generator]
public class AvaloniaNameSourceGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context) { }
public void Initialize(GeneratorInitializationContext context) { }
public void Execute(GeneratorExecutionContext context)
public void Execute(GeneratorExecutionContext context)
{
try
{
try
{
var generator = CreateNameGenerator(context);
var partials = generator.GenerateNameReferences(context.AdditionalFiles);
foreach (var partial in partials) context.AddSource(partial.FileName, partial.Content);
}
catch (Exception exception)
{
ReportUnhandledError(context, exception);
}
var generator = CreateNameGenerator(context);
var partials = generator.GenerateNameReferences(context.AdditionalFiles);
foreach (var partial in partials) context.AddSource(partial.FileName, partial.Content);
}
private static INameGenerator CreateNameGenerator(GeneratorExecutionContext context)
catch (Exception exception)
{
var options = new GeneratorOptions(context);
var types = new RoslynTypeSystem((CSharpCompilation)context.Compilation);
var defaultFieldModifier = options.AvaloniaNameGeneratorDefaultFieldModifier.ToString().ToLowerInvariant();
ICodeGenerator generator = options.AvaloniaNameGeneratorBehavior switch {
Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(),
Behavior.InitializeComponent => new InitializeComponentCodeGenerator(types),
_ => throw new ArgumentOutOfRangeException()
};
var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
return new AvaloniaNameGenerator(
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath),
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace),
new XamlXViewResolver(types, compiler, true, type => ReportInvalidType(context, type)),
new XamlXNameResolver(defaultFieldModifier),
generator);
ReportUnhandledError(context, exception);
}
}
private static void ReportUnhandledError(GeneratorExecutionContext context, Exception error)
{
const string message =
"Unhandled exception occured while generating typed Name references. " +
"Please file an issue: https://github.com/avaloniaui/avalonia.namegenerator";
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"AXN0002",
message,
error.ToString(),
"Usage",
DiagnosticSeverity.Error,
true),
Location.None));
}
private static INameGenerator CreateNameGenerator(GeneratorExecutionContext context)
{
var options = new GeneratorOptions(context);
var types = new RoslynTypeSystem((CSharpCompilation)context.Compilation);
var defaultFieldModifier = options.AvaloniaNameGeneratorDefaultFieldModifier.ToString().ToLowerInvariant();
ICodeGenerator generator = options.AvaloniaNameGeneratorBehavior switch {
Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(),
Behavior.InitializeComponent => new InitializeComponentCodeGenerator(types),
_ => throw new ArgumentOutOfRangeException()
};
private static void ReportInvalidType(GeneratorExecutionContext context, string typeName)
{
var message =
$"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " +
$"The type '{typeName}' does not exist in the assembly.";
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"AXN0001",
message,
message,
"Usage",
DiagnosticSeverity.Error,
true),
Location.None));
}
var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
return new AvaloniaNameGenerator(
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath),
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace),
new XamlXViewResolver(types, compiler, true, type => ReportInvalidType(context, type)),
new XamlXNameResolver(defaultFieldModifier),
generator);
}
private static void ReportUnhandledError(GeneratorExecutionContext context, Exception error)
{
const string message =
"Unhandled exception occured while generating typed Name references. " +
"Please file an issue: https://github.com/avaloniaui/avalonia.namegenerator";
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"AXN0002",
message,
error.ToString(),
"Usage",
DiagnosticSeverity.Error,
true),
Location.None));
}
private static void ReportInvalidType(GeneratorExecutionContext context, string typeName)
{
var message =
$"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " +
$"The type '{typeName}' does not exist in the assembly.";
context.ReportDiagnostic(
Diagnostic.Create(
new DiagnosticDescriptor(
"AXN0001",
message,
message,
"Usage",
DiagnosticSeverity.Error,
true),
Location.None));
}
}
}

81
src/Avalonia.NameGenerator/Generator/AvaloniaNameGenerator.cs

@ -3,51 +3,50 @@ using System.Linq;
using Avalonia.NameGenerator.Domain;
using Microsoft.CodeAnalysis;
namespace Avalonia.NameGenerator.Generator
namespace Avalonia.NameGenerator.Generator;
internal class AvaloniaNameGenerator : INameGenerator
{
internal class AvaloniaNameGenerator : INameGenerator
{
private readonly IGlobPattern _pathPattern;
private readonly IGlobPattern _namespacePattern;
private readonly IViewResolver _classes;
private readonly INameResolver _names;
private readonly ICodeGenerator _code;
private readonly IGlobPattern _pathPattern;
private readonly IGlobPattern _namespacePattern;
private readonly IViewResolver _classes;
private readonly INameResolver _names;
private readonly ICodeGenerator _code;
public AvaloniaNameGenerator(
IGlobPattern pathPattern,
IGlobPattern namespacePattern,
IViewResolver classes,
INameResolver names,
ICodeGenerator code)
{
_pathPattern = pathPattern;
_namespacePattern = namespacePattern;
_classes = classes;
_names = names;
_code = code;
}
public AvaloniaNameGenerator(
IGlobPattern pathPattern,
IGlobPattern namespacePattern,
IViewResolver classes,
INameResolver names,
ICodeGenerator code)
{
_pathPattern = pathPattern;
_namespacePattern = namespacePattern;
_classes = classes;
_names = names;
_code = code;
}
public IReadOnlyList<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles)
{
var resolveViews =
from file in additionalFiles
where (file.Path.EndsWith(".xaml") ||
file.Path.EndsWith(".paml") ||
file.Path.EndsWith(".axaml")) &&
_pathPattern.Matches(file.Path)
let xaml = file.GetText()!.ToString()
let view = _classes.ResolveView(xaml)
where view != null && _namespacePattern.Matches(view.Namespace)
select view;
public IReadOnlyList<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles)
{
var resolveViews =
from file in additionalFiles
where (file.Path.EndsWith(".xaml") ||
file.Path.EndsWith(".paml") ||
file.Path.EndsWith(".axaml")) &&
_pathPattern.Matches(file.Path)
let xaml = file.GetText()!.ToString()
let view = _classes.ResolveView(xaml)
where view != null && _namespacePattern.Matches(view.Namespace)
select view;
var query =
from view in resolveViews
let names = _names.ResolveNames(view.Xaml)
let code = _code.GenerateCode(view.ClassName, view.Namespace, view.XamlType, names)
let fileName = $"{view.ClassName}.g.cs"
select new GeneratedPartialClass(fileName, code);
var query =
from view in resolveViews
let names = _names.ResolveNames(view.Xaml)
let code = _code.GenerateCode(view.ClassName, view.Namespace, view.XamlType, names)
let fileName = $"{view.ClassName}.g.cs"
select new GeneratedPartialClass(fileName, code);
return query.ToList();
}
return query.ToList();
}
}

23
src/Avalonia.NameGenerator/Generator/GlobPattern.cs

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

19
src/Avalonia.NameGenerator/Generator/GlobPatternGroup.cs

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

65
src/Avalonia.NameGenerator/Generator/InitializeComponentCodeGenerator.cs

@ -2,12 +2,12 @@
using Avalonia.NameGenerator.Domain;
using XamlX.TypeSystem;
namespace Avalonia.NameGenerator.Generator
namespace Avalonia.NameGenerator.Generator;
internal class InitializeComponentCodeGenerator: ICodeGenerator
{
internal class InitializeComponentCodeGenerator: ICodeGenerator
{
private readonly bool _diagnosticsAreConnected;
private const string AttachDevToolsCodeBlock = @"
private readonly bool _diagnosticsAreConnected;
private const string AttachDevToolsCodeBlock = @"
#if DEBUG
if (attachDevTools)
{
@ -15,28 +15,28 @@ namespace Avalonia.NameGenerator.Generator
}
#endif
";
private const string AttachDevToolsParameterDocumentation
= @" /// <param name=""attachDevTools"">Should the dev tools be attached.</param>
private const string AttachDevToolsParameterDocumentation
= @" /// <param name=""attachDevTools"">Should the dev tools be attached.</param>
";
public InitializeComponentCodeGenerator(IXamlTypeSystem types)
{
_diagnosticsAreConnected = types.FindAssembly("Avalonia.Diagnostics") != null;
}
public InitializeComponentCodeGenerator(IXamlTypeSystem types)
{
_diagnosticsAreConnected = types.FindAssembly("Avalonia.Diagnostics") != null;
}
public string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names)
public string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names)
{
var properties = new List<string>();
var initializations = new List<string>();
foreach (var (typeName, name, fieldModifier) in names)
{
var properties = new List<string>();
var initializations = new List<string>();
foreach (var (typeName, name, fieldModifier) in names)
{
properties.Add($" {fieldModifier} global::{typeName} {name};");
initializations.Add($" {name} = this.FindControl<global::{typeName}>(\"{name}\");");
}
properties.Add($" {fieldModifier} global::{typeName} {name};");
initializations.Add($" {name} = this.FindControl<global::{typeName}>(\"{name}\");");
}
var attachDevTools = _diagnosticsAreConnected && IsWindow(xamlType);
var attachDevTools = _diagnosticsAreConnected && IsWindow(xamlType);
return $@"// <auto-generated />
return $@"// <auto-generated />
using Avalonia;
using Avalonia.Controls;
@ -65,19 +65,18 @@ namespace {nameSpace}
}}
}}
";
}
}
private static bool IsWindow(IXamlType xamlType)
private static bool IsWindow(IXamlType xamlType)
{
var type = xamlType;
bool isWindow;
do
{
var type = xamlType;
bool isWindow;
do
{
isWindow = type.FullName == "Avalonia.Controls.Window";
type = type.BaseType;
} while (!isWindow && type != null);
isWindow = type.FullName == "Avalonia.Controls.Window";
type = type.BaseType;
} while (!isWindow && type != null);
return isWindow;
}
return isWindow;
}
}
}

23
src/Avalonia.NameGenerator/Generator/OnlyPropertiesCodeGenerator.cs

@ -3,19 +3,19 @@ using System.Linq;
using Avalonia.NameGenerator.Domain;
using XamlX.TypeSystem;
namespace Avalonia.NameGenerator.Generator
namespace Avalonia.NameGenerator.Generator;
internal class OnlyPropertiesCodeGenerator : ICodeGenerator
{
internal class OnlyPropertiesCodeGenerator : ICodeGenerator
public string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names)
{
public string GenerateCode(string className, string nameSpace, IXamlType XamlType, 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 />
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;
@ -27,6 +27,5 @@ namespace {nameSpace}
}}
}}
";
}
}
}

141
src/Avalonia.NameGenerator/Generator/XamlXNameResolver.cs

@ -4,89 +4,88 @@ using Avalonia.NameGenerator.Domain;
using XamlX;
using XamlX.Ast;
namespace Avalonia.NameGenerator.Generator
namespace Avalonia.NameGenerator.Generator;
internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
{
internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
{
private readonly List<ResolvedName> _items = new();
private readonly string _defaultFieldModifier;
private readonly List<ResolvedName> _items = new();
private readonly string _defaultFieldModifier;
public XamlXNameResolver(string defaultFieldModifier = "internal")
{
_defaultFieldModifier = defaultFieldModifier;
}
public XamlXNameResolver(string defaultFieldModifier = "internal")
{
_defaultFieldModifier = defaultFieldModifier;
}
public IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml)
{
_items.Clear();
xaml.Root.Visit(this);
xaml.Root.VisitChildren(this);
return _items;
}
public IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml)
{
_items.Clear();
xaml.Root.Visit(this);
xaml.Root.VisitChildren(this);
return _items;
}
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
{
if (node is not XamlAstObjectNode objectNode)
return node;
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");
var clrType = objectNode.Type.GetClrType();
var isAvaloniaControl = clrType
.Interfaces
.Any(abstraction => abstraction.IsInterface &&
abstraction.FullName == "Avalonia.Controls.IControl");
if (!isAvaloniaControl)
return node;
if (!isAvaloniaControl)
return node;
foreach (var child in objectNode.Children)
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)
{
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);
}
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) { }
return node;
}
void IXamlAstVisitor.Push(IXamlAstNode node) { }
void IXamlAstVisitor.Pop() { }
void IXamlAstVisitor.Pop() { }
private string TryGetFieldModifier(XamlAstObjectNode objectNode)
{
// We follow Xamarin.Forms API behavior in terms of x:FieldModifier here:
// https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/field-modifiers
// However, by default we use 'internal' field modifier here for generated
// x:Name references for historical purposes and WPF compatibility.
//
var fieldModifierType = objectNode
.Children
.OfType<XamlAstXmlDirective>()
.Where(dir => dir.Name == "FieldModifier" && dir.Namespace == XamlNamespaces.Xaml2006)
.Select(dir => dir.Values[0])
.OfType<XamlAstTextNode>()
.Select(txt => txt.Text)
.FirstOrDefault();
private string TryGetFieldModifier(XamlAstObjectNode objectNode)
{
// We follow Xamarin.Forms API behavior in terms of x:FieldModifier here:
// https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/field-modifiers
// However, by default we use 'internal' field modifier here for generated
// x:Name references for historical purposes and WPF compatibility.
//
var fieldModifierType = objectNode
.Children
.OfType<XamlAstXmlDirective>()
.Where(dir => dir.Name == "FieldModifier" && dir.Namespace == XamlNamespaces.Xaml2006)
.Select(dir => dir.Values[0])
.OfType<XamlAstTextNode>()
.Select(txt => txt.Text)
.FirstOrDefault();
return fieldModifierType?.ToLowerInvariant() switch
{
"private" => "private",
"public" => "public",
"protected" => "protected",
"internal" => "internal",
"notpublic" => "internal",
_ => _defaultFieldModifier
};
}
return fieldModifierType?.ToLowerInvariant() switch
{
"private" => "private",
"public" => "public",
"protected" => "protected",
"internal" => "internal",
"notpublic" => "internal",
_ => _defaultFieldModifier
};
}
}
}

131
src/Avalonia.NameGenerator/Generator/XamlXViewResolver.cs

@ -7,89 +7,88 @@ using XamlX;
using XamlX.Ast;
using XamlX.Parsers;
namespace Avalonia.NameGenerator.Generator
namespace Avalonia.NameGenerator.Generator;
internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
{
internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
{
private readonly RoslynTypeSystem _typeSystem;
private readonly MiniCompiler _compiler;
private readonly bool _checkTypeValidity;
private readonly Action<string> _onTypeInvalid;
private readonly RoslynTypeSystem _typeSystem;
private readonly MiniCompiler _compiler;
private readonly bool _checkTypeValidity;
private readonly Action<string> _onTypeInvalid;
private ResolvedView _resolvedClass;
private XamlDocument _xaml;
private ResolvedView _resolvedClass;
private XamlDocument _xaml;
public XamlXViewResolver(
RoslynTypeSystem typeSystem,
MiniCompiler compiler,
bool checkTypeValidity = false,
Action<string> onTypeInvalid = null)
{
_checkTypeValidity = checkTypeValidity;
_onTypeInvalid = onTypeInvalid;
_typeSystem = typeSystem;
_compiler = compiler;
}
public XamlXViewResolver(
RoslynTypeSystem typeSystem,
MiniCompiler compiler,
bool checkTypeValidity = false,
Action<string> onTypeInvalid = null)
{
_checkTypeValidity = checkTypeValidity;
_onTypeInvalid = onTypeInvalid;
_typeSystem = typeSystem;
_compiler = compiler;
}
public ResolvedView ResolveView(string xaml)
public ResolvedView ResolveView(string xaml)
{
_resolvedClass = null;
_xaml = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
{
_resolvedClass = null;
_xaml = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
{
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
});
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
});
_compiler.Transform(_xaml);
_xaml.Root.Visit(this);
_xaml.Root.VisitChildren(this);
return _resolvedClass;
}
_compiler.Transform(_xaml);
_xaml.Root.Visit(this);
_xaml.Root.VisitChildren(this);
return _resolvedClass;
}
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
{
if (node is not XamlAstObjectNode objectNode)
return node;
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");
var clrType = objectNode.Type.GetClrType();
var isAvaloniaControl = clrType
.Interfaces
.Any(abstraction => abstraction.IsInterface &&
abstraction.FullName == "Avalonia.Controls.IControl");
if (!isAvaloniaControl)
return node;
if (!isAvaloniaControl)
return node;
foreach (var child in objectNode.Children)
foreach (var child in objectNode.Children)
{
if (child is XamlAstXmlDirective directive &&
directive.Name == "Class" &&
directive.Namespace == XamlNamespaces.Xaml2006 &&
directive.Values[0] is XamlAstTextNode text)
{
if (child is XamlAstXmlDirective directive &&
directive.Name == "Class" &&
directive.Namespace == XamlNamespaces.Xaml2006 &&
directive.Values[0] is XamlAstTextNode text)
if (_checkTypeValidity)
{
if (_checkTypeValidity)
var existingType = _typeSystem.FindType(text.Text);
if (existingType == null)
{
var existingType = _typeSystem.FindType(text.Text);
if (existingType == null)
{
_onTypeInvalid?.Invoke(text.Text);
return node;
}
_onTypeInvalid?.Invoke(text.Text);
return node;
}
}
var split = text.Text.Split('.');
var nameSpace = string.Join(".", split.Take(split.Length - 1));
var className = split.Last();
var split = text.Text.Split('.');
var nameSpace = string.Join(".", split.Take(split.Length - 1));
var className = split.Last();
_resolvedClass = new ResolvedView(className, clrType, nameSpace, _xaml);
return node;
}
_resolvedClass = new ResolvedView(className, clrType, nameSpace, _xaml);
return node;
}
return node;
}
void IXamlAstVisitor.Push(IXamlAstNode node) { }
void IXamlAstVisitor.Pop() { }
return node;
}
}
void IXamlAstVisitor.Push(IXamlAstNode node) { }
void IXamlAstVisitor.Pop() { }
}

47
src/Avalonia.NameGenerator/GeneratorContextExtensions.cs

@ -1,31 +1,30 @@
using System.Linq;
using Microsoft.CodeAnalysis;
namespace Avalonia.NameGenerator
{
internal static class GeneratorContextExtensions
{
private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup";
namespace Avalonia.NameGenerator;
public static string GetMSBuildProperty(
this GeneratorExecutionContext context,
string name,
string defaultValue = "")
{
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue($"build_property.{name}", out var value);
return value ?? defaultValue;
}
internal static class GeneratorContextExtensions
{
private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup";
public static string[] GetMSBuildItems(this GeneratorExecutionContext context, string name)
=> context
.AdditionalFiles
.Where(f =>
context
.AnalyzerConfigOptions
.GetOptions(f)
.TryGetValue(SourceItemGroupMetadata, out var sourceItemGroup)
&& sourceItemGroup == name)
.Select(f => f.Path)
.ToArray();
public static string GetMsBuildProperty(
this GeneratorExecutionContext context,
string name,
string defaultValue = "")
{
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue($"build_property.{name}", out var value);
return value ?? defaultValue;
}
public static string[] GetMsBuildItems(this GeneratorExecutionContext context, string name)
=> context
.AdditionalFiles
.Where(f =>
context
.AnalyzerConfigOptions
.GetOptions(f)
.TryGetValue(SourceItemGroupMetadata, out var sourceItemGroup)
&& sourceItemGroup == name)
.Select(f => f.Path)
.ToArray();
}

135
src/Avalonia.NameGenerator/GeneratorOptions.cs

@ -1,94 +1,93 @@
using System;
using Microsoft.CodeAnalysis;
namespace Avalonia.NameGenerator
namespace Avalonia.NameGenerator;
public enum BuildProperties
{
public enum BuildProperties
{
AvaloniaNameGeneratorBehavior = 0,
AvaloniaNameGeneratorDefaultFieldModifier = 1,
AvaloniaNameGeneratorFilterByPath = 2,
AvaloniaNameGeneratorFilterByNamespace = 3,
}
AvaloniaNameGeneratorBehavior = 0,
AvaloniaNameGeneratorDefaultFieldModifier = 1,
AvaloniaNameGeneratorFilterByPath = 2,
AvaloniaNameGeneratorFilterByNamespace = 3,
}
public enum DefaultFieldModifier
{
Public = 0,
Private = 1,
Internal = 2,
Protected = 3,
}
public enum DefaultFieldModifier
{
Public = 0,
Private = 1,
Internal = 2,
Protected = 3,
}
public enum Behavior
{
OnlyProperties = 0,
InitializeComponent = 1,
}
public enum Behavior
{
OnlyProperties = 0,
InitializeComponent = 1,
}
public class GeneratorOptions
{
private readonly GeneratorExecutionContext _context;
public class GeneratorOptions
{
private readonly GeneratorExecutionContext _context;
public GeneratorOptions(GeneratorExecutionContext context) => _context = context;
public GeneratorOptions(GeneratorExecutionContext context) => _context = context;
public Behavior AvaloniaNameGeneratorBehavior
public Behavior AvaloniaNameGeneratorBehavior
{
get
{
get
{
const Behavior defaultBehavior = Behavior.InitializeComponent;
var propertyValue = _context
.GetMSBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorBehavior),
defaultBehavior.ToString());
const Behavior defaultBehavior = Behavior.InitializeComponent;
var propertyValue = _context
.GetMsBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorBehavior),
defaultBehavior.ToString());
if (!Enum.TryParse(propertyValue, true, out Behavior behavior))
return defaultBehavior;
return behavior;
}
if (!Enum.TryParse(propertyValue, true, out Behavior behavior))
return defaultBehavior;
return behavior;
}
}
public DefaultFieldModifier AvaloniaNameGeneratorDefaultFieldModifier
public DefaultFieldModifier AvaloniaNameGeneratorDefaultFieldModifier
{
get
{
get
{
const DefaultFieldModifier defaultFieldModifier = DefaultFieldModifier.Internal;
var propertyValue = _context
.GetMSBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorDefaultFieldModifier),
defaultFieldModifier.ToString());
const DefaultFieldModifier defaultFieldModifier = DefaultFieldModifier.Internal;
var propertyValue = _context
.GetMsBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorDefaultFieldModifier),
defaultFieldModifier.ToString());
if (!Enum.TryParse(propertyValue, true, out DefaultFieldModifier modifier))
return defaultFieldModifier;
return modifier;
}
if (!Enum.TryParse(propertyValue, true, out DefaultFieldModifier modifier))
return defaultFieldModifier;
return modifier;
}
}
public string[] AvaloniaNameGeneratorFilterByPath
public string[] AvaloniaNameGeneratorFilterByPath
{
get
{
get
{
var propertyValue = _context.GetMSBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorFilterByPath),
"*");
var propertyValue = _context.GetMsBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorFilterByPath),
"*");
if (propertyValue.Contains(";"))
return propertyValue.Split(';');
return new[] {propertyValue};
}
if (propertyValue.Contains(";"))
return propertyValue.Split(';');
return new[] {propertyValue};
}
}
public string[] AvaloniaNameGeneratorFilterByNamespace
public string[] AvaloniaNameGeneratorFilterByNamespace
{
get
{
get
{
var propertyValue = _context.GetMSBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorFilterByNamespace),
"*");
var propertyValue = _context.GetMsBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorFilterByNamespace),
"*");
if (propertyValue.Contains(";"))
return propertyValue.Split(';');
return new[] {propertyValue};
}
if (propertyValue.Contains(";"))
return propertyValue.Split(';');
return new[] {propertyValue};
}
}
}
Loading…
Cancel
Save