Browse Source

Split XAML parsing and type resolution into separated steps

pull/19216/head
Max Katz 1 year ago
parent
commit
f1ebfd91ae
  1. 2
      samples/Generators.Sandbox/Controls/SignUpView.xaml
  2. 7
      src/tools/Avalonia.Generators/Common/Domain/INameResolver.cs
  3. 31
      src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs
  4. 36
      src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs
  5. 24
      src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs
  6. 37
      src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs
  7. 22
      src/tools/Avalonia.Generators/Compiler/NoopTypeSystem.cs
  8. 2
      src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs
  9. 123
      src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameIncrementalGenerator.cs
  10. 4
      src/tools/Avalonia.Generators/NameGenerator/TrackingNames.cs
  11. 24
      tests/Avalonia.Generators.Tests/CompilationUtils.cs
  12. 24
      tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs
  13. 4
      tests/Avalonia.Generators.Tests/MiniCompilerTests.cs
  14. 24
      tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs
  15. 10
      tests/Avalonia.Generators.Tests/XamlXClassResolverTests.cs
  16. 23
      tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs

2
samples/Generators.Sandbox/Controls/SignUpView.xaml

@ -8,7 +8,7 @@
Watermark="Please, enter user name..."
UseFloatingWatermark="True" />
<TextBlock x:Name="UserNameValidation"
Foreground="Red"
Foreground="Green"
FontSize="12" />
<TextBox Margin="0 10 0 0"
x:Name="PasswordTextBox"

7
src/tools/Avalonia.Generators/Common/Domain/INameResolver.cs

@ -1,5 +1,6 @@
using System.Collections.Immutable;
using XamlX.Ast;
using XamlX.TypeSystem;
namespace Avalonia.Generators.Common.Domain;
@ -13,7 +14,11 @@ internal enum NamedFieldModifier
internal interface INameResolver
{
ImmutableArray<ResolvedName> ResolveNames(XamlDocument xaml);
ImmutableArray<ResolvedXmlName> ResolveXmlNames(XamlDocument xaml);
ResolvedName ResolveName(IXamlType xamlType, string name, string? fieldModifier);
}
internal record XamlXmlType(string Name, string? XmlNamespace, ImmutableArray<XamlXmlType> GenericArguments);
internal record ResolvedXmlName(XamlXmlType XmlType, string Name, string? FieldModifier);
internal record ResolvedName(string TypeName, string Name, string? FieldModifier);

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

@ -5,27 +5,40 @@ namespace Avalonia.Generators.Common.Domain;
internal interface IViewResolver
{
ResolvedView? ResolveView(string xaml);
ResolvedViewDocument? ResolveView(string xaml);
}
internal record ResolvedViewInfo(string ClassName, string Namespace, bool IsWindow)
internal record ResolvedViewInfo(string ClassName, string Namespace)
{
public string FullName => $"{Namespace}.{ClassName}";
public override string ToString() => FullName;
}
internal record ResolvedView(string ClassName, string Namespace, bool IsWindow, XamlDocument Xaml)
: ResolvedViewInfo(ClassName, Namespace, IsWindow);
internal record ResolvedViewDocument(string ClassName, string Namespace, XamlDocument Xaml)
: ResolvedViewInfo(ClassName, Namespace);
internal record ResolvedViewWithNames(
internal record ResolvedXmlView(
string ClassName,
string Namespace,
ImmutableArray<ResolvedXmlName> XmlNames)
: ResolvedViewInfo(ClassName, Namespace)
{
public ResolvedXmlView(ResolvedViewInfo info, ImmutableArray<ResolvedXmlName> xmlNames)
: this(info.ClassName, info.Namespace, xmlNames)
{
}
}
internal record ResolvedView(
string ClassName,
string Namespace,
bool IsWindow,
ImmutableArray<ResolvedName> ResolvedNames)
: ResolvedViewInfo(ClassName, Namespace, IsWindow)
ImmutableArray<ResolvedName> Names)
: ResolvedViewInfo(ClassName, Namespace)
{
public ResolvedViewWithNames(ResolvedView view, ImmutableArray<ResolvedName> resolvedNames)
: this(view.ClassName, view.Namespace, view.IsWindow, resolvedNames)
public ResolvedView(ResolvedViewInfo info, bool isWindow, ImmutableArray<ResolvedName> names)
: this(info.ClassName, info.Namespace, isWindow, names)
{
}

36
src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs

@ -5,20 +5,31 @@ using System.Linq;
using Avalonia.Generators.Common.Domain;
using XamlX;
using XamlX.Ast;
using XamlX.TypeSystem;
namespace Avalonia.Generators.Common;
internal class XamlXNameResolver
: INameResolver, IXamlAstVisitor
{
private readonly List<ResolvedName> _items = new();
private readonly Dictionary<string, ResolvedXmlName> _items = new();
public ImmutableArray<ResolvedName> ResolveNames(XamlDocument xaml)
public ImmutableArray<ResolvedXmlName> ResolveXmlNames(XamlDocument xaml)
{
_items.Clear();
xaml.Root.Visit(this);
xaml.Root.VisitChildren(this);
return _items.ToImmutableArray();
return _items.Values.ToImmutableArray();
}
public ResolvedName ResolveName(IXamlType clrType, string name, string? fieldModifier)
{
var typeName = $"{clrType.Namespace}.{clrType.Name}";
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}"))}>";
return new ResolvedName(genericTypeName, name, fieldModifier);
}
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
@ -26,9 +37,7 @@ internal class XamlXNameResolver
if (node is not XamlAstObjectNode objectNode)
return node;
var clrType = objectNode.Type.GetClrType();
if (!clrType.IsAvaloniaStyledElement())
return node;
var xamlType = (XamlAstXmlTypeReference)objectNode.Type;
foreach (var child in objectNode.Children)
{
@ -40,20 +49,17 @@ internal class XamlXNameResolver
propertyValueNode.Values[0] is XamlAstTextNode text)
{
var fieldModifier = TryGetFieldModifier(objectNode);
var typeName = $"{clrType.Namespace}.{clrType.Name}";
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(genericTypeName, text.Text, fieldModifier);
if (_items.Contains(resolvedName))
var resolvedName = new ResolvedXmlName(ConvertType(xamlType), text.Text, fieldModifier);
if (_items.ContainsKey(text.Text))
continue;
_items.Add(resolvedName);
_items.Add(text.Text, resolvedName);
}
}
return node;
static XamlXmlType ConvertType(XamlAstXmlTypeReference type) => new(type.Name, type.XmlNamespace,
type.GenericArguments.Select(ConvertType).ToImmutableArray());
}
void IXamlAstVisitor.Push(IXamlAstNode node) { }

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

@ -10,16 +10,12 @@ using XamlX.TypeSystem;
namespace Avalonia.Generators.Common;
internal class XamlXViewResolver(
IXamlTypeSystem typeSystem,
MiniCompiler compiler,
bool checkTypeValidity = false,
Action<string>? onTypeInvalid = null) : IViewResolver, IXamlAstVisitor
internal class XamlXViewResolver(MiniCompiler compiler) : IViewResolver, IXamlAstVisitor
{
private ResolvedView? _resolvedClass;
private ResolvedViewDocument? _resolvedClass;
private XamlDocument? _xaml;
public ResolvedView? ResolveView(string xaml)
public ResolvedViewDocument? ResolveView(string xaml)
{
_resolvedClass = null;
_xaml = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
@ -32,32 +28,22 @@ internal class XamlXViewResolver(
_xaml.Root.VisitChildren(this);
return _resolvedClass;
}
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
{
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 { Name: "Class", Namespace: XamlNamespaces.Xaml2006 } directive
&& directive.Values[0] is XamlAstTextNode text)
{
var existingType = typeSystem.FindType(text.Text);
if (checkTypeValidity && 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, nameSpace, existingType?.IsAvaloniaWindow() ?? false, _xaml!);
_resolvedClass = new ResolvedViewDocument(className, nameSpace, _xaml!);
return node;
}
}

37
src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs

@ -1,6 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Generators.Common.Domain;
using XamlX.Ast;
using XamlX.Compiler;
using XamlX.Emit;
using XamlX.Transform;
@ -14,7 +16,22 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
public const string AvaloniaXmlnsDefinitionAttribute = "Avalonia.Metadata.XmlnsDefinitionAttribute";
[UnconditionalSuppressMessage("Trimming", "IL2072", Justification = TrimmingMessages.Roslyn)]
public static MiniCompiler CreateDefault(IXamlTypeSystem typeSystem, params string[] additionalTypes)
public static MiniCompiler CreateNoop()
{
var typeSystem = new NoopTypeSystem();
var mappings = new XamlLanguageTypeMappings(typeSystem);
var diagnosticsHandler = new XamlDiagnosticsHandler();
var configuration = new TransformerConfiguration(
typeSystem,
typeSystem.Assemblies.First(),
mappings,
diagnosticsHandler: diagnosticsHandler);
return new MiniCompiler(configuration);
}
[UnconditionalSuppressMessage("Trimming", "IL2072", Justification = TrimmingMessages.Roslyn)]
public static MiniCompiler CreateRoslyn(RoslynTypeSystem typeSystem, params string[] additionalTypes)
{
var mappings = new XamlLanguageTypeMappings(typeSystem);
foreach (var additionalType in additionalTypes)
@ -38,9 +55,18 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
Transformers.Add(new KnownDirectivesTransformer());
Transformers.Add(new XamlIntrinsicsTransformer());
Transformers.Add(new XArgumentsTransformer());
Transformers.Add(new TypeReferenceResolver());
}
public IXamlType ResolveXamlType(XamlXmlType type)
{
var clrTypeRef = TypeReferenceResolver.ResolveType(
new AstTransformationContext(_configuration, null), ToTypeRef(type));
return clrTypeRef.Type;
static XamlAstXmlTypeReference ToTypeRef(XamlXmlType type) => new(EmptyLineInfo.Instance,
type.XmlNamespace, type.Name, type.GenericArguments.Select(ToTypeRef));
}
protected override XamlEmitContext<object, IXamlEmitResult> InitCodeGen(
IFileSource file,
IXamlTypeBuilder<object> declaringType,
@ -48,4 +74,11 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
XamlRuntimeContext<object, IXamlEmitResult> context,
bool needContextLocal) =>
throw new NotSupportedException();
private class EmptyLineInfo : IXamlLineInfo
{
public static IXamlLineInfo Instance { get; } = new EmptyLineInfo();
public int Line { get => 0; set { } }
public int Position { get => 0; set { } }
}
}

22
src/tools/Avalonia.Generators/Compiler/NoopTypeSystem.cs

@ -0,0 +1,22 @@
using System.Collections.Generic;
using XamlX.TypeSystem;
namespace Avalonia.Generators.Compiler;
internal class NoopTypeSystem : IXamlTypeSystem
{
public IEnumerable<IXamlAssembly> Assemblies => [NoopAssembly.Instance];
public IXamlAssembly? FindAssembly(string substring) => null;
public IXamlType? FindType(string name) => XamlPseudoType.Unresolved(name);
public IXamlType? FindType(string name, string assembly) => XamlPseudoType.Unresolved(name);
internal class NoopAssembly : IXamlAssembly
{
public static NoopAssembly Instance { get; } = new();
public bool Equals(IXamlAssembly other) => ReferenceEquals(this, other);
public string Name { get; } = "Noop";
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = [];
public IXamlType? FindType(string fullName) => XamlPseudoType.Unresolved(fullName);
}
}

2
src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs

@ -14,7 +14,7 @@ internal class RoslynTypeSystem : IXamlTypeSystem
private readonly List<IXamlAssembly> _assemblies = new();
private readonly ConcurrentDictionary<string, IXamlType?> _typeCache = new();
public RoslynTypeSystem(CSharpCompilation compilation)
public RoslynTypeSystem(Compilation compilation)
{
_assemblies.Add(new RoslynAssembly(compilation.Assembly));

123
src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameIncrementalGenerator.cs

@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Avalonia.Generators.Common;
using Avalonia.Generators.Common.Domain;
using Avalonia.Generators.Compiler;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace Avalonia.Generators.NameGenerator;
@ -13,6 +13,7 @@ namespace Avalonia.Generators.NameGenerator;
public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator
{
private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup";
private static readonly MiniCompiler s_noopCompiler = MiniCompiler.CreateNoop();
public void Initialize(IncrementalGeneratorInitializationContext context)
{
@ -53,24 +54,12 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator
.Select(static (pair, _) => pair.Left)
.WithTrackingName(TrackingNames.InputXamlFilesProvider);
// Map compilation into readonly XAML type system.
// Which is ONLY updated when any compilation references have changed.
var typeSystem = context.CompilationProvider
.WithComparer(new CompilationReferencesComparer())
.Select(static (compilation, _) => new RoslynTypeSystem((CSharpCompilation)compilation))
.WithTrackingName(TrackingNames.XamlTypeSystemProvider);
// Actual parsing step. We input XAML files one by one, and reuse readonly type system.
// It's detached from the up-to-date compilation info, we can't access type information here yet.
// Otherwise slow parsing would slow down IDE on any file edited.
// This pipeline step only depends on the input xaml tiles and type system (which depends on assembly references, but not actual types).
var partialFilesInfo = xamlFiles.Combine(typeSystem)
.Select(static (pair, cancellationToken) =>
// Actual parsing step. We input XAML files one by one, but don't resolve any types.
// That's why we use NoOp type system here, allowing parsing to run detached from C# compilation.
// Otherwise we would need to re-parse XAML on any C# file changed.
var parsedXamlClasses = xamlFiles
.Select(static (file, cancellationToken) =>
{
var (file, types) = pair;
var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
var canAttachDevTools = types.FindAssembly("Avalonia.Diagnostics") is not null;
var text = file.GetText(cancellationToken);
var diagnostics = new List<DiagnosticDescriptor>();
if (text is not null)
@ -78,10 +67,7 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator
try
{
var xaml = text.ToString();
var viewResolver = new XamlXViewResolver(
types, compiler, true,
invalidType =>
diagnostics.Add(GeneratorExtensions.NameGeneratorInvalidType(invalidType)));
var viewResolver = new XamlXViewResolver(s_noopCompiler);
var view = viewResolver.ResolveView(xaml);
if (view is null)
{
@ -89,26 +75,83 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator
}
var nameResolver = new XamlXNameResolver();
var resolvedNames = nameResolver.ResolveNames(view.Xaml);
var xmlNames = nameResolver.ResolveXmlNames(view.Xaml);
return new PartialClassInfo(
new ResolvedViewWithNames(view, resolvedNames),
canAttachDevTools,
return new XmlClassInfo(
new ResolvedXmlView(view, xmlNames),
diagnostics.ToImmutableArray());
}
catch (Exception ex)
{
diagnostics.Add(GeneratorExtensions.NameGeneratorUnhandledError(ex));
return new PartialClassInfo(null, canAttachDevTools, diagnostics.ToImmutableArray());
return new XmlClassInfo(null, diagnostics.ToImmutableArray());
}
}
return null;
})
.Where(request => request is not null)
.WithTrackingName(TrackingNames.ParsedXamlPartialFiles);
.WithTrackingName(TrackingNames.ParsedXamlClasses);
// Note: this step will be re-executed on any C# file changes.
// As much as possible heavy tasks should be moved outside of this step, like XAML parsing.
var resolvedNames = parsedXamlClasses.Collect()
.Combine(context.CompilationProvider)
.SelectMany(static (pair, _) =>
{
var (classes, compilation) = pair;
var roslynTypeSystem = new RoslynTypeSystem(compilation);
var compiler = MiniCompiler.CreateRoslyn(roslynTypeSystem, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
var hasDevToolsReference = compilation.ReferencedAssemblyNames.Any(r => r.Name == "Avalonia.Diagnostics");
var nameResolver = new XamlXNameResolver();
var outputs = new List<ResolvedClassInfo>();
foreach (var classInfo in classes)
{
var diagnostics = new List<DiagnosticDescriptor>(classInfo!.Diagnostics);
if (classInfo.XmlView is { } xmlView)
{
var type = roslynTypeSystem.FindType(xmlView.FullName);
ResolvedView? view = null;
if (type is null)
{
diagnostics.Add(GeneratorExtensions.NameGeneratorInvalidType(xmlView.FullName));
}
else if (type.IsAvaloniaStyledElement())
{
var resolvedNames = new List<ResolvedName>();
foreach (var xmlName in xmlView.XmlNames)
{
try
{
var clrType = compiler.ResolveXamlType(xmlName.XmlType);
if (!clrType.IsAvaloniaStyledElement())
{
continue;
}
resolvedNames.Add(nameResolver
.ResolveName(clrType, xmlName.Name, xmlName.FieldModifier));
}
catch (Exception ex)
{
diagnostics.Add(GeneratorExtensions.NameGeneratorUnhandledError(ex));
}
}
view = new ResolvedView(xmlView, type.IsAvaloniaWindow(), resolvedNames.ToImmutableArray());
}
context.RegisterSourceOutput(partialFilesInfo.Combine(options), static (context, pair) =>
outputs.Add(new ResolvedClassInfo(view, hasDevToolsReference, diagnostics.ToImmutableArray()));
}
}
return outputs;
})
.WithTrackingName(TrackingNames.ResolvedNamesProvider);
context.RegisterSourceOutput(resolvedNames.Combine(options), static (context, pair) =>
{
var (info, options) = pair;
@ -117,7 +160,7 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator
context.Report(diagnostic);
}
if (info.ViewInfo is { } view && options.AvaloniaNameGeneratorFilterByNamespace.Matches(view.Namespace))
if (info.View is { } view && options.AvaloniaNameGeneratorFilterByNamespace.Matches(view.Namespace))
{
ICodeGenerator codeGenerator = options.AvaloniaNameGeneratorBehavior switch
{
@ -138,17 +181,21 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator
};
var generatedPartialClass = codeGenerator.GenerateCode(
info.ViewInfo.ClassName,
info.ViewInfo.Namespace,
info.ViewInfo.ResolvedNames);
info.View.ClassName,
info.View.Namespace,
info.View.Names);
context.AddSource(fileName, generatedPartialClass);
}
});
}
}
internal record PartialClassInfo(
ResolvedViewWithNames? ViewInfo,
bool CanAttachDevTools,
ImmutableArray<DiagnosticDescriptor> Diagnostics);
internal record XmlClassInfo(
ResolvedXmlView? XmlView,
ImmutableArray<DiagnosticDescriptor> Diagnostics);
internal record ResolvedClassInfo(
ResolvedView? View,
bool CanAttachDevTools,
ImmutableArray<DiagnosticDescriptor> Diagnostics);
}

4
src/tools/Avalonia.Generators/NameGenerator/TrackingNames.cs

@ -2,8 +2,8 @@ namespace Avalonia.Generators.NameGenerator;
internal static class TrackingNames
{
public const string XamlTypeSystemProvider = nameof(XamlTypeSystemProvider);
public const string ResolvedNamesProvider = nameof(ResolvedNamesProvider);
public const string XamlGeneratorOptionsProvider = nameof(XamlGeneratorOptionsProvider);
public const string InputXamlFilesProvider = nameof(InputXamlFilesProvider);
public const string ParsedXamlPartialFiles = nameof(ParsedXamlPartialFiles);
public const string ParsedXamlClasses = nameof(ParsedXamlClasses);
}

24
tests/Avalonia.Generators.Tests/CompilationUtils.cs

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Generators.Common;
using Avalonia.Generators.Common.Domain;
using Avalonia.Generators.Compiler;
using Microsoft.CodeAnalysis;
namespace Avalonia.Generators.Tests;
internal static class CompilationUtils
{
internal static IEnumerable<ResolvedName> ResolveNames(this IEnumerable<ResolvedXmlName> names, Compilation compilation, XamlXNameResolver nameResolver)
{
var compiler = MiniCompiler.CreateRoslyn(new RoslynTypeSystem(compilation), MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
return names
.Select(xmlName =>
{
var clrType = compiler.ResolveXamlType(xmlName.XmlType);
return (clrType, nameResolver.ResolveName(clrType, xmlName.Name, xmlName.FieldModifier));
})
.Where(t => t.clrType.IsAvaloniaStyledElement())
.Select(t => t.Item2);
}
}

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

@ -1,5 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Generators.Common;
using Avalonia.Generators.Common.Domain;
using Avalonia.Generators.Compiler;
using Avalonia.Generators.NameGenerator;
using Avalonia.Generators.Tests.InitializeComponent.GeneratedInitializeComponent;
@ -30,28 +32,28 @@ public class InitializeComponentTests
bool devToolsMode)
{
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));
// Step 1: parse XAML as xml nodes, without any type information.
var classResolver = new XamlXViewResolver(MiniCompiler.CreateNoop());
var xaml = await View.Load(markup);
var classInfo = classResolver.ResolveView(xaml);
Assert.NotNull(classInfo);
var nameResolver = new XamlXNameResolver();
var names = nameResolver.ResolveNames(classInfo.Xaml);
var names = nameResolver.ResolveXmlNames(classInfo.Xaml);
// Step 2: use compilation context to resolve types
var compilation =
View.CreateAvaloniaCompilation(excluded)
.WithCustomTextBox();
var resolvedNames = names.ResolveNames(compilation, nameResolver).ToArray();
// Step 3: run generator
var generator = new InitializeComponentCodeGenerator(devToolsMode);
var generatorVersion = typeof(InitializeComponentCodeGenerator).Assembly.GetName().Version?.ToString();
var code = generator
.GenerateCode("SampleView", "Sample.App", names)
.GenerateCode("SampleView", "Sample.App", resolvedNames)
.Replace("\r", string.Empty);
var expected = (await InitializeComponentCode.Load(expectation))

4
tests/Avalonia.Generators.Tests/MiniCompilerTests.cs

@ -21,7 +21,7 @@ public class MiniCompilerTests
{
var xaml = XDocumentXamlParser.Parse(MiniValidXaml);
var compilation = CreateBasicCompilation(MiniClass);
MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation)).Transform(xaml);
MiniCompiler.CreateRoslyn(new RoslynTypeSystem(compilation)).Transform(xaml);
Assert.NotNull(xaml.Root);
}
@ -31,7 +31,7 @@ public class MiniCompilerTests
{
var xaml = XDocumentXamlParser.Parse(AvaloniaXaml);
var compilation = View.CreateAvaloniaCompilation();
MiniCompiler.CreateDefault(new RoslynTypeSystem(compilation)).Transform(xaml);
MiniCompiler.CreateRoslyn(new RoslynTypeSystem(compilation)).Transform(xaml);
Assert.NotNull(xaml.Root);
}

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

@ -1,5 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Generators.Common;
using Avalonia.Generators.Common.Domain;
using Avalonia.Generators.Compiler;
using Avalonia.Generators.NameGenerator;
using Avalonia.Generators.Tests.OnlyProperties.GeneratedCode;
@ -25,27 +27,27 @@ public class OnlyPropertiesTests
[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 classResolver = new XamlXViewResolver(
new RoslynTypeSystem(compilation),
MiniCompiler.CreateDefault(
new RoslynTypeSystem(compilation),
MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
// Step 1: parse XAML as xml nodes, without any type information.
var classResolver = new XamlXViewResolver(MiniCompiler.CreateNoop());
var xaml = await View.Load(markup);
var classInfo = classResolver.ResolveView(xaml);
Assert.NotNull(classInfo);
var nameResolver = new XamlXNameResolver();
var names = nameResolver.ResolveNames(classInfo.Xaml);
var names = nameResolver.ResolveXmlNames(classInfo.Xaml);
// Step 2: use compilation context to resolve types
var compilation =
View.CreateAvaloniaCompilation()
.WithCustomTextBox();
var resolvedNames = names.ResolveNames(compilation, nameResolver).ToArray();
// Step 3: run generator
var generator = new OnlyPropertiesCodeGenerator();
var generatorVersion = typeof(OnlyPropertiesCodeGenerator).Assembly.GetName().Version?.ToString();
var code = generator
.GenerateCode("SampleView", "Sample.App", names)
.GenerateCode("SampleView", "Sample.App", resolvedNames)
.Replace("\r", string.Empty);
var expected = (await OnlyPropertiesCode.Load(expectation))

10
tests/Avalonia.Generators.Tests/XamlXClassResolverTests.cs

@ -23,15 +23,7 @@ public class XamlXClassResolverTests
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()
.WithBaseView();
var types = new RoslynTypeSystem(compilation);
var resolver = new XamlXViewResolver(
types,
MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
var resolver = new XamlXViewResolver(MiniCompiler.CreateNoop());
var resolvedClass = resolver.ResolveView(xaml);
Assert.NotNull(resolvedClass);

23
tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Generators.Common;
@ -6,6 +7,7 @@ using Avalonia.Generators.Common.Domain;
using Avalonia.Generators.Compiler;
using Avalonia.ReactiveUI;
using Avalonia.Generators.Tests.Views;
using Microsoft.CodeAnalysis;
using Xunit;
namespace Avalonia.Generators.Tests;
@ -123,20 +125,19 @@ public class XamlXNameResolverTests
private static IReadOnlyList<ResolvedName> ResolveNames(string xaml)
{
var nameResolver = new XamlXNameResolver();
// Step 1: parse XAML as xml nodes, without any type information.
var classResolver = new XamlXViewResolver(MiniCompiler.CreateNoop());
var classInfo = classResolver.ResolveView(xaml);
Assert.NotNull(classInfo);
var names = nameResolver.ResolveXmlNames(classInfo.Xaml);
// Step 2: use compilation context to resolve types
var compilation =
View.CreateAvaloniaCompilation()
.WithCustomTextBox()
.WithBaseView();
var classResolver = new XamlXViewResolver(
new RoslynTypeSystem(compilation),
MiniCompiler.CreateDefault(
new RoslynTypeSystem(compilation),
MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
var classInfo = classResolver.ResolveView(xaml);
Assert.NotNull(classInfo);
var nameResolver = new XamlXNameResolver();
return nameResolver.ResolveNames(classInfo.Xaml);
return names.ResolveNames(compilation, nameResolver).ToArray();
}
}

Loading…
Cancel
Save