Browse Source

fix: Don't parse XAML twice, bring back the extensions

pull/10407/head
artyom 5 years ago
parent
commit
3fdacad616
  1. 13
      src/Avalonia.NameGenerator.Tests/FindControlNameGeneratorTests.cs
  2. 79
      src/Avalonia.NameGenerator.Tests/XamlXNameResolverTests.cs
  3. 6
      src/Avalonia.NameGenerator/AvaloniaNameSourceGenerator.cs
  4. 6
      src/Avalonia.NameGenerator/Domain/IClassResolver.cs
  5. 3
      src/Avalonia.NameGenerator/Domain/INameResolver.cs
  6. 20
      src/Avalonia.NameGenerator/Generator/AvaloniaNameGenerator.cs
  7. 2
      src/Avalonia.NameGenerator/Generator/FindControlCodeGenerator.cs
  8. 12
      src/Avalonia.NameGenerator/Generator/XamlXClassResolver.cs
  9. 17
      src/Avalonia.NameGenerator/Generator/XamlXNameResolver.cs
  10. 31
      src/Avalonia.NameGenerator/GeneratorContextExtensions.cs

13
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);

79
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<ResolvedName> 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);
}
}
}

6
src/Avalonia.NameGenerator/NameReferenceGenerator.cs → 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
{

6
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;
}
}
}

3
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<ResolvedName> ResolveNames(string xaml);
IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml);
}
internal record ResolvedName

20
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;
}
}
}
}

2
src/Avalonia.NameGenerator/Generator/FindControlNameGenerator.cs → 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<ResolvedName> names)
{

12
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<string> _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<string, string>
_xaml = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
{
{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;
}
}

17
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<ResolvedName> _items = new();
private readonly MiniCompiler _compiler;
public XamlXNameResolver(MiniCompiler compiler) => _compiler = compiler;
public IReadOnlyList<ResolvedName> ResolveNames(string xaml)
public IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml)
{
var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
{
{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;
}

31
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();
}
}
Loading…
Cancel
Save