Skip to content
This repository has been archived by the owner on Jun 28, 2023. It is now read-only.

Commit

Permalink
fix: Name Generation for Identical View Class Names (#93)
Browse files Browse the repository at this point in the history
* fix: Name Generation for Identical View Class Names
* nit: Formatting
  • Loading branch information
worldbeater authored Nov 4, 2022
1 parent 138be17 commit 88b52d8
Show file tree
Hide file tree
Showing 16 changed files with 173 additions and 193 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ The `x:Name` generator can be configured via MsBuild properties that you can put
The generator will process only XAML files with base classes' namespaces matching the specified glob pattern(s).
Example: `MyApp.Presentation.*`, `MyApp.Presentation.Views;MyApp.Presentation.Controls`

- `AvaloniaNameGeneratorViewFileNamingStrategy`
Possible values: `ClassName`, `NamespaceAndClassName`
Default value: `NamespaceAndClassName`
Determines how the automatically generated view files should be [named](https://github.com/AvaloniaUI/Avalonia.NameGenerator/issues/92).

The default values are given by:

```xml
Expand All @@ -119,6 +124,7 @@ The default values are given by:
<AvaloniaNameGeneratorDefaultFieldModifier>internal</AvaloniaNameGeneratorDefaultFieldModifier>
<AvaloniaNameGeneratorFilterByPath>*</AvaloniaNameGeneratorFilterByPath>
<AvaloniaNameGeneratorFilterByNamespace>*</AvaloniaNameGeneratorFilterByNamespace>
<AvaloniaNameGeneratorViewFileNamingStrategy>NamespaceAndClassName</AvaloniaNameGeneratorViewFileNamingStrategy>
</PropertyGroup>
<!-- ... -->
</Project>
Expand Down
3 changes: 1 addition & 2 deletions src/Avalonia.NameGenerator.Sandbox/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Avalonia.Markup.Xaml;
using Avalonia.NameGenerator.Sandbox.ViewModels;
using Avalonia.NameGenerator.Sandbox.Views;

namespace Avalonia.NameGenerator.Sandbox;

Expand All @@ -10,7 +9,7 @@ public class App : Application

public override void OnFrameworkInitializationCompleted()
{
var view = new SignUpView
var view = new Views.SignUpView
{
ViewModel = new SignUpViewModel()
};
Expand Down
38 changes: 38 additions & 0 deletions src/Avalonia.NameGenerator.Sandbox/Controls/SignUpView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Avalonia.NameGenerator.Sandbox.Controls"
x:Class="Avalonia.NameGenerator.Sandbox.Controls.SignUpView">
<StackPanel>
<controls:CustomTextBox Margin="0 10 0 0"
x:Name="UserNameTextBox"
Watermark="Please, enter user name..."
UseFloatingWatermark="True" />
<TextBlock x:Name="UserNameValidation"
Foreground="Red"
FontSize="12" />
<TextBox Margin="0 10 0 0"
x:Name="PasswordTextBox"
Watermark="Please, enter your password..."
UseFloatingWatermark="True"
PasswordChar="*" />
<TextBlock x:Name="PasswordValidation"
Foreground="Red"
FontSize="12" />
<TextBox Margin="0 10 0 0"
x:Name="ConfirmPasswordTextBox"
Watermark="Please, confirm the password..."
UseFloatingWatermark="True"
PasswordChar="*" />
<TextBlock x:Name="ConfirmPasswordValidation"
TextWrapping="Wrap"
Foreground="Red"
FontSize="12" />
<Button Margin="0 10 0 5"
Content="Sign up"
x:Name="SignUpButton" />
<TextBlock x:Name="CompoundValidation"
TextWrapping="Wrap"
Foreground="Red"
FontSize="12" />
</StackPanel>
</UserControl>
53 changes: 53 additions & 0 deletions src/Avalonia.NameGenerator.Sandbox/Controls/SignUpView.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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.Controls;

/// <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 : ReactiveUserControl<SignUpViewModel>
{
public SignUpView()
{
// The InitializeComponent method is also generated automatically
// and lives in the autogenerated part of the partial class.
InitializeComponent();
this.WhenActivated(disposables =>
{
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!";
PasswordTextBox.Text = "1234";
ConfirmPasswordTextBox.Text = "1234";
});
}
}
32 changes: 1 addition & 31 deletions src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,6 @@
x:Class="Avalonia.NameGenerator.Sandbox.Views.SignUpView">
<StackPanel Margin="10">
<TextBlock Text="Sign Up" />
<controls:CustomTextBox Margin="0 10 0 0"
x:Name="UserNameTextBox"
Watermark="Please, enter user name..."
UseFloatingWatermark="True" />
<TextBlock x:Name="UserNameValidation"
Foreground="Red"
FontSize="12" />
<TextBox Margin="0 10 0 0"
x:Name="PasswordTextBox"
Watermark="Please, enter your password..."
UseFloatingWatermark="True"
PasswordChar="*" />
<TextBlock x:Name="PasswordValidation"
Foreground="Red"
FontSize="12" />
<TextBox Margin="0 10 0 0"
x:Name="ConfirmPasswordTextBox"
Watermark="Please, confirm the password..."
UseFloatingWatermark="True"
PasswordChar="*" />
<TextBlock x:Name="ConfirmPasswordValidation"
TextWrapping="Wrap"
Foreground="Red"
FontSize="12" />
<Button Margin="0 10 0 5"
Content="Sign up"
x:Name="SignUpButton" />
<TextBlock x:Name="CompoundValidation"
TextWrapping="Wrap"
Foreground="Red"
FontSize="12" />
<controls:SignUpView x:Name="SignUpControl" />
</StackPanel>
</Window>
31 changes: 3 additions & 28 deletions src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System;
using System.Reactive.Disposables;
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;

Expand All @@ -23,31 +20,9 @@ public SignUpView()
InitializeComponent();
this.WhenActivated(disposables =>
{
this.Bind(ViewModel, x => x.UserName, x => x.UserNameTextBox.Text)
this.WhenAnyValue(view => view.ViewModel)
.BindTo(this, view => view.SignUpControl.ViewModel)
.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!";
PasswordTextBox.Text = "1234";
ConfirmPasswordTextBox.Text = "1234";
});
}
}
2 changes: 0 additions & 2 deletions src/Avalonia.NameGenerator.Tests/Views/View.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Avalonia.Controls;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

Expand Down
29 changes: 12 additions & 17 deletions src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Cont
Assert.NotEmpty(controls);
Assert.Equal(1, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName);
Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
}

[Theory]
Expand All @@ -40,9 +40,9 @@ public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Cont
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);
Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
Assert.Contains(typeof(TextBox).FullName!, controls[1].TypeName);
Assert.Contains(typeof(Button).FullName!, controls[2].TypeName);
}

[Fact]
Expand All @@ -56,9 +56,9 @@ public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Custom_Con
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);
Assert.Contains(typeof(RoutedViewHost).FullName!, controls[0].TypeName);
Assert.Contains(typeof(RoutedViewHost).FullName!, controls[1].TypeName);
Assert.Contains("Controls.CustomTextBox", controls[2].TypeName);
}

[Fact]
Expand All @@ -70,17 +70,12 @@ public async Task Should_Resolve_Types_From_Avalonia_Markup_File_When_Types_Cont

var currentControl = controls[0];
Assert.Equal("Root", currentControl.Name);
Assert.Equal("Sample.App.BaseView", currentControl.TypeName);
Assert.Equal(1, currentControl.GenericTypeArguments.Count);
Assert.Equal(typeof(string).FullName, currentControl.GenericTypeArguments[0]);
Assert.Equal("global::Sample.App.BaseView<global::System.String>", currentControl.PrintableTypeName);
Assert.Equal("global::Sample.App.BaseView<global::System.String>", currentControl.TypeName);

currentControl = controls[1];
Assert.Equal("NotAsRootNode", currentControl.Name);
Assert.Equal("Sample.App.BaseView", currentControl.TypeName);
Assert.Equal(1, currentControl.GenericTypeArguments.Count);
Assert.Equal(typeof(int).FullName, currentControl.GenericTypeArguments[0]);
Assert.Equal("global::Sample.App.BaseView<global::System.Int32>", currentControl.PrintableTypeName);
Assert.Contains("Sample.App.BaseView", currentControl.TypeName);
Assert.Equal("global::Sample.App.BaseView<global::System.Int32>", currentControl.TypeName);
}

[Fact]
Expand All @@ -102,8 +97,8 @@ public async Task Should_Not_Resolve_Elements_From_DataTemplates()
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);
Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
Assert.Contains(typeof(ListBox).FullName!, controls[1].TypeName);
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion src/Avalonia.NameGenerator/Domain/ICodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace Avalonia.NameGenerator.Domain;

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);
}
48 changes: 1 addition & 47 deletions src/Avalonia.NameGenerator/Domain/INameResolver.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Linq;

using XamlX.Ast;

namespace Avalonia.NameGenerator.Domain;
Expand All @@ -10,48 +8,4 @@ internal interface INameResolver
IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml);
}

internal class ResolvedName
{
public ResolvedName(string typeName, string name, string fieldModifier, IReadOnlyList<string> genericTypeArguments)
{
TypeName = typeName;
Name = name;
FieldModifier = fieldModifier;
GenericTypeArguments = genericTypeArguments;
}

public string TypeName { get; }
public string Name { get; }
public string FieldModifier { get; }
public IReadOnlyList<string> GenericTypeArguments { get; }

public string PrintableTypeName =>
GenericTypeArguments.Count == 0
? $"global::{TypeName}"
: $@"global::{TypeName}<{string.Join(", ", GenericTypeArguments.Select(arg => $"global::{arg}"))}>";

public void Deconstruct(out string typeName, out string name, out string fieldModifier)
{
typeName = TypeName;
name = Name;
fieldModifier = FieldModifier;
}

public override bool Equals(object obj)
{
if (obj is not ResolvedName name)
{
return false;
}

return name.TypeName == TypeName
&& name.Name == Name
&& name.FieldModifier == FieldModifier
&& name.GenericTypeArguments.SequenceEqual(GenericTypeArguments);
}

public override int GetHashCode()
{
return (TypeName, Name, FieldModifier).GetHashCode();
}
}
internal record ResolvedName(string TypeName, string Name, string FieldModifier);
6 changes: 3 additions & 3 deletions src/Avalonia.NameGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void Execute(GeneratorExecutionContext context)
{
var generator = CreateNameGenerator(context);
var partials = generator.GenerateNameReferences(context.AdditionalFiles);
foreach (var partial in partials) context.AddSource(partial.FileName, partial.Content);
foreach (var (fileName, content) in partials) context.AddSource(fileName, content);
}
catch (Exception exception)
{
Expand All @@ -33,7 +33,6 @@ private static INameGenerator CreateNameGenerator(GeneratorExecutionContext cont
{
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),
Expand All @@ -42,10 +41,11 @@ private static INameGenerator CreateNameGenerator(GeneratorExecutionContext cont

var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
return new AvaloniaNameGenerator(
options.AvaloniaNameGeneratorViewFileNamingStrategy,
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath),
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace),
new XamlXViewResolver(types, compiler, true, type => ReportInvalidType(context, type)),
new XamlXNameResolver(defaultFieldModifier),
new XamlXNameResolver(options.AvaloniaNameGeneratorDefaultFieldModifier),
generator);
}

Expand Down
Loading

0 comments on commit 88b52d8

Please sign in to comment.