Browse Source

fix: Name Generation for Identical View Class Names (#93)

* fix: Name Generation for Identical View Class Names
* nit: Formatting
pull/10407/head
Artyom V. Gorchakov 4 years ago
committed by GitHub
parent
commit
88b52d8900
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      README.md
  2. 3
      src/Avalonia.NameGenerator.Sandbox/App.xaml.cs
  3. 38
      src/Avalonia.NameGenerator.Sandbox/Controls/SignUpView.xaml
  4. 53
      src/Avalonia.NameGenerator.Sandbox/Controls/SignUpView.xaml.cs
  5. 32
      src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml
  6. 31
      src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml.cs
  7. 2
      src/Avalonia.NameGenerator.Tests/Views/View.cs
  8. 29
      src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs
  9. 2
      src/Avalonia.NameGenerator/Domain/ICodeGenerator.cs
  10. 48
      src/Avalonia.NameGenerator/Domain/INameResolver.cs
  11. 6
      src/Avalonia.NameGenerator/Generator.cs
  12. 13
      src/Avalonia.NameGenerator/Generator/AvaloniaNameGenerator.cs
  13. 3
      src/Avalonia.NameGenerator/Generator/InitializeComponentCodeGenerator.cs
  14. 4
      src/Avalonia.NameGenerator/Generator/OnlyPropertiesCodeGenerator.cs
  15. 15
      src/Avalonia.NameGenerator/Generator/XamlXNameResolver.cs
  16. 81
      src/Avalonia.NameGenerator/GeneratorOptions.cs

6
README.md

@ -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). 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` 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: The default values are given by:
```xml ```xml
@ -119,6 +124,7 @@ The default values are given by:
<AvaloniaNameGeneratorDefaultFieldModifier>internal</AvaloniaNameGeneratorDefaultFieldModifier> <AvaloniaNameGeneratorDefaultFieldModifier>internal</AvaloniaNameGeneratorDefaultFieldModifier>
<AvaloniaNameGeneratorFilterByPath>*</AvaloniaNameGeneratorFilterByPath> <AvaloniaNameGeneratorFilterByPath>*</AvaloniaNameGeneratorFilterByPath>
<AvaloniaNameGeneratorFilterByNamespace>*</AvaloniaNameGeneratorFilterByNamespace> <AvaloniaNameGeneratorFilterByNamespace>*</AvaloniaNameGeneratorFilterByNamespace>
<AvaloniaNameGeneratorViewFileNamingStrategy>NamespaceAndClassName</AvaloniaNameGeneratorViewFileNamingStrategy>
</PropertyGroup> </PropertyGroup>
<!-- ... --> <!-- ... -->
</Project> </Project>

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

@ -1,6 +1,5 @@
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.NameGenerator.Sandbox.ViewModels; using Avalonia.NameGenerator.Sandbox.ViewModels;
using Avalonia.NameGenerator.Sandbox.Views;
namespace Avalonia.NameGenerator.Sandbox; namespace Avalonia.NameGenerator.Sandbox;
@ -10,7 +9,7 @@ public class App : Application
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{ {
var view = new SignUpView var view = new Views.SignUpView
{ {
ViewModel = new SignUpViewModel() ViewModel = new SignUpViewModel()
}; };

38
src/Avalonia.NameGenerator.Sandbox/Controls/SignUpView.xaml

@ -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
src/Avalonia.NameGenerator.Sandbox/Controls/SignUpView.xaml.cs

@ -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
src/Avalonia.NameGenerator.Sandbox/Views/SignUpView.xaml

@ -4,36 +4,6 @@
x:Class="Avalonia.NameGenerator.Sandbox.Views.SignUpView"> x:Class="Avalonia.NameGenerator.Sandbox.Views.SignUpView">
<StackPanel Margin="10"> <StackPanel Margin="10">
<TextBlock Text="Sign Up" /> <TextBlock Text="Sign Up" />
<controls:CustomTextBox Margin="0 10 0 0" <controls:SignUpView x:Name="SignUpControl" />
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> </StackPanel>
</Window> </Window>

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

@ -1,10 +1,7 @@
using System; using System.Reactive.Disposables;
using System.Reactive.Disposables;
using Avalonia.NameGenerator.Sandbox.ViewModels; using Avalonia.NameGenerator.Sandbox.ViewModels;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Formatters;
namespace Avalonia.NameGenerator.Sandbox.Views; namespace Avalonia.NameGenerator.Sandbox.Views;
@ -23,31 +20,9 @@ public partial class SignUpView : ReactiveWindow<SignUpViewModel>
InitializeComponent(); InitializeComponent();
this.WhenActivated(disposables => 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); .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
src/Avalonia.NameGenerator.Tests/Views/View.cs

@ -3,9 +3,7 @@ using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;

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

@ -24,7 +24,7 @@ public class XamlXNameResolverTests
Assert.NotEmpty(controls); Assert.NotEmpty(controls);
Assert.Equal(1, controls.Count); Assert.Equal(1, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name); Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName); Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
} }
[Theory] [Theory]
@ -40,9 +40,9 @@ public class XamlXNameResolverTests
Assert.Equal("UserNameTextBox", controls[0].Name); Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.Equal("PasswordTextBox", controls[1].Name); Assert.Equal("PasswordTextBox", controls[1].Name);
Assert.Equal("SignUpButton", controls[2].Name); Assert.Equal("SignUpButton", controls[2].Name);
Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName); Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
Assert.Equal(typeof(TextBox).FullName, controls[1].TypeName); Assert.Contains(typeof(TextBox).FullName!, controls[1].TypeName);
Assert.Equal(typeof(Button).FullName, controls[2].TypeName); Assert.Contains(typeof(Button).FullName!, controls[2].TypeName);
} }
[Fact] [Fact]
@ -56,9 +56,9 @@ public class XamlXNameResolverTests
Assert.Equal("ClrNamespaceRoutedViewHost", controls[0].Name); Assert.Equal("ClrNamespaceRoutedViewHost", controls[0].Name);
Assert.Equal("UriRoutedViewHost", controls[1].Name); Assert.Equal("UriRoutedViewHost", controls[1].Name);
Assert.Equal("UserNameTextBox", controls[2].Name); Assert.Equal("UserNameTextBox", controls[2].Name);
Assert.Equal(typeof(RoutedViewHost).FullName, controls[0].TypeName); Assert.Contains(typeof(RoutedViewHost).FullName!, controls[0].TypeName);
Assert.Equal(typeof(RoutedViewHost).FullName, controls[1].TypeName); Assert.Contains(typeof(RoutedViewHost).FullName!, controls[1].TypeName);
Assert.Equal("Controls.CustomTextBox", controls[2].TypeName); Assert.Contains("Controls.CustomTextBox", controls[2].TypeName);
} }
[Fact] [Fact]
@ -70,17 +70,12 @@ public class XamlXNameResolverTests
var currentControl = controls[0]; var currentControl = controls[0];
Assert.Equal("Root", currentControl.Name); Assert.Equal("Root", currentControl.Name);
Assert.Equal("Sample.App.BaseView", currentControl.TypeName); Assert.Equal("global::Sample.App.BaseView<global::System.String>", 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);
currentControl = controls[1]; currentControl = controls[1];
Assert.Equal("NotAsRootNode", currentControl.Name); Assert.Equal("NotAsRootNode", currentControl.Name);
Assert.Equal("Sample.App.BaseView", currentControl.TypeName); Assert.Contains("Sample.App.BaseView", currentControl.TypeName);
Assert.Equal(1, currentControl.GenericTypeArguments.Count); Assert.Equal("global::Sample.App.BaseView<global::System.Int32>", currentControl.TypeName);
Assert.Equal(typeof(int).FullName, currentControl.GenericTypeArguments[0]);
Assert.Equal("global::Sample.App.BaseView<global::System.Int32>", currentControl.PrintableTypeName);
} }
[Fact] [Fact]
@ -102,8 +97,8 @@ public class XamlXNameResolverTests
Assert.Equal(2, controls.Count); Assert.Equal(2, controls.Count);
Assert.Equal("UserNameTextBox", controls[0].Name); Assert.Equal("UserNameTextBox", controls[0].Name);
Assert.Equal("NamedListBox", controls[1].Name); Assert.Equal("NamedListBox", controls[1].Name);
Assert.Equal(typeof(TextBox).FullName, controls[0].TypeName); Assert.Contains(typeof(TextBox).FullName!, controls[0].TypeName);
Assert.Equal(typeof(ListBox).FullName, controls[1].TypeName); Assert.Contains(typeof(ListBox).FullName!, controls[1].TypeName);
} }
[Fact] [Fact]

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

@ -5,5 +5,5 @@ 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);
} }

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

@ -1,6 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using XamlX.Ast; using XamlX.Ast;
namespace Avalonia.NameGenerator.Domain; namespace Avalonia.NameGenerator.Domain;
@ -10,48 +8,4 @@ internal interface INameResolver
IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml); IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml);
} }
internal class ResolvedName internal record ResolvedName(string TypeName, string Name, string FieldModifier);
{
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();
}
}

6
src/Avalonia.NameGenerator/Generator.cs

@ -21,7 +21,7 @@ public class AvaloniaNameSourceGenerator : ISourceGenerator
{ {
var generator = CreateNameGenerator(context); var generator = CreateNameGenerator(context);
var partials = generator.GenerateNameReferences(context.AdditionalFiles); 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) catch (Exception exception)
{ {
@ -33,7 +33,6 @@ public class AvaloniaNameSourceGenerator : ISourceGenerator
{ {
var options = new GeneratorOptions(context); var options = new GeneratorOptions(context);
var types = new RoslynTypeSystem((CSharpCompilation)context.Compilation); var types = new RoslynTypeSystem((CSharpCompilation)context.Compilation);
var defaultFieldModifier = options.AvaloniaNameGeneratorDefaultFieldModifier.ToString().ToLowerInvariant();
ICodeGenerator generator = options.AvaloniaNameGeneratorBehavior switch { ICodeGenerator generator = options.AvaloniaNameGeneratorBehavior switch {
Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(), Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(),
Behavior.InitializeComponent => new InitializeComponentCodeGenerator(types), Behavior.InitializeComponent => new InitializeComponentCodeGenerator(types),
@ -42,10 +41,11 @@ public class AvaloniaNameSourceGenerator : ISourceGenerator
var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute); var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
return new AvaloniaNameGenerator( return new AvaloniaNameGenerator(
options.AvaloniaNameGeneratorViewFileNamingStrategy,
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath), new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath),
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace), new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace),
new XamlXViewResolver(types, compiler, true, type => ReportInvalidType(context, type)), new XamlXViewResolver(types, compiler, true, type => ReportInvalidType(context, type)),
new XamlXNameResolver(defaultFieldModifier), new XamlXNameResolver(options.AvaloniaNameGeneratorDefaultFieldModifier),
generator); generator);
} }

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

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.NameGenerator.Domain; using Avalonia.NameGenerator.Domain;
@ -7,6 +8,7 @@ namespace Avalonia.NameGenerator.Generator;
internal class AvaloniaNameGenerator : INameGenerator internal class AvaloniaNameGenerator : INameGenerator
{ {
private readonly ViewFileNamingStrategy _naming;
private readonly IGlobPattern _pathPattern; private readonly IGlobPattern _pathPattern;
private readonly IGlobPattern _namespacePattern; private readonly IGlobPattern _namespacePattern;
private readonly IViewResolver _classes; private readonly IViewResolver _classes;
@ -14,12 +16,14 @@ internal class AvaloniaNameGenerator : INameGenerator
private readonly ICodeGenerator _code; private readonly ICodeGenerator _code;
public AvaloniaNameGenerator( public AvaloniaNameGenerator(
ViewFileNamingStrategy naming,
IGlobPattern pathPattern, IGlobPattern pathPattern,
IGlobPattern namespacePattern, IGlobPattern namespacePattern,
IViewResolver classes, IViewResolver classes,
INameResolver names, INameResolver names,
ICodeGenerator code) ICodeGenerator code)
{ {
_naming = naming;
_pathPattern = pathPattern; _pathPattern = pathPattern;
_namespacePattern = namespacePattern; _namespacePattern = namespacePattern;
_classes = classes; _classes = classes;
@ -44,9 +48,16 @@ internal class AvaloniaNameGenerator : INameGenerator
from view in resolveViews from view in resolveViews
let names = _names.ResolveNames(view.Xaml) let names = _names.ResolveNames(view.Xaml)
let code = _code.GenerateCode(view.ClassName, view.Namespace, view.XamlType, names) let code = _code.GenerateCode(view.ClassName, view.Namespace, view.XamlType, names)
let fileName = $"{view.ClassName}.g.cs" let fileName = ResolveViewFileName(view, _naming)
select new GeneratedPartialClass(fileName, code); select new GeneratedPartialClass(fileName, code);
return query.ToList(); return query.ToList();
} }
private static string ResolveViewFileName(ResolvedView view, ViewFileNamingStrategy strategy) => strategy switch
{
ViewFileNamingStrategy.ClassName => $"{view.ClassName}.g.cs",
ViewFileNamingStrategy.NamespaceAndClassName => $"{view.Namespace}.{view.ClassName}.g.cs",
_ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, "Unknown naming strategy!")
};
} }

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

@ -32,8 +32,7 @@ internal class InitializeComponentCodeGenerator: ICodeGenerator
var initializations = new List<string>(); var initializations = new List<string>();
foreach (var resolvedName in names) foreach (var resolvedName in names)
{ {
var (_, name, fieldModifier) = resolvedName; var (typeName, name, fieldModifier) = resolvedName;
string typeName = resolvedName.PrintableTypeName;
properties.Add($" {fieldModifier} {typeName} {name};"); properties.Add($" {fieldModifier} {typeName} {name};");
initializations.Add($" {name} = this.FindControl<{typeName}>(\"{name}\");"); initializations.Add($" {name} = this.FindControl<{typeName}>(\"{name}\");");
} }

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

@ -11,8 +11,8 @@ internal class OnlyPropertiesCodeGenerator : ICodeGenerator
{ {
var namedControls = names var namedControls = names
.Select(info => " " + .Select(info => " " +
$"{info.FieldModifier} {info.PrintableTypeName} {info.Name} => " + $"{info.FieldModifier} {info.TypeName} {info.Name} => " +
$"this.FindControl<{info.PrintableTypeName}>(\"{info.Name}\");") $"this.FindControl<{info.TypeName}>(\"{info.Name}\");")
.ToList(); .ToList();
var lines = string.Join("\n", namedControls); var lines = string.Join("\n", namedControls);
return $@"// <auto-generated /> return $@"// <auto-generated />

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

@ -1,9 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using Avalonia.NameGenerator.Domain; using Avalonia.NameGenerator.Domain;
using XamlX; using XamlX;
using XamlX.Ast; using XamlX.Ast;
@ -14,9 +12,9 @@ internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
private readonly List<ResolvedName> _items = new(); private readonly List<ResolvedName> _items = new();
private readonly string _defaultFieldModifier; private readonly string _defaultFieldModifier;
public XamlXNameResolver(string defaultFieldModifier = "internal") public XamlXNameResolver(DefaultFieldModifier defaultFieldModifier = DefaultFieldModifier.Internal)
{ {
_defaultFieldModifier = defaultFieldModifier; _defaultFieldModifier = defaultFieldModifier.ToString().ToLowerInvariant();
} }
public IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml) public IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml)
@ -50,10 +48,13 @@ internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
propertyValueNode.Values[0] is XamlAstTextNode text) propertyValueNode.Values[0] is XamlAstTextNode text)
{ {
var fieldModifier = TryGetFieldModifier(objectNode); var fieldModifier = TryGetFieldModifier(objectNode);
string typeName = $@"{clrType.Namespace}.{clrType.Name}"; var typeName = $@"{clrType.Namespace}.{clrType.Name}";
IReadOnlyList<string> typeAgs = clrType.GenericArguments.Select(arg => arg.FullName).ToImmutableList(); var typeAgs = clrType.GenericArguments.Select(arg => arg.FullName).ToImmutableList();
var genericTypeName = typeAgs.Count == 0
? $"global::{typeName}"
: $@"global::{typeName}<{string.Join(", ", typeAgs.Select(arg => $"global::{arg}"))}>";
var resolvedName = new ResolvedName(typeName, text.Text, fieldModifier, typeAgs); var resolvedName = new ResolvedName(genericTypeName, text.Text, fieldModifier);
if (_items.Contains(resolvedName)) if (_items.Contains(resolvedName))
continue; continue;
_items.Add(resolvedName); _items.Add(resolvedName);

81
src/Avalonia.NameGenerator/GeneratorOptions.cs

@ -9,6 +9,7 @@ public enum BuildProperties
AvaloniaNameGeneratorDefaultFieldModifier = 1, AvaloniaNameGeneratorDefaultFieldModifier = 1,
AvaloniaNameGeneratorFilterByPath = 2, AvaloniaNameGeneratorFilterByPath = 2,
AvaloniaNameGeneratorFilterByNamespace = 3, AvaloniaNameGeneratorFilterByNamespace = 3,
AvaloniaNameGeneratorViewFileNamingStrategy = 4,
} }
public enum DefaultFieldModifier public enum DefaultFieldModifier
@ -25,69 +26,49 @@ public enum Behavior
InitializeComponent = 1, InitializeComponent = 1,
} }
public enum ViewFileNamingStrategy
{
ClassName = 0,
NamespaceAndClassName = 1,
}
public class GeneratorOptions public class GeneratorOptions
{ {
private readonly GeneratorExecutionContext _context; private readonly GeneratorExecutionContext _context;
public GeneratorOptions(GeneratorExecutionContext context) => _context = context; public GeneratorOptions(GeneratorExecutionContext context) => _context = context;
public Behavior AvaloniaNameGeneratorBehavior public Behavior AvaloniaNameGeneratorBehavior => GetEnumProperty(
{ BuildProperties.AvaloniaNameGeneratorBehavior,
get Behavior.InitializeComponent);
{
const Behavior defaultBehavior = Behavior.InitializeComponent;
var propertyValue = _context
.GetMsBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorBehavior),
defaultBehavior.ToString());
if (!Enum.TryParse(propertyValue, true, out Behavior behavior)) public DefaultFieldModifier AvaloniaNameGeneratorDefaultFieldModifier => GetEnumProperty(
return defaultBehavior; BuildProperties.AvaloniaNameGeneratorDefaultFieldModifier,
return behavior; DefaultFieldModifier.Internal);
}
}
public DefaultFieldModifier AvaloniaNameGeneratorDefaultFieldModifier public ViewFileNamingStrategy AvaloniaNameGeneratorViewFileNamingStrategy => GetEnumProperty(
{ BuildProperties.AvaloniaNameGeneratorViewFileNamingStrategy,
get ViewFileNamingStrategy.NamespaceAndClassName);
{
const DefaultFieldModifier defaultFieldModifier = DefaultFieldModifier.Internal;
var propertyValue = _context
.GetMsBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorDefaultFieldModifier),
defaultFieldModifier.ToString());
if (!Enum.TryParse(propertyValue, true, out DefaultFieldModifier modifier)) public string[] AvaloniaNameGeneratorFilterByPath => GetStringArrayProperty(
return defaultFieldModifier; BuildProperties.AvaloniaNameGeneratorFilterByPath,
return modifier; "*");
}
}
public string[] AvaloniaNameGeneratorFilterByPath public string[] AvaloniaNameGeneratorFilterByNamespace => GetStringArrayProperty(
{ BuildProperties.AvaloniaNameGeneratorFilterByNamespace,
get "*");
{
var propertyValue = _context.GetMsBuildProperty(
nameof(BuildProperties.AvaloniaNameGeneratorFilterByPath),
"*");
if (propertyValue.Contains(";")) private string[] GetStringArrayProperty(BuildProperties name, string defaultValue)
return propertyValue.Split(';'); {
return new[] {propertyValue}; var key = name.ToString();
} var value = _context.GetMsBuildProperty(key, defaultValue);
return value.Contains(";") ? value.Split(';') : new[] {value};
} }
public string[] AvaloniaNameGeneratorFilterByNamespace private TEnum GetEnumProperty<TEnum>(BuildProperties name, TEnum defaultValue) where TEnum : struct
{ {
get var key = name.ToString();
{ var value = _context.GetMsBuildProperty(key, defaultValue.ToString());
var propertyValue = _context.GetMsBuildProperty( return Enum.TryParse(value, true, out TEnum behavior) ? behavior : defaultValue;
nameof(BuildProperties.AvaloniaNameGeneratorFilterByNamespace),
"*");
if (propertyValue.Contains(";"))
return propertyValue.Split(';');
return new[] {propertyValue};
}
} }
} }
Loading…
Cancel
Save