diff --git a/samples/Generators.Sandbox/App.xaml.cs b/samples/Generators.Sandbox/App.xaml.cs
index 6118b3f177..e592923eea 100644
--- a/samples/Generators.Sandbox/App.xaml.cs
+++ b/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();
}
-}
\ No newline at end of file
+}
diff --git a/samples/Generators.Sandbox/MyResources.axaml b/samples/Generators.Sandbox/MyResources.axaml
new file mode 100644
index 0000000000..70d192a299
--- /dev/null
+++ b/samples/Generators.Sandbox/MyResources.axaml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 4b9e608f5c..ce42224f38 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -18,6 +18,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/IComponentConnector.cs b/src/Markup/Avalonia.Markup.Xaml/IComponentConnector.cs
new file mode 100644
index 0000000000..84cf14a0e2
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/IComponentConnector.cs
@@ -0,0 +1,16 @@
+namespace Avalonia.Markup.Xaml;
+
+public interface IComponentConnector
+{
+ ///
+ /// Loads the compiled page of a component.
+ ///
+ void InitializeComponent();
+
+ ///
+ /// Attaches names to compiled content.
+ ///
+ /// An identifier token to distinguish calls.
+ /// The target to connect names to.
+ void Connect (int connectionId, object target);
+}
diff --git a/src/tools/Avalonia.Generators/Common/Domain/IClassResolver.cs b/src/tools/Avalonia.Generators/Common/Domain/IClassResolver.cs
new file mode 100644
index 0000000000..e08ab46027
--- /dev/null
+++ b/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);
diff --git a/src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs b/src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs
index 4b426172f8..c1ebdc3e45 100644
--- a/src/tools/Avalonia.Generators/Common/Domain/ICodeGenerator.cs
+++ b/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 names);
+ string GenerateCode(ResolvedView view, IEnumerable names);
}
diff --git a/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs b/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs
index 49ceb6f69e..1f4677aa23 100644
--- a/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs
+++ b/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; }
+}
diff --git a/src/tools/Avalonia.Generators/Common/XamlXClassResolver.cs b/src/tools/Avalonia.Generators/Common/XamlXClassResolver.cs
new file mode 100644
index 0000000000..9e9355687d
--- /dev/null
+++ b/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();
+ }
+}
diff --git a/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs b/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs
index b0495b2840..2e26460a34 100644
--- a/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs
+++ b/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? _onTypeInvalid;
private readonly Action? _onUnhandledError;
private ResolvedView? _resolvedClass;
@@ -23,12 +21,8 @@ internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
public XamlXViewResolver(
RoslynTypeSystem typeSystem,
MiniCompiler compiler,
- bool checkTypeValidity = false,
- Action? onTypeInvalid = null,
Action? 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;
}
}
diff --git a/src/tools/Avalonia.Generators/GeneratorContextExtensions.cs b/src/tools/Avalonia.Generators/GeneratorContextExtensions.cs
index b1f7738a8a..170c761edd 100644
--- a/src/tools/Avalonia.Generators/GeneratorContextExtensions.cs
+++ b/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(
diff --git a/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameGenerator.cs b/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameGenerator.cs
index 67389ef826..e90c9b8f73 100644
--- a/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameGenerator.cs
+++ b/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 GenerateNameReferences(IEnumerable additionalFiles, CancellationToken cancellationToken)
+ public IEnumerable GenerateSupportClasses(IEnumerable 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();
- 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
diff --git a/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameSourceGenerator.cs b/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameSourceGenerator.cs
index e93895db2e..8a3e051626 100644
--- a/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameSourceGenerator.cs
+++ b/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);
}
}
diff --git a/src/tools/Avalonia.Generators/NameGenerator/INameGenerator.cs b/src/tools/Avalonia.Generators/NameGenerator/INameGenerator.cs
index 5b44de43c1..15e501128b 100644
--- a/src/tools/Avalonia.Generators/NameGenerator/INameGenerator.cs
+++ b/src/tools/Avalonia.Generators/NameGenerator/INameGenerator.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Generators.NameGenerator;
internal interface INameGenerator
{
- IEnumerable GenerateNameReferences(IEnumerable additionalFiles, CancellationToken cancellationToken);
+ IEnumerable GenerateSupportClasses(IEnumerable additionalFiles, CancellationToken cancellationToken);
}
internal record GeneratedPartialClass(string FileName, string Content);
diff --git a/src/tools/Avalonia.Generators/NameGenerator/InitializeComponentCodeGenerator.cs b/src/tools/Avalonia.Generators/NameGenerator/InitializeComponentCodeGenerator.cs
index 3dd058af0b..06fdffa13c 100644
--- a/src/tools/Avalonia.Generators/NameGenerator/InitializeComponentCodeGenerator.cs
+++ b/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 names)
+ public string GenerateCode(ResolvedView view, IEnumerable names)
{
var properties = new List();
var initializations = new List();
@@ -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 $@"//
@@ -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)}
diff --git a/src/tools/Avalonia.Generators/NameGenerator/OnlyPropertiesCodeGenerator.cs b/src/tools/Avalonia.Generators/NameGenerator/OnlyPropertiesCodeGenerator.cs
index 8b295acd6b..4754839b3b 100644
--- a/src/tools/Avalonia.Generators/NameGenerator/OnlyPropertiesCodeGenerator.cs
+++ b/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 names)
+ public string GenerateCode(ResolvedView view, IEnumerable 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}
}}
diff --git a/tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs b/tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs
index 15fb282ed9..db1b78e60f 100644
--- a/tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs
+++ b/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))
diff --git a/tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs b/tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs
index 3f498c2be2..93bec19325 100644
--- a/tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs
+++ b/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))