Browse Source

wip

xclass-generator
Max Katz 1 year ago
parent
commit
ad2ae99d27
  1. 10
      samples/Generators.Sandbox/App.xaml.cs
  2. 6
      samples/Generators.Sandbox/MyResources.axaml
  3. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  4. 16
      src/Markup/Avalonia.Markup.Xaml/IComponentConnector.cs
  5. 10
      src/tools/Avalonia.Generators/Common/Domain/IClassResolver.cs
  6. 2
      src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs
  7. 6
      src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs
  8. 27
      src/tools/Avalonia.Generators/Common/XamlXClassResolver.cs
  9. 27
      src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs
  10. 6
      src/tools/Avalonia.Generators/GeneratorContextExtensions.cs
  11. 49
      src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameGenerator.cs
  12. 6
      src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameSourceGenerator.cs
  13. 2
      src/tools/Avalonia.Generators/NameGenerator/INameGenerator.cs
  14. 10
      src/tools/Avalonia.Generators/NameGenerator/InitializeComponentCodeGenerator.cs
  15. 6
      src/tools/Avalonia.Generators/NameGenerator/OnlyPropertiesCodeGenerator.cs
  16. 2
      tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs
  17. 2
      tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs

10
samples/Generators.Sandbox/App.xaml.cs

@ -4,9 +4,13 @@ using Generators.Sandbox.ViewModels;
namespace Generators.Sandbox;
public class App : Application
public partial class App : Application
{
public override void Initialize() => AvaloniaXamlLoader.Load(this);
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
Resources.MergedDictionaries.Add(new global::Sandbox.MyResources());
}
public override void OnFrameworkInitializationCompleted()
{
@ -17,4 +21,4 @@ public class App : Application
view.Show();
base.OnFrameworkInitializationCompleted();
}
}
}

6
samples/Generators.Sandbox/MyResources.axaml

@ -0,0 +1,6 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sandbox.MyResources">
<!-- Add Resources Here -->
</ResourceDictionary>

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -18,6 +18,7 @@
<Compile Include="Data\DynamicResourceExpression.cs" />
<Compile Include="EagerParentStackEnumerator.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="IComponentConnector.cs" />
<Compile Include="MarkupExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindingExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\CompiledBindingPath.cs" />

16
src/Markup/Avalonia.Markup.Xaml/IComponentConnector.cs

@ -0,0 +1,16 @@
namespace Avalonia.Markup.Xaml;
public interface IComponentConnector
{
/// <summary>
/// Loads the compiled page of a component.
/// </summary>
void InitializeComponent();
/// <summary>
/// Attaches names to compiled content.
/// </summary>
/// <param name="connectionId">An identifier token to distinguish calls.</param>
/// <param name="target">The target to connect names to.</param>
void Connect (int connectionId, object target);
}

10
src/tools/Avalonia.Generators/Common/Domain/IClassResolver.cs

@ -0,0 +1,10 @@
using XamlX.Ast;
namespace Avalonia.Generators.Common.Domain;
internal interface IClassResolver
{
ResolvedClass? ResolveClass(XamlDocument xaml);
}
internal record ResolvedClass(string TypeName, string ClassModifier);

2
src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs

@ -5,5 +5,5 @@ namespace Avalonia.Generators.Common.Domain;
internal interface ICodeGenerator
{
string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names);
string GenerateCode(ResolvedView view, IEnumerable<ResolvedName> names);
}

6
src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs

@ -8,4 +8,8 @@ internal interface IViewResolver
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)
{
public bool IsStyledElement { get; init; }
public bool HasClass { get; init; }
}

27
src/tools/Avalonia.Generators/Common/XamlXClassResolver.cs

@ -0,0 +1,27 @@
using Avalonia.Generators.Common.Domain;
using XamlX.Ast;
namespace Avalonia.Generators.Common;
internal class XamlXClassResolver : IClassResolver, IXamlAstVisitor
{
public ResolvedClass? ResolveClass(XamlDocument xaml)
{
return null;
}
public IXamlAstNode Visit(IXamlAstNode node)
{
throw new System.NotImplementedException();
}
public void Push(IXamlAstNode node)
{
throw new System.NotImplementedException();
}
public void Pop()
{
throw new System.NotImplementedException();
}
}

27
src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs

@ -13,8 +13,6 @@ internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
{
private readonly RoslynTypeSystem _typeSystem;
private readonly MiniCompiler _compiler;
private readonly bool _checkTypeValidity;
private readonly Action<string>? _onTypeInvalid;
private readonly Action<Exception>? _onUnhandledError;
private ResolvedView? _resolvedClass;
@ -23,12 +21,8 @@ internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
public XamlXViewResolver(
RoslynTypeSystem typeSystem,
MiniCompiler compiler,
bool checkTypeValidity = false,
Action<string>? onTypeInvalid = null,
Action<Exception>? onUnhandledError = null)
{
_checkTypeValidity = checkTypeValidity;
_onTypeInvalid = onTypeInvalid;
_onUnhandledError = onUnhandledError;
_typeSystem = typeSystem;
_compiler = compiler;
@ -61,10 +55,6 @@ internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
if (node is not XamlAstObjectNode objectNode)
return node;
var clrType = objectNode.Type.GetClrType();
if (!clrType.IsAvaloniaStyledElement())
return node;
foreach (var child in objectNode.Children)
{
if (child is XamlAstXmlDirective directive &&
@ -72,21 +62,16 @@ internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
directive.Namespace == XamlNamespaces.Xaml2006 &&
directive.Values[0] is XamlAstTextNode text)
{
if (_checkTypeValidity)
{
var existingType = _typeSystem.FindType(text.Text);
if (existingType == null)
{
_onTypeInvalid?.Invoke(text.Text);
return node;
}
}
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!);
var clrType = objectNode.Type.GetClrType();
_resolvedClass = new ResolvedView(className, clrType, nameSpace, _xaml!)
{
IsStyledElement = clrType.IsAvaloniaStyledElement(),
HasClass = _typeSystem.FindType(text.Text) is not null
};
return node;
}
}

6
src/tools/Avalonia.Generators/GeneratorContextExtensions.cs

@ -6,7 +6,6 @@ namespace Avalonia.Generators;
internal static class GeneratorContextExtensions
{
private const string UnhandledErrorDescriptorId = "AXN0002";
private const string InvalidTypeDescriptorId = "AXN0001";
public static string GetMsBuildProperty(
this GeneratorExecutionContext context,
@ -24,11 +23,6 @@ internal static class GeneratorContextExtensions
error.Message,
error.ToString());
public static void ReportNameGeneratorInvalidType(this GeneratorExecutionContext context, string typeName) =>
context.Report(InvalidTypeDescriptorId,
$"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " +
$"The type '{typeName}' does not exist in the assembly.");
private static void Report(this GeneratorExecutionContext context, string id, string title, string? message = null, string? description = null) =>
context.ReportDiagnostic(
Diagnostic.Create(

49
src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameGenerator.cs

@ -12,49 +12,52 @@ internal class AvaloniaNameGenerator : INameGenerator
private readonly ViewFileNamingStrategy _naming;
private readonly IGlobPattern _pathPattern;
private readonly IGlobPattern _namespacePattern;
private readonly IViewResolver _classes;
private readonly IViewResolver _views;
private readonly INameResolver _names;
private readonly IClassResolver _classes;
private readonly ICodeGenerator _code;
public AvaloniaNameGenerator(
ViewFileNamingStrategy naming,
IGlobPattern pathPattern,
IGlobPattern namespacePattern,
IViewResolver classes,
IViewResolver views,
INameResolver names,
IClassResolver classes,
ICodeGenerator code)
{
_naming = naming;
_pathPattern = pathPattern;
_namespacePattern = namespacePattern;
_classes = classes;
_views = views;
_names = names;
_classes = classes;
_code = code;
}
public IEnumerable<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles, CancellationToken cancellationToken)
public IEnumerable<GeneratedPartialClass> GenerateSupportClasses(IEnumerable<AdditionalText> additionalFiles, CancellationToken cancellationToken)
{
var resolveViews =
from file in additionalFiles
let filePath = file.Path
where (filePath.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) ||
filePath.EndsWith(".paml", StringComparison.OrdinalIgnoreCase) ||
filePath.EndsWith(".axaml", StringComparison.OrdinalIgnoreCase)) &&
_pathPattern.Matches(filePath)
let xaml = file.GetText(cancellationToken)?.ToString()
where xaml != null
let view = _classes.ResolveView(xaml)
where view != null && _namespacePattern.Matches(view.Namespace)
select view;
var resolveViews = additionalFiles.Select(file => (file, filePath: file.Path))
.Where(t =>
(t.filePath.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) ||
t.filePath.EndsWith(".paml", StringComparison.OrdinalIgnoreCase) ||
t.filePath.EndsWith(".axaml", StringComparison.OrdinalIgnoreCase)) &&
_pathPattern.Matches(t.filePath))
.Select(t => t.file.GetText(cancellationToken)?.ToString())
.Where(xaml => xaml != null)
.Select(xaml => _views.ResolveView(xaml!))
.Where(view => view != null && _namespacePattern.Matches(view.Namespace))!
.ToArray<ResolvedView>();
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 = ResolveViewFileName(view, _naming)
select new GeneratedPartialClass(fileName, code);
var xNameFiles = resolveViews
.Select(view => (view, names: _names.ResolveNames(view.Xaml)))
.Where(t => !t.view.HasClass || (t.view.IsStyledElement && t.names.Any()))
.Select(t => (
fileName: ResolveViewFileName(t.view, _naming),
code: _code.GenerateCode(t.view, t.names)))
.Select(t => new GeneratedPartialClass(t.fileName, t.code));
return query;
return xNameFiles;
}
private static string ResolveViewFileName(ResolvedView view, ViewFileNamingStrategy strategy) => strategy switch

6
src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameSourceGenerator.cs

@ -27,7 +27,7 @@ public class AvaloniaNameSourceGenerator : ISourceGenerator
return;
}
var partials = generator.GenerateNameReferences(ResolveAdditionalFiles(context), context.CancellationToken);
var partials = generator.GenerateSupportClasses(ResolveAdditionalFiles(context), context.CancellationToken);
foreach (var (fileName, content) in partials)
{
if(context.CancellationToken.IsCancellationRequested)
@ -77,10 +77,10 @@ public class AvaloniaNameSourceGenerator : ISourceGenerator
options.AvaloniaNameGeneratorViewFileNamingStrategy,
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath),
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace),
new XamlXViewResolver(types, compiler, true,
type => context.ReportNameGeneratorInvalidType(type),
new XamlXViewResolver(types, compiler,
error => context.ReportNameGeneratorUnhandledError(error)),
new XamlXNameResolver(options.AvaloniaNameGeneratorClassFieldModifier),
new XamlXClassResolver(),
generator);
}
}

2
src/tools/Avalonia.Generators/NameGenerator/INameGenerator.cs

@ -6,7 +6,7 @@ namespace Avalonia.Generators.NameGenerator;
internal interface INameGenerator
{
IEnumerable<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles, CancellationToken cancellationToken);
IEnumerable<GeneratedPartialClass> GenerateSupportClasses(IEnumerable<AdditionalText> additionalFiles, CancellationToken cancellationToken);
}
internal record GeneratedPartialClass(string FileName, string Content);

10
src/tools/Avalonia.Generators/NameGenerator/InitializeComponentCodeGenerator.cs

@ -27,7 +27,7 @@ internal class InitializeComponentCodeGenerator : ICodeGenerator
_diagnosticsAreConnected = avaloniaNameGeneratorAttachDevTools && types.FindAssembly("Avalonia.Diagnostics") != null;
}
public string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names)
public string GenerateCode(ResolvedView view, IEnumerable<ResolvedName> names)
{
var properties = new List<string>();
var initializations = new List<string>();
@ -53,7 +53,9 @@ internal class InitializeComponentCodeGenerator : ICodeGenerator
hasNames = true;
}
var attachDevTools = _diagnosticsAreConnected && IsWindow(xamlType);
var attachDevTools = _diagnosticsAreConnected && IsWindow(view.XamlType);
var baseClassPart = view.HasClass ? "" : $": global::{view.XamlType.FullName}";
return $@"// <auto-generated />
@ -61,9 +63,9 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace {nameSpace}
namespace {view.Namespace}
{{
partial class {className}
partial class {view.ClassName} {baseClassPart}
{{
{string.Join("\n", properties)}

6
src/tools/Avalonia.Generators/NameGenerator/OnlyPropertiesCodeGenerator.cs

@ -10,7 +10,7 @@ internal class OnlyPropertiesCodeGenerator : ICodeGenerator
private string _generatorName = typeof(OnlyPropertiesCodeGenerator).FullName;
private string _generatorVersion = typeof(OnlyPropertiesCodeGenerator).Assembly.GetName().Version.ToString();
public string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names)
public string GenerateCode(ResolvedView view, IEnumerable<ResolvedName> names)
{
var namedControls = names
.Select(info => " " +
@ -24,9 +24,9 @@ internal class OnlyPropertiesCodeGenerator : ICodeGenerator
using Avalonia.Controls;
namespace {nameSpace}
namespace {view.Namespace}
{{
partial class {className}
partial class {view.Xaml} : global::{view.XamlType.FullName}
{{
{lines}
}}

2
tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs

@ -52,7 +52,7 @@ public class InitializeComponentTests
var generatorVersion = typeof(InitializeComponentCodeGenerator).Assembly.GetName().Version?.ToString();
var code = generator
.GenerateCode("SampleView", "Sample.App", classInfo.XamlType, names)
.GenerateNamesSupportCode("SampleView", "Sample.App", classInfo.XamlType, names)
.Replace("\r", string.Empty);
var expected = (await InitializeComponentCode.Load(expectation))

2
tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs

@ -45,7 +45,7 @@ public class OnlyPropertiesTests
var generatorVersion = typeof(OnlyPropertiesCodeGenerator).Assembly.GetName().Version?.ToString();
var code = generator
.GenerateCode("SampleView", "Sample.App", classInfo.XamlType, names)
.GenerateNamesSupportCode("SampleView", "Sample.App", classInfo.XamlType, names)
.Replace("\r", string.Empty);
var expected = (await OnlyPropertiesCode.Load(expectation))

Loading…
Cancel
Save