diff --git a/samples/Generators.Sandbox/Controls/SignUpView.xaml b/samples/Generators.Sandbox/Controls/SignUpView.xaml index c126f36f53..1cfb581cf9 100644 --- a/samples/Generators.Sandbox/Controls/SignUpView.xaml +++ b/samples/Generators.Sandbox/Controls/SignUpView.xaml @@ -8,7 +8,7 @@ Watermark="Please, enter user name..." UseFloatingWatermark="True" /> ResolveNames(XamlDocument xaml); + ImmutableArray ResolveXmlNames(XamlDocument xaml); + ResolvedName ResolveName(IXamlType xamlType, string name, string? fieldModifier); } +internal record XamlXmlType(string Name, string? XmlNamespace, ImmutableArray GenericArguments); + +internal record ResolvedXmlName(XamlXmlType XmlType, string Name, string? FieldModifier); internal record ResolvedName(string TypeName, string Name, string? FieldModifier); diff --git a/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs b/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs index 3688c35dfa..23c4f7fdda 100644 --- a/src/tools/Avalonia.Generators/Common/Domain/IViewResolver.cs +++ b/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 XmlNames) + : ResolvedViewInfo(ClassName, Namespace) +{ + public ResolvedXmlView(ResolvedViewInfo info, ImmutableArray xmlNames) + : this(info.ClassName, info.Namespace, xmlNames) + { + + } +} + +internal record ResolvedView( string ClassName, string Namespace, bool IsWindow, - ImmutableArray ResolvedNames) - : ResolvedViewInfo(ClassName, Namespace, IsWindow) + ImmutableArray Names) + : ResolvedViewInfo(ClassName, Namespace) { - public ResolvedViewWithNames(ResolvedView view, ImmutableArray resolvedNames) - : this(view.ClassName, view.Namespace, view.IsWindow, resolvedNames) + public ResolvedView(ResolvedViewInfo info, bool isWindow, ImmutableArray names) + : this(info.ClassName, info.Namespace, isWindow, names) { } diff --git a/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs b/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs index 11e926ac57..a6c111e99a 100644 --- a/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs +++ b/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 _items = new(); + private readonly Dictionary _items = new(); - public ImmutableArray ResolveNames(XamlDocument xaml) + public ImmutableArray 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) { } diff --git a/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs b/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs index 64432a2687..599fbdee36 100644 --- a/src/tools/Avalonia.Generators/Common/XamlXViewResolver.cs +++ b/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? 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 @@ -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; } } diff --git a/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs b/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs index 1be3e6e8a1..3e3bded3b3 100644 --- a/src/tools/Avalonia.Generators/Compiler/MiniCompiler.cs +++ b/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 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 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 InitCodeGen( IFileSource file, IXamlTypeBuilder declaringType, @@ -48,4 +74,11 @@ internal sealed class MiniCompiler : XamlCompiler XamlRuntimeContext 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 { } } + } } diff --git a/src/tools/Avalonia.Generators/Compiler/NoopTypeSystem.cs b/src/tools/Avalonia.Generators/Compiler/NoopTypeSystem.cs new file mode 100644 index 0000000000..b9bc801a52 --- /dev/null +++ b/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 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 CustomAttributes { get; } = []; + public IXamlType? FindType(string fullName) => XamlPseudoType.Unresolved(fullName); + } +} + diff --git a/src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs b/src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs index b102a93a92..04e0e594c4 100644 --- a/src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs +++ b/src/tools/Avalonia.Generators/Compiler/RoslynTypeSystem.cs @@ -14,7 +14,7 @@ internal class RoslynTypeSystem : IXamlTypeSystem private readonly List _assemblies = new(); private readonly ConcurrentDictionary _typeCache = new(); - public RoslynTypeSystem(CSharpCompilation compilation) + public RoslynTypeSystem(Compilation compilation) { _assemblies.Add(new RoslynAssembly(compilation.Assembly)); diff --git a/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameIncrementalGenerator.cs b/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameIncrementalGenerator.cs index 14c3dd5a7e..3dd889e9ec 100644 --- a/src/tools/Avalonia.Generators/NameGenerator/AvaloniaNameIncrementalGenerator.cs +++ b/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(); 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(); + foreach (var classInfo in classes) + { + var diagnostics = new List(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(); + 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 Diagnostics); + internal record XmlClassInfo( + ResolvedXmlView? XmlView, + ImmutableArray Diagnostics); + + internal record ResolvedClassInfo( + ResolvedView? View, + bool CanAttachDevTools, + ImmutableArray Diagnostics); +} diff --git a/src/tools/Avalonia.Generators/NameGenerator/TrackingNames.cs b/src/tools/Avalonia.Generators/NameGenerator/TrackingNames.cs index 7c691e3f21..0ed928a532 100644 --- a/src/tools/Avalonia.Generators/NameGenerator/TrackingNames.cs +++ b/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); } diff --git a/tests/Avalonia.Generators.Tests/CompilationUtils.cs b/tests/Avalonia.Generators.Tests/CompilationUtils.cs new file mode 100644 index 0000000000..3094b65b28 --- /dev/null +++ b/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 ResolveNames(this IEnumerable 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); + } +} diff --git a/tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs b/tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs index 4e7a465fda..9e2a5bd6d9 100644 --- a/tests/Avalonia.Generators.Tests/InitializeComponent/InitializeComponentTests.cs +++ b/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)) diff --git a/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs b/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs index a54fc7bf12..041e830674 100644 --- a/tests/Avalonia.Generators.Tests/MiniCompilerTests.cs +++ b/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); } diff --git a/tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs b/tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs index 20e4217c84..d1efa80401 100644 --- a/tests/Avalonia.Generators.Tests/OnlyProperties/OnlyPropertiesTests.cs +++ b/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)) diff --git a/tests/Avalonia.Generators.Tests/XamlXClassResolverTests.cs b/tests/Avalonia.Generators.Tests/XamlXClassResolverTests.cs index 8d6db6ce47..b1e5e28f3f 100644 --- a/tests/Avalonia.Generators.Tests/XamlXClassResolverTests.cs +++ b/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); diff --git a/tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs b/tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs index 46bd75d643..27d8f9d264 100644 --- a/tests/Avalonia.Generators.Tests/XamlXNameResolverTests.cs +++ b/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 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(); } }