From 3fdacad616f4a367f6308548ff1aaab20e3e7bd9 Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 10 Feb 2021 01:20:09 +0300 Subject: [PATCH] fix: Don't parse XAML twice, bring back the extensions --- .../FindControlNameGeneratorTests.cs | 13 ++- .../XamlXNameResolverTests.cs | 79 ++++++------------- ...ator.cs => AvaloniaNameSourceGenerator.cs} | 6 +- .../Domain/IClassResolver.cs | 6 +- .../Domain/INameResolver.cs | 3 +- .../Generator/AvaloniaNameGenerator.cs | 20 +---- ...nerator.cs => FindControlCodeGenerator.cs} | 2 +- .../Generator/XamlXClassResolver.cs | 12 +-- .../Generator/XamlXNameResolver.cs | 17 +--- .../GeneratorContextExtensions.cs | 31 ++++++++ 10 files changed, 89 insertions(+), 100 deletions(-) rename src/Avalonia.NameGenerator/{NameReferenceGenerator.cs => AvaloniaNameSourceGenerator.cs} (94%) rename src/Avalonia.NameGenerator/Generator/{FindControlNameGenerator.cs => FindControlCodeGenerator.cs} (92%) create mode 100644 src/Avalonia.NameGenerator/GeneratorContextExtensions.cs diff --git a/src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs b/src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs index f77abb663f..461af0da72 100644 --- a/src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs +++ b/src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs @@ -23,19 +23,24 @@ namespace Avalonia.NameGenerator.Tests [InlineData(Code.FieldModifier, View.FieldModifier)] public async Task Should_Generate_FindControl_Refs_From_Avalonia_Markup_File(string expectation, string markup) { - var xaml = await View.Load(markup); var compilation = View.CreateAvaloniaCompilation() .WithCustomTextBox(); - var resolver = new XamlXNameResolver( + var classResolver = new XamlXClassResolver( + new RoslynTypeSystem(compilation), MiniCompiler.CreateDefault( new RoslynTypeSystem(compilation), MiniCompiler.AvaloniaXmlnsDefinitionAttribute)); - var generator = new FindControlNameGenerator(); + var xaml = await View.Load(markup); + var classInfo = classResolver.ResolveClass(xaml); + var nameResolver = new XamlXNameResolver(); + var names = nameResolver.ResolveNames(classInfo.Xaml); + + var generator = new FindControlCodeGenerator(); var code = generator - .GenerateCode("SampleView", "Sample.App", resolver.ResolveNames(xaml)) + .GenerateCode("SampleView", "Sample.App", names) .Replace("\r", string.Empty); var expected = await Code.Load(expectation); diff --git a/src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs b/src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs index 2ea136be68..88b0788628 100644 --- a/src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs +++ b/src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs @@ -1,6 +1,8 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.NameGenerator.Compiler; +using Avalonia.NameGenerator.Domain; using Avalonia.NameGenerator.Generator; using Avalonia.ReactiveUI; using Avalonia.NameGenerator.Tests.Views; @@ -17,14 +19,7 @@ namespace Avalonia.NameGenerator.Tests public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Control(string resource) { var xaml = await View.Load(resource); - var compilation = View.CreateAvaloniaCompilation(); - - var resolver = new XamlXNameResolver( - MiniCompiler.CreateDefault( - new RoslynTypeSystem(compilation), - MiniCompiler.AvaloniaXmlnsDefinitionAttribute)); - - var controls = resolver.ResolveNames(xaml); + var controls = ResolveNames(xaml); Assert.NotEmpty(controls); Assert.Equal(1, controls.Count); @@ -38,14 +33,7 @@ namespace Avalonia.NameGenerator.Tests public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Named_Controls(string resource) { var xaml = await View.Load(resource); - var compilation = View.CreateAvaloniaCompilation(); - - var resolver = new XamlXNameResolver( - MiniCompiler.CreateDefault( - new RoslynTypeSystem(compilation), - MiniCompiler.AvaloniaXmlnsDefinitionAttribute)); - - var controls = resolver.ResolveNames(xaml); + var controls = ResolveNames(xaml); Assert.NotEmpty(controls); Assert.Equal(3, controls.Count); @@ -60,17 +48,8 @@ namespace Avalonia.NameGenerator.Tests [Fact] public async Task Should_Resolve_Types_From_Avalonia_Markup_File_With_Custom_Controls() { - var compilation = - View.CreateAvaloniaCompilation() - .WithCustomTextBox(); - var xaml = await View.Load(View.CustomControls); - var resolver = new XamlXNameResolver( - MiniCompiler.CreateDefault( - new RoslynTypeSystem(compilation), - MiniCompiler.AvaloniaXmlnsDefinitionAttribute)); - - var controls = resolver.ResolveNames(xaml); + var controls = ResolveNames(xaml); Assert.NotEmpty(controls); Assert.Equal(3, controls.Count); @@ -86,14 +65,7 @@ namespace Avalonia.NameGenerator.Tests public async Task Should_Not_Resolve_Named_Controls_From_Avalonia_Markup_File_Without_Named_Controls() { var xaml = await View.Load(View.NoNamedControls); - var compilation = View.CreateAvaloniaCompilation(); - - var resolver = new XamlXNameResolver( - MiniCompiler.CreateDefault( - new RoslynTypeSystem(compilation), - MiniCompiler.AvaloniaXmlnsDefinitionAttribute)); - - var controls = resolver.ResolveNames(xaml); + var controls = ResolveNames(xaml); Assert.Empty(controls); } @@ -102,14 +74,7 @@ namespace Avalonia.NameGenerator.Tests public async Task Should_Not_Resolve_Elements_From_DataTemplates() { var xaml = await View.Load(View.DataTemplates); - var compilation = View.CreateAvaloniaCompilation(); - - var resolver = new XamlXNameResolver( - MiniCompiler.CreateDefault( - new RoslynTypeSystem(compilation), - MiniCompiler.AvaloniaXmlnsDefinitionAttribute)); - - var controls = resolver.ResolveNames(xaml); + var controls = ResolveNames(xaml); Assert.NotEmpty(controls); Assert.Equal(2, controls.Count); @@ -122,17 +87,8 @@ namespace Avalonia.NameGenerator.Tests [Fact] public async Task Should_Resolve_Names_From_Complex_Views() { - var compilation = - View.CreateAvaloniaCompilation() - .WithCustomTextBox(); - var xaml = await View.Load(View.SignUpView); - var resolver = new XamlXNameResolver( - MiniCompiler.CreateDefault( - new RoslynTypeSystem(compilation), - MiniCompiler.AvaloniaXmlnsDefinitionAttribute)); - - var controls = resolver.ResolveNames(xaml); + var controls = ResolveNames(xaml); Assert.NotEmpty(controls); Assert.Equal(9, controls.Count); @@ -146,5 +102,22 @@ namespace Avalonia.NameGenerator.Tests Assert.Equal("SignUpButton", controls[7].Name); Assert.Equal("CompoundValidation", controls[8].Name); } + + private static IReadOnlyList ResolveNames(string xaml) + { + var compilation = + View.CreateAvaloniaCompilation() + .WithCustomTextBox(); + + var classResolver = new XamlXClassResolver( + new RoslynTypeSystem(compilation), + MiniCompiler.CreateDefault( + new RoslynTypeSystem(compilation), + MiniCompiler.AvaloniaXmlnsDefinitionAttribute)); + + var classInfo = classResolver.ResolveClass(xaml); + var nameResolver = new XamlXNameResolver(); + return nameResolver.ResolveNames(classInfo.Xaml); + } } } \ No newline at end of file diff --git a/src/Avalonia.NameGenerator/NameReferenceGenerator.cs b/src/Avalonia.NameGenerator/AvaloniaNameSourceGenerator.cs similarity index 94% rename from src/Avalonia.NameGenerator/NameReferenceGenerator.cs rename to src/Avalonia.NameGenerator/AvaloniaNameSourceGenerator.cs index 710d907d9b..3cf8440f9e 100644 --- a/src/Avalonia.NameGenerator/NameReferenceGenerator.cs +++ b/src/Avalonia.NameGenerator/AvaloniaNameSourceGenerator.cs @@ -11,7 +11,7 @@ using Microsoft.CodeAnalysis.CSharp; namespace Avalonia.NameGenerator { [Generator] - public class NameReferenceGenerator : ISourceGenerator + public class AvaloniaNameSourceGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { } @@ -24,8 +24,8 @@ namespace Avalonia.NameGenerator INameGenerator avaloniaNameGenerator = new AvaloniaNameGenerator( new XamlXClassResolver(types, compiler, true, type => ReportInvalidType(context, type)), - new XamlXNameResolver(compiler), - new FindControlNameGenerator()); + new XamlXNameResolver(), + new FindControlCodeGenerator()); try { diff --git a/src/Avalonia.NameGenerator/Domain/IClassResolver.cs b/src/Avalonia.NameGenerator/Domain/IClassResolver.cs index 5f83e2cca3..e8ef0e1027 100644 --- a/src/Avalonia.NameGenerator/Domain/IClassResolver.cs +++ b/src/Avalonia.NameGenerator/Domain/IClassResolver.cs @@ -1,3 +1,5 @@ +using XamlX.Ast; + namespace Avalonia.NameGenerator.Domain { internal interface IClassResolver @@ -7,13 +9,15 @@ namespace Avalonia.NameGenerator.Domain internal record ResolvedClass { + public XamlDocument Xaml { get; } public string ClassName { get; } public string NameSpace { get; } - public ResolvedClass(string className, string nameSpace) + public ResolvedClass(string className, string nameSpace, XamlDocument xaml) { ClassName = className; NameSpace = nameSpace; + Xaml = xaml; } } } \ No newline at end of file diff --git a/src/Avalonia.NameGenerator/Domain/INameResolver.cs b/src/Avalonia.NameGenerator/Domain/INameResolver.cs index 08623c20a1..09f9037403 100644 --- a/src/Avalonia.NameGenerator/Domain/INameResolver.cs +++ b/src/Avalonia.NameGenerator/Domain/INameResolver.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; +using XamlX.Ast; namespace Avalonia.NameGenerator.Domain { internal interface INameResolver { - IReadOnlyList ResolveNames(string xaml); + IReadOnlyList ResolveNames(XamlDocument xaml); } internal record ResolvedName diff --git a/src/Avalonia.NameGenerator/Generator/AvaloniaNameGenerator.cs b/src/Avalonia.NameGenerator/Generator/AvaloniaNameGenerator.cs index f35e256db4..0512a8bba3 100644 --- a/src/Avalonia.NameGenerator/Generator/AvaloniaNameGenerator.cs +++ b/src/Avalonia.NameGenerator/Generator/AvaloniaNameGenerator.cs @@ -28,12 +28,10 @@ namespace Avalonia.NameGenerator.Generator let xaml = file.GetText()!.ToString() let type = _classes.ResolveClass(xaml) where type != null - let className = type.ClassName - let nameSpace = type.NameSpace - select new ResolvedView(className, nameSpace, xaml); + select type; var query = - from view in resolveViewsQuery.ToList() + from view in resolveViewsQuery let names = _names.ResolveNames(view.Xaml) let code = _code.GenerateCode(view.ClassName, view.NameSpace, names) let fileName = $"{view.ClassName}.g.cs" @@ -41,19 +39,5 @@ namespace Avalonia.NameGenerator.Generator return query.ToList(); } - - private record ResolvedView - { - public string ClassName { get; } - public string NameSpace { get; } - public string Xaml { get; } - - public ResolvedView(string className, string nameSpace, string xaml) - { - ClassName = className; - NameSpace = nameSpace; - Xaml = xaml; - } - } } } \ No newline at end of file diff --git a/src/Avalonia.NameGenerator/Generator/FindControlNameGenerator.cs b/src/Avalonia.NameGenerator/Generator/FindControlCodeGenerator.cs similarity index 92% rename from src/Avalonia.NameGenerator/Generator/FindControlNameGenerator.cs rename to src/Avalonia.NameGenerator/Generator/FindControlCodeGenerator.cs index 971ce84407..a6dd275d61 100644 --- a/src/Avalonia.NameGenerator/Generator/FindControlNameGenerator.cs +++ b/src/Avalonia.NameGenerator/Generator/FindControlCodeGenerator.cs @@ -4,7 +4,7 @@ using Avalonia.NameGenerator.Domain; namespace Avalonia.NameGenerator.Generator { - internal class FindControlNameGenerator : ICodeGenerator + internal class FindControlCodeGenerator : ICodeGenerator { public string GenerateCode(string className, string nameSpace, IEnumerable names) { diff --git a/src/Avalonia.NameGenerator/Generator/XamlXClassResolver.cs b/src/Avalonia.NameGenerator/Generator/XamlXClassResolver.cs index 319f097382..a879d5466e 100644 --- a/src/Avalonia.NameGenerator/Generator/XamlXClassResolver.cs +++ b/src/Avalonia.NameGenerator/Generator/XamlXClassResolver.cs @@ -15,7 +15,9 @@ namespace Avalonia.NameGenerator.Generator private readonly MiniCompiler _compiler; private readonly bool _checkTypeValidity; private readonly Action _onTypeInvalid; + private ResolvedClass _resolvedClass; + private XamlDocument _xaml; public XamlXClassResolver( RoslynTypeSystem typeSystem, @@ -32,14 +34,14 @@ namespace Avalonia.NameGenerator.Generator public ResolvedClass ResolveClass(string xaml) { _resolvedClass = null; - var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary + _xaml = XDocumentXamlParser.Parse(xaml, new Dictionary { {XamlNamespaces.Blend2008, XamlNamespaces.Blend2008} }); - _compiler.Transform(parsed); - parsed.Root.Visit(this); - parsed.Root.VisitChildren(this); + _compiler.Transform(_xaml); + _xaml.Root.Visit(this); + _xaml.Root.VisitChildren(this); return _resolvedClass; } @@ -77,7 +79,7 @@ namespace Avalonia.NameGenerator.Generator var split = text.Text.Split('.'); var nameSpace = string.Join(".", split.Take(split.Length - 1)); var className = split.Last(); - _resolvedClass = new ResolvedClass(className, nameSpace); + _resolvedClass = new ResolvedClass(className, nameSpace, _xaml); return node; } } diff --git a/src/Avalonia.NameGenerator/Generator/XamlXNameResolver.cs b/src/Avalonia.NameGenerator/Generator/XamlXNameResolver.cs index 532ac78543..5f72aed48a 100644 --- a/src/Avalonia.NameGenerator/Generator/XamlXNameResolver.cs +++ b/src/Avalonia.NameGenerator/Generator/XamlXNameResolver.cs @@ -1,30 +1,19 @@ using System.Collections.Generic; using System.Linq; -using Avalonia.NameGenerator.Compiler; using Avalonia.NameGenerator.Domain; using XamlX; using XamlX.Ast; -using XamlX.Parsers; namespace Avalonia.NameGenerator.Generator { internal class XamlXNameResolver : INameResolver, IXamlAstVisitor { private readonly List _items = new(); - private readonly MiniCompiler _compiler; - public XamlXNameResolver(MiniCompiler compiler) => _compiler = compiler; - - public IReadOnlyList ResolveNames(string xaml) + public IReadOnlyList ResolveNames(XamlDocument xaml) { - var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary - { - {XamlNamespaces.Blend2008, XamlNamespaces.Blend2008} - }); - - _compiler.Transform(parsed); - parsed.Root.Visit(this); - parsed.Root.VisitChildren(this); + xaml.Root.Visit(this); + xaml.Root.VisitChildren(this); return _items; } diff --git a/src/Avalonia.NameGenerator/GeneratorContextExtensions.cs b/src/Avalonia.NameGenerator/GeneratorContextExtensions.cs new file mode 100644 index 0000000000..f9b4cd6b2d --- /dev/null +++ b/src/Avalonia.NameGenerator/GeneratorContextExtensions.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Avalonia.NameGenerator +{ + internal static class GeneratorContextExtensions + { + private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup"; + + public static string GetMSBuildProperty( + this GeneratorExecutionContext context, + string name, + string defaultValue = "") + { + context.AnalyzerConfigOptions.GlobalOptions.TryGetValue($"build_property.{name}", out var value); + return value ?? defaultValue; + } + + public static string[] GetMSBuildItems(this GeneratorExecutionContext context, string name) + => context + .AdditionalFiles + .Where(f => + context + .AnalyzerConfigOptions + .GetOptions(f) + .TryGetValue(SourceItemGroupMetadata, out var sourceItemGroup) + && sourceItemGroup == name) + .Select(f => f.Path) + .ToArray(); + } +} \ No newline at end of file