Migrate AvaloniaNameSourceGenerator to IIncrementalGenerator (#19216)
* Add AnalyzerProject.targets targets * PrivateAssets on Workspaces.Common * Migrate AvaloniaNameSourceGenerator to IIncrementalGenerator * Remove outdated lins in the generator readme * Add GlobPattern.ToString * Fix tests * Formatting * Redo pipeline, make steps more independent from each other, compilation should be reused between dependency changes * Split XAML parsing and type resolution into separated steps * Restore CompilationReferencesComparer usage * Revert "Restore CompilationReferencesComparer usage" This reverts commit c51341990b8030bb2594ee66e8819b903383bcc1. * Split ResolvedNamesProvider pipeline step, process files in parallel * Add comment * Switch back to EquatableList * Add cancellation to the incremenetal source gen * Rethrow cancellation exception
This commit is contained in:
@@ -121,6 +121,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
|
||||
build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
|
||||
build\WarnAsErrors.props = build\WarnAsErrors.props
|
||||
build\XUnit.props = build\XUnit.props
|
||||
build\AnalyzerProject.targets = build\AnalyzerProject.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<EnforceExtendedAnalyzerRules Condition="'$(EnforceExtendedAnalyzerRules)' == ''">true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent Condition="'$(IsRoslynComponent)' == ''">true</IsRoslynComponent>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.5.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -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"
|
||||
|
||||
@@ -2,23 +2,16 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<DebugType>embedded</DebugType>
|
||||
<IsPackable>true</IsPackable>
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\..\..\build\TrimmingEnable.props" />
|
||||
<Import Project="..\..\..\build\NullableEnable.props" />
|
||||
<Import Project="../../../build/TrimmingEnable.props" />
|
||||
<Import Project="../../../build/NullableEnable.props" />
|
||||
<Import Project="../../../build/AnalyzerProject.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -6,17 +6,10 @@
|
||||
<DebugType>embedded</DebugType>
|
||||
<IsPackable>true</IsPackable>
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<Nullable>enable</Nullable>
|
||||
<XamlXSourcePath>../../../external/XamlX/src/XamlX</XamlXSourcePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(XamlXSourcePath)/**/*.cs"
|
||||
Exclude="$(XamlXSourcePath)/obj/**/*.cs;$(XamlXSourcePath)/IL/SreTypeSystem.cs"
|
||||
@@ -35,4 +28,5 @@
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../../../build/TrimmingEnable.props" />
|
||||
<Import Project="../../../build/AnalyzerProject.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Avalonia.Generators.Common.Domain;
|
||||
|
||||
internal interface ICodeGenerator
|
||||
{
|
||||
string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names);
|
||||
string GenerateCode(string className, string nameSpace, IEnumerable<ResolvedName> names);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Avalonia.Generators.Common.Domain;
|
||||
|
||||
internal interface IGlobPattern
|
||||
internal interface IGlobPattern : IEquatable<IGlobPattern>
|
||||
{
|
||||
bool Matches(string str);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using XamlX.Ast;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Avalonia.Generators.Common.Domain;
|
||||
|
||||
@@ -13,7 +15,11 @@ internal enum NamedFieldModifier
|
||||
|
||||
internal interface INameResolver
|
||||
{
|
||||
IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml);
|
||||
EquatableList<ResolvedXmlName> ResolveXmlNames(XamlDocument xaml, CancellationToken cancellationToken);
|
||||
ResolvedName ResolveName(IXamlType xamlType, string name, string? fieldModifier);
|
||||
}
|
||||
|
||||
internal record ResolvedName(string TypeName, string Name, string FieldModifier);
|
||||
internal record XamlXmlType(string Name, string? XmlNamespace, EquatableList<XamlXmlType> GenericArguments);
|
||||
|
||||
internal record ResolvedXmlName(XamlXmlType XmlType, string Name, string? FieldModifier);
|
||||
internal record ResolvedName(string TypeName, string Name, string? FieldModifier);
|
||||
|
||||
@@ -1,11 +1,46 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using XamlX.Ast;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Avalonia.Generators.Common.Domain;
|
||||
|
||||
internal interface IViewResolver
|
||||
{
|
||||
ResolvedView? ResolveView(string xaml);
|
||||
ResolvedViewDocument? ResolveView(string xaml, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
internal record ResolvedView(string ClassName, IXamlType XamlType, string Namespace, XamlDocument Xaml);
|
||||
internal record ResolvedViewInfo(string ClassName, string Namespace)
|
||||
{
|
||||
public string FullName => $"{Namespace}.{ClassName}";
|
||||
public override string ToString() => FullName;
|
||||
}
|
||||
|
||||
internal record ResolvedViewDocument(string ClassName, string Namespace, XamlDocument Xaml)
|
||||
: ResolvedViewInfo(ClassName, Namespace);
|
||||
|
||||
internal record ResolvedXmlView(
|
||||
string ClassName,
|
||||
string Namespace,
|
||||
EquatableList<ResolvedXmlName> XmlNames)
|
||||
: ResolvedViewInfo(ClassName, Namespace)
|
||||
{
|
||||
public ResolvedXmlView(ResolvedViewInfo info, EquatableList<ResolvedXmlName> xmlNames)
|
||||
: this(info.ClassName, info.Namespace, xmlNames)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal record ResolvedView(
|
||||
string ClassName,
|
||||
string Namespace,
|
||||
bool IsWindow,
|
||||
EquatableList<ResolvedName> Names)
|
||||
: ResolvedViewInfo(ClassName, Namespace)
|
||||
{
|
||||
public ResolvedView(ResolvedViewInfo info, bool isWindow, EquatableList<ResolvedName> names)
|
||||
: this(info.ClassName, info.Namespace, isWindow, names)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Avalonia.Generators.Common;
|
||||
|
||||
// https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.cookbook.md#pipeline-model-design
|
||||
// With minor modification to use ReadOnlyCollection instead of List
|
||||
internal class EquatableList<T>(IList<T> collection)
|
||||
: ReadOnlyCollection<T>(collection), IEquatable<EquatableList<T>>
|
||||
{
|
||||
public bool Equals(EquatableList<T>? other)
|
||||
{
|
||||
// If the other list is null or a different size, they're not equal
|
||||
if (other is null || Count != other.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare each pair of elements for equality
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (!EqualityComparer<T>.Default.Equals(this[i], other[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, the lists are equal
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return Equals(obj as EquatableList<T>);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = 0;
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
hash ^= this[i]?.GetHashCode() ?? 0;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public static bool operator ==(EquatableList<T>? list1, EquatableList<T>? list2)
|
||||
{
|
||||
return ReferenceEquals(list1, list2)
|
||||
|| list1 is not null && list2 is not null && list1.Equals(list2);
|
||||
}
|
||||
|
||||
public static bool operator !=(EquatableList<T>? list1, EquatableList<T>? list2)
|
||||
{
|
||||
return !(list1 == list2);
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,19 @@ internal class GlobPattern : IGlobPattern
|
||||
{
|
||||
private const RegexOptions GlobOptions = RegexOptions.IgnoreCase | RegexOptions.Singleline;
|
||||
private readonly Regex _regex;
|
||||
private readonly string _pattern;
|
||||
|
||||
public GlobPattern(string pattern)
|
||||
{
|
||||
_pattern = pattern;
|
||||
var expression = "^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$";
|
||||
_regex = new Regex(expression, GlobOptions);
|
||||
}
|
||||
|
||||
public bool Matches(string str) => _regex.IsMatch(str);
|
||||
|
||||
public bool Equals(IGlobPattern other) => other is GlobPattern pattern && pattern._pattern == _pattern;
|
||||
public override int GetHashCode() => _pattern.GetHashCode();
|
||||
public override bool Equals(object? obj) => obj is GlobPattern pattern && Equals(pattern);
|
||||
public override string ToString() => _pattern;
|
||||
}
|
||||
|
||||
@@ -4,14 +4,20 @@ using Avalonia.Generators.Common.Domain;
|
||||
|
||||
namespace Avalonia.Generators.Common;
|
||||
|
||||
internal class GlobPatternGroup : IGlobPattern
|
||||
internal class GlobPatternGroup(IEnumerable<string> patterns)
|
||||
: EquatableList<GlobPattern>(patterns.Select(p => new GlobPattern(p)).ToArray()), IGlobPattern
|
||||
{
|
||||
private readonly GlobPattern[] _patterns;
|
||||
public bool Matches(string str)
|
||||
{
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
if (this[i].Matches(str))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public GlobPatternGroup(IEnumerable<string> patterns) =>
|
||||
_patterns = patterns
|
||||
.Select(pattern => new GlobPattern(pattern))
|
||||
.ToArray();
|
||||
|
||||
public bool Matches(string str) => _patterns.Any(pattern => pattern.Matches(str));
|
||||
public bool Equals(IGlobPattern other) => other is GlobPatternGroup group && base.Equals(group);
|
||||
public override string ToString() => $"[{string.Join(", ", this.Select(p => p.ToString()))}]";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Avalonia.Generators.Common;
|
||||
@@ -6,20 +6,14 @@ namespace Avalonia.Generators.Common;
|
||||
internal static class ResolverExtensions
|
||||
{
|
||||
public static bool IsAvaloniaStyledElement(this IXamlType clrType) =>
|
||||
clrType.HasStyledElementBaseType() ||
|
||||
clrType.HasIStyledElementInterface();
|
||||
Inherits(clrType, "Avalonia.StyledElement");
|
||||
public static bool IsAvaloniaWindow(this IXamlType clrType) =>
|
||||
Inherits(clrType, "Avalonia.Controls.Window");
|
||||
|
||||
private static bool HasStyledElementBaseType(this IXamlType clrType)
|
||||
private static bool Inherits(IXamlType clrType, string metadataName)
|
||||
{
|
||||
// Check for the base type since IStyledElement interface is removed.
|
||||
// https://github.com/AvaloniaUI/Avalonia/pull/9553
|
||||
if (clrType.FullName == "Avalonia.StyledElement")
|
||||
if (string.Equals(clrType.FullName, metadataName, StringComparison.Ordinal))
|
||||
return true;
|
||||
return clrType.BaseType != null && IsAvaloniaStyledElement(clrType.BaseType);
|
||||
return clrType.BaseType is { } baseType && Inherits(baseType, metadataName);
|
||||
}
|
||||
|
||||
private static bool HasIStyledElementInterface(this IXamlType clrType) =>
|
||||
clrType.Interfaces.Any(abstraction =>
|
||||
abstraction.IsInterface &&
|
||||
abstraction.FullName == "Avalonia.IStyledElement");
|
||||
}
|
||||
|
||||
@@ -1,38 +1,56 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Avalonia.Generators.Common.Domain;
|
||||
using XamlX;
|
||||
using XamlX.Ast;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Avalonia.Generators.Common;
|
||||
|
||||
internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
|
||||
internal class XamlXNameResolver
|
||||
: INameResolver, IXamlAstVisitor
|
||||
{
|
||||
private readonly List<ResolvedName> _items = new();
|
||||
private readonly string _defaultFieldModifier;
|
||||
private readonly Dictionary<string, ResolvedXmlName> _items = new();
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
public XamlXNameResolver(NamedFieldModifier namedFieldModifier = NamedFieldModifier.Internal)
|
||||
{
|
||||
_defaultFieldModifier = namedFieldModifier.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
public IReadOnlyList<ResolvedName> ResolveNames(XamlDocument xaml)
|
||||
public EquatableList<ResolvedXmlName> ResolveXmlNames(XamlDocument xaml, CancellationToken cancellationToken)
|
||||
{
|
||||
_items.Clear();
|
||||
xaml.Root.Visit(this);
|
||||
xaml.Root.VisitChildren(this);
|
||||
return _items;
|
||||
try
|
||||
{
|
||||
_cancellationToken = cancellationToken;
|
||||
xaml.Root.Visit(this);
|
||||
xaml.Root.VisitChildren(this);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cancellationToken = CancellationToken.None;
|
||||
}
|
||||
|
||||
return new EquatableList<ResolvedXmlName>(_items.Values.ToArray());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -44,27 +62,24 @@ internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
|
||||
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,
|
||||
new EquatableList<XamlXmlType>(type.GenericArguments.Select(ConvertType).ToArray()));
|
||||
}
|
||||
|
||||
void IXamlAstVisitor.Push(IXamlAstNode node) { }
|
||||
|
||||
void IXamlAstVisitor.Pop() { }
|
||||
|
||||
private string TryGetFieldModifier(XamlAstObjectNode objectNode)
|
||||
private string? TryGetFieldModifier(XamlAstObjectNode objectNode)
|
||||
{
|
||||
// We follow Xamarin.Forms API behavior in terms of x:FieldModifier here:
|
||||
// https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/field-modifiers
|
||||
@@ -87,7 +102,7 @@ internal class XamlXNameResolver : INameResolver, IXamlAstVisitor
|
||||
"protected" => "protected",
|
||||
"internal" => "internal",
|
||||
"notpublic" => "internal",
|
||||
_ => _defaultFieldModifier
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Avalonia.Generators.Common.Domain;
|
||||
using Avalonia.Generators.Compiler;
|
||||
using XamlX;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Parsers;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Avalonia.Generators.Common;
|
||||
|
||||
internal class XamlXViewResolver : IViewResolver, IXamlAstVisitor
|
||||
internal class XamlXViewResolver(MiniCompiler compiler) : IViewResolver, IXamlAstVisitor
|
||||
{
|
||||
private readonly RoslynTypeSystem _typeSystem;
|
||||
private readonly MiniCompiler _compiler;
|
||||
private readonly bool _checkTypeValidity;
|
||||
private readonly Action<string>? _onTypeInvalid;
|
||||
private readonly Action<Exception>? _onUnhandledError;
|
||||
|
||||
private ResolvedView? _resolvedClass;
|
||||
private ResolvedViewDocument? _resolvedClass;
|
||||
private XamlDocument? _xaml;
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
public XamlXViewResolver(
|
||||
RoslynTypeSystem typeSystem,
|
||||
MiniCompiler compiler,
|
||||
bool checkTypeValidity = false,
|
||||
Action<string>? onTypeInvalid = null,
|
||||
Action<Exception>? onUnhandledError = null)
|
||||
public ResolvedViewDocument? ResolveView(string xaml, CancellationToken cancellationToken)
|
||||
{
|
||||
_checkTypeValidity = checkTypeValidity;
|
||||
_onTypeInvalid = onTypeInvalid;
|
||||
_onUnhandledError = onUnhandledError;
|
||||
_typeSystem = typeSystem;
|
||||
_compiler = compiler;
|
||||
}
|
||||
_resolvedClass = null;
|
||||
_xaml = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
|
||||
{
|
||||
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
|
||||
});
|
||||
|
||||
public ResolvedView? ResolveView(string xaml)
|
||||
{
|
||||
try
|
||||
{
|
||||
_resolvedClass = null;
|
||||
_xaml = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
|
||||
{
|
||||
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
|
||||
});
|
||||
|
||||
_compiler.Transform(_xaml);
|
||||
_cancellationToken = cancellationToken;
|
||||
compiler.TransformWithCancellation(_xaml, cancellationToken);
|
||||
_xaml.Root.Visit(this);
|
||||
_xaml.Root.VisitChildren(this);
|
||||
return _resolvedClass;
|
||||
}
|
||||
catch (Exception exception)
|
||||
finally
|
||||
{
|
||||
_onUnhandledError?.Invoke(exception);
|
||||
return null;
|
||||
_cancellationToken = CancellationToken.None;
|
||||
}
|
||||
return _resolvedClass;
|
||||
}
|
||||
|
||||
|
||||
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
|
||||
{
|
||||
if (node is not XamlAstObjectNode objectNode)
|
||||
return node;
|
||||
_cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var clrType = objectNode.Type.GetClrType();
|
||||
if (!clrType.IsAvaloniaStyledElement())
|
||||
if (node is not XamlAstObjectNode objectNode)
|
||||
return node;
|
||||
|
||||
foreach (var child in objectNode.Children)
|
||||
{
|
||||
if (child is XamlAstXmlDirective directive &&
|
||||
directive.Name == "Class" &&
|
||||
directive.Namespace == XamlNamespaces.Xaml2006 &&
|
||||
directive.Values[0] is XamlAstTextNode text)
|
||||
if (child is XamlAstXmlDirective { Name: "Class", Namespace: XamlNamespaces.Xaml2006 } directive
|
||||
&& 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!);
|
||||
_resolvedClass = new ResolvedViewDocument(className, nameSpace, _xaml!);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Avalonia.Generators.Common.Domain;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Compiler;
|
||||
using XamlX.Emit;
|
||||
using XamlX.Transform;
|
||||
@@ -14,7 +17,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(RoslynTypeSystem 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)
|
||||
@@ -29,7 +47,7 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
|
||||
diagnosticsHandler: diagnosticsHandler);
|
||||
return new MiniCompiler(configuration);
|
||||
}
|
||||
|
||||
|
||||
private MiniCompiler(TransformerConfiguration configuration)
|
||||
: base(configuration, new XamlLanguageEmitMappings<object, IXamlEmitResult>(), false)
|
||||
{
|
||||
@@ -38,9 +56,42 @@ internal sealed class MiniCompiler : XamlCompiler<object, IXamlEmitResult>
|
||||
Transformers.Add(new KnownDirectivesTransformer());
|
||||
Transformers.Add(new XamlIntrinsicsTransformer());
|
||||
Transformers.Add(new XArgumentsTransformer());
|
||||
Transformers.Add(new TypeReferenceResolver());
|
||||
}
|
||||
|
||||
public IXamlTypeSystem TypeSystem => _configuration.TypeSystem;
|
||||
|
||||
public void TransformWithCancellation(XamlDocument doc, CancellationToken cancellationToken)
|
||||
{
|
||||
var ctx = CreateTransformationContext(doc);
|
||||
|
||||
var root = doc.Root;
|
||||
ctx.RootObject = new XamlRootObjectNode((XamlAstObjectNode)root);
|
||||
foreach (var transformer in Transformers)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ctx.VisitChildren(ctx.RootObject, transformer);
|
||||
root = ctx.Visit(root, transformer);
|
||||
}
|
||||
|
||||
foreach (var simplifier in SimplificationTransformers)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
root = ctx.Visit(root, simplifier);
|
||||
}
|
||||
|
||||
doc.Root = root;
|
||||
}
|
||||
|
||||
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 +99,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 { } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -11,8 +12,9 @@ namespace Avalonia.Generators.Compiler;
|
||||
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));
|
||||
|
||||
@@ -34,9 +36,9 @@ internal class RoslynTypeSystem : IXamlTypeSystem
|
||||
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2092", Justification = TrimmingMessages.Roslyn)]
|
||||
public IXamlType? FindType(string name) =>
|
||||
_assemblies
|
||||
_typeCache.GetOrAdd(name, _ => _assemblies
|
||||
.Select(assembly => assembly.FindType(name))
|
||||
.FirstOrDefault(type => type != null);
|
||||
.FirstOrDefault(type => type != null));
|
||||
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2092", Justification = TrimmingMessages.Roslyn)]
|
||||
public IXamlType? FindType(string name, string assembly) =>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Avalonia.Generators;
|
||||
|
||||
internal static class GeneratorContextExtensions
|
||||
{
|
||||
private const string UnhandledErrorDescriptorId = "AXN0002";
|
||||
private const string InvalidTypeDescriptorId = "AXN0001";
|
||||
|
||||
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 void ReportNameGeneratorUnhandledError(this GeneratorExecutionContext context, Exception error) =>
|
||||
context.Report(UnhandledErrorDescriptorId,
|
||||
"Unhandled exception occurred while generating typed Name references. " +
|
||||
"Please file an issue: https://github.com/avaloniaui/Avalonia",
|
||||
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(
|
||||
new DiagnosticDescriptor(
|
||||
id: id,
|
||||
title: title,
|
||||
messageFormat: message ?? title,
|
||||
category: "Usage",
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description),
|
||||
Location.None));
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Avalonia.Generators;
|
||||
|
||||
internal static class GeneratorExtensions
|
||||
{
|
||||
private const string UnhandledErrorDescriptorId = "AXN0002";
|
||||
private const string InvalidTypeDescriptorId = "AXN0001";
|
||||
|
||||
public static string GetMsBuildProperty(
|
||||
this AnalyzerConfigOptions options,
|
||||
string name,
|
||||
string defaultValue = "")
|
||||
{
|
||||
options.TryGetValue($"build_property.{name}", out var value);
|
||||
return value ?? defaultValue;
|
||||
}
|
||||
|
||||
public static DiagnosticDescriptor NameGeneratorUnhandledError(Exception error) => new(
|
||||
UnhandledErrorDescriptorId,
|
||||
title: "Unhandled exception occurred while generating typed Name references. " +
|
||||
"Please file an issue: https://github.com/avaloniaui/Avalonia",
|
||||
messageFormat: error.Message,
|
||||
description: error.ToString(),
|
||||
category: "Usage",
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true);
|
||||
|
||||
public static DiagnosticDescriptor NameGeneratorInvalidType(string typeName) => new(
|
||||
InvalidTypeDescriptorId,
|
||||
title: $"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " +
|
||||
$"The type '{typeName}' does not exist in the assembly.",
|
||||
messageFormat: $"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " +
|
||||
$"The type '{typeName}' does not exist in the assembly.",
|
||||
category: "Usage",
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true);
|
||||
|
||||
public static void Report(this SourceProductionContext context, DiagnosticDescriptor diagnostics) =>
|
||||
context.ReportDiagnostic(Diagnostic.Create(diagnostics, Location.None));
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using Avalonia.Generators.Common;
|
||||
using Avalonia.Generators.Common.Domain;
|
||||
using Avalonia.Generators.NameGenerator;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
|
||||
namespace Avalonia.Generators;
|
||||
|
||||
@@ -18,58 +19,72 @@ internal enum BuildProperties
|
||||
// TODO add other generators properties here.
|
||||
}
|
||||
|
||||
internal class GeneratorOptions
|
||||
internal record GeneratorOptions
|
||||
{
|
||||
private readonly GeneratorExecutionContext _context;
|
||||
|
||||
public GeneratorOptions(GeneratorExecutionContext context) => _context = context;
|
||||
|
||||
public bool AvaloniaNameGeneratorIsEnabled => GetBoolProperty(
|
||||
BuildProperties.AvaloniaNameGeneratorIsEnabled,
|
||||
true);
|
||||
|
||||
public Behavior AvaloniaNameGeneratorBehavior => GetEnumProperty(
|
||||
BuildProperties.AvaloniaNameGeneratorBehavior,
|
||||
Behavior.InitializeComponent);
|
||||
|
||||
public NamedFieldModifier AvaloniaNameGeneratorClassFieldModifier => GetEnumProperty(
|
||||
BuildProperties.AvaloniaNameGeneratorDefaultFieldModifier,
|
||||
NamedFieldModifier.Internal);
|
||||
|
||||
public ViewFileNamingStrategy AvaloniaNameGeneratorViewFileNamingStrategy => GetEnumProperty(
|
||||
BuildProperties.AvaloniaNameGeneratorViewFileNamingStrategy,
|
||||
ViewFileNamingStrategy.NamespaceAndClassName);
|
||||
|
||||
public string[] AvaloniaNameGeneratorFilterByPath => GetStringArrayProperty(
|
||||
BuildProperties.AvaloniaNameGeneratorFilterByPath,
|
||||
"*");
|
||||
|
||||
public string[] AvaloniaNameGeneratorFilterByNamespace => GetStringArrayProperty(
|
||||
BuildProperties.AvaloniaNameGeneratorFilterByNamespace,
|
||||
"*");
|
||||
|
||||
public bool AvaloniaNameGeneratorAttachDevTools => GetBoolProperty(
|
||||
BuildProperties.AvaloniaNameGeneratorAttachDevTools,
|
||||
true);
|
||||
|
||||
private string[] GetStringArrayProperty(BuildProperties name, string defaultValue)
|
||||
public GeneratorOptions(AnalyzerConfigOptions options)
|
||||
{
|
||||
var key = name.ToString();
|
||||
var value = _context.GetMsBuildProperty(key, defaultValue);
|
||||
return value.Contains(";") ? value.Split(';') : new[] {value};
|
||||
AvaloniaNameGeneratorIsEnabled = GetBoolProperty(
|
||||
options,
|
||||
BuildProperties.AvaloniaNameGeneratorIsEnabled,
|
||||
true);
|
||||
AvaloniaNameGeneratorBehavior = GetEnumProperty(
|
||||
options,
|
||||
BuildProperties.AvaloniaNameGeneratorBehavior,
|
||||
Behavior.InitializeComponent);
|
||||
AvaloniaNameGeneratorClassFieldModifier = GetEnumProperty(
|
||||
options,
|
||||
BuildProperties.AvaloniaNameGeneratorDefaultFieldModifier,
|
||||
NamedFieldModifier.Internal);
|
||||
AvaloniaNameGeneratorViewFileNamingStrategy = GetEnumProperty(
|
||||
options,
|
||||
BuildProperties.AvaloniaNameGeneratorViewFileNamingStrategy,
|
||||
ViewFileNamingStrategy.NamespaceAndClassName);
|
||||
AvaloniaNameGeneratorFilterByPath = new GlobPatternGroup(GetStringArrayProperty(
|
||||
options,
|
||||
BuildProperties.AvaloniaNameGeneratorFilterByPath,
|
||||
"*"));
|
||||
AvaloniaNameGeneratorFilterByNamespace = new GlobPatternGroup(GetStringArrayProperty(
|
||||
options,
|
||||
BuildProperties.AvaloniaNameGeneratorFilterByNamespace,
|
||||
"*"));
|
||||
AvaloniaNameGeneratorAttachDevTools = GetBoolProperty(
|
||||
options,
|
||||
BuildProperties.AvaloniaNameGeneratorAttachDevTools,
|
||||
true);
|
||||
}
|
||||
|
||||
private TEnum GetEnumProperty<TEnum>(BuildProperties name, TEnum defaultValue) where TEnum : struct
|
||||
public bool AvaloniaNameGeneratorIsEnabled { get; }
|
||||
|
||||
public Behavior AvaloniaNameGeneratorBehavior { get; }
|
||||
|
||||
public NamedFieldModifier AvaloniaNameGeneratorClassFieldModifier { get; }
|
||||
|
||||
public ViewFileNamingStrategy AvaloniaNameGeneratorViewFileNamingStrategy { get; }
|
||||
|
||||
public IGlobPattern AvaloniaNameGeneratorFilterByPath { get; }
|
||||
|
||||
public IGlobPattern AvaloniaNameGeneratorFilterByNamespace { get; }
|
||||
|
||||
public bool AvaloniaNameGeneratorAttachDevTools { get; }
|
||||
|
||||
private static string[] GetStringArrayProperty(AnalyzerConfigOptions options, BuildProperties name, string defaultValue)
|
||||
{
|
||||
var key = name.ToString();
|
||||
var value = _context.GetMsBuildProperty(key, defaultValue.ToString());
|
||||
var value = options.GetMsBuildProperty(key, defaultValue);
|
||||
return value.Contains(";") ? value.Split(';') : [value];
|
||||
}
|
||||
|
||||
private static TEnum GetEnumProperty<TEnum>(AnalyzerConfigOptions options, BuildProperties name, TEnum defaultValue) where TEnum : struct
|
||||
{
|
||||
var key = name.ToString();
|
||||
var value = options.GetMsBuildProperty(key, defaultValue.ToString());
|
||||
return Enum.TryParse(value, true, out TEnum behavior) ? behavior : defaultValue;
|
||||
}
|
||||
|
||||
private bool GetBoolProperty(BuildProperties name, bool defaultValue)
|
||||
|
||||
private static bool GetBoolProperty(AnalyzerConfigOptions options, BuildProperties name, bool defaultValue)
|
||||
{
|
||||
var key = name.ToString();
|
||||
var value = _context.GetMsBuildProperty(key, defaultValue.ToString());
|
||||
var value = options.GetMsBuildProperty(key, defaultValue.ToString());
|
||||
return bool.TryParse(value, out var result) ? result : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Avalonia.Generators.Common.Domain;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Avalonia.Generators.NameGenerator;
|
||||
|
||||
internal class AvaloniaNameGenerator : INameGenerator
|
||||
{
|
||||
private readonly ViewFileNamingStrategy _naming;
|
||||
private readonly IGlobPattern _pathPattern;
|
||||
private readonly IGlobPattern _namespacePattern;
|
||||
private readonly IViewResolver _classes;
|
||||
private readonly INameResolver _names;
|
||||
private readonly ICodeGenerator _code;
|
||||
|
||||
public AvaloniaNameGenerator(
|
||||
ViewFileNamingStrategy naming,
|
||||
IGlobPattern pathPattern,
|
||||
IGlobPattern namespacePattern,
|
||||
IViewResolver classes,
|
||||
INameResolver names,
|
||||
ICodeGenerator code)
|
||||
{
|
||||
_naming = naming;
|
||||
_pathPattern = pathPattern;
|
||||
_namespacePattern = namespacePattern;
|
||||
_classes = classes;
|
||||
_names = names;
|
||||
_code = code;
|
||||
}
|
||||
|
||||
public IEnumerable<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> 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 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);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private static string ResolveViewFileName(ResolvedView view, ViewFileNamingStrategy strategy) => strategy switch
|
||||
{
|
||||
ViewFileNamingStrategy.ClassName => $"{view.ClassName}.g.cs",
|
||||
ViewFileNamingStrategy.NamespaceAndClassName => $"{view.Namespace}.{view.ClassName}.g.cs",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, "Unknown naming strategy!")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
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 XamlX.Transform;
|
||||
|
||||
namespace Avalonia.Generators.NameGenerator;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
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)
|
||||
{
|
||||
// Map MSBuild properties onto readonly GeneratorOptions.
|
||||
var options = context.AnalyzerConfigOptionsProvider
|
||||
.Select(static (options, _) => new GeneratorOptions(options.GlobalOptions))
|
||||
.WithTrackingName(TrackingNames.XamlGeneratorOptionsProvider);
|
||||
|
||||
// Filter additional texts, we only need Avalonia XAML files.
|
||||
var xamlFiles = context.AdditionalTextsProvider
|
||||
.Combine(options.Combine(context.AnalyzerConfigOptionsProvider))
|
||||
.Where(static pair =>
|
||||
{
|
||||
var text = pair.Left;
|
||||
var (options, optionsProvider) = pair.Right;
|
||||
var filePath = text.Path;
|
||||
|
||||
if (!(filePath.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) ||
|
||||
filePath.EndsWith(".paml", StringComparison.OrdinalIgnoreCase) ||
|
||||
filePath.EndsWith(".axaml", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!options.AvaloniaNameGeneratorFilterByPath.Matches(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!optionsProvider.GetOptions(pair.Left).TryGetValue(SourceItemGroupMetadata, out var itemGroup)
|
||||
|| itemGroup != "AvaloniaXaml")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.Select(static (pair, _) => pair.Left)
|
||||
.WithTrackingName(TrackingNames.InputXamlFilesProvider);
|
||||
|
||||
// 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) =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var text = file.GetText(cancellationToken);
|
||||
var diagnostics = new List<DiagnosticDescriptor>();
|
||||
if (text is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var xaml = text.ToString();
|
||||
var viewResolver = new XamlXViewResolver(s_noopCompiler);
|
||||
var view = viewResolver.ResolveView(xaml, cancellationToken);
|
||||
if (view is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var nameResolver = new XamlXNameResolver();
|
||||
var xmlNames = nameResolver.ResolveXmlNames(view.Xaml, cancellationToken);
|
||||
|
||||
return new XmlClassInfo(
|
||||
new ResolvedXmlView(view, xmlNames),
|
||||
new EquatableList<DiagnosticDescriptor>(diagnostics));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
diagnostics.Add(GeneratorExtensions.NameGeneratorUnhandledError(ex));
|
||||
return new XmlClassInfo(null, new EquatableList<DiagnosticDescriptor>(diagnostics));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.Where(request => request is not null)
|
||||
.WithTrackingName(TrackingNames.ParsedXamlClasses);
|
||||
|
||||
// IMPORTANT: we shouldn't cache CompilationProvider as a whole,
|
||||
// But we also should keep in mind that CompilationProvider can frequently re-trigger generator.
|
||||
var compiler = context.CompilationProvider
|
||||
.Select(static (compilation, _) =>
|
||||
{
|
||||
var roslynTypeSystem = new RoslynTypeSystem(compilation);
|
||||
return MiniCompiler.CreateRoslyn(roslynTypeSystem, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
|
||||
})
|
||||
.WithTrackingName(TrackingNames.XamlTypeSystem);
|
||||
|
||||
// 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
|
||||
.Combine(compiler)
|
||||
.Select(static (pair, ct) =>
|
||||
{
|
||||
var (classInfo, compiler) = pair;
|
||||
var hasDevToolsReference = compiler.TypeSystem.FindAssembly("Avalonia.Diagnostics") is not null;
|
||||
var nameResolver = new XamlXNameResolver();
|
||||
|
||||
var diagnostics = new List<DiagnosticDescriptor>(classInfo!.Diagnostics);
|
||||
ResolvedView? view = null;
|
||||
if (classInfo.XmlView is { } xmlView)
|
||||
{
|
||||
var type = compiler.TypeSystem.FindType(xmlView.FullName);
|
||||
|
||||
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)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
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(), new EquatableList<ResolvedName>(resolvedNames));
|
||||
}
|
||||
}
|
||||
|
||||
return new ResolvedClassInfo(view, hasDevToolsReference, new EquatableList<DiagnosticDescriptor>(diagnostics));
|
||||
})
|
||||
.WithTrackingName(TrackingNames.ResolvedNamesProvider);
|
||||
|
||||
context.RegisterSourceOutput(resolvedNames.Combine(options), static (context, pair) =>
|
||||
{
|
||||
var (info, options) = pair;
|
||||
|
||||
foreach (var diagnostic in info!.Diagnostics)
|
||||
{
|
||||
context.Report(diagnostic);
|
||||
}
|
||||
|
||||
if (info.View is { } view && options.AvaloniaNameGeneratorFilterByNamespace.Matches(view.Namespace))
|
||||
{
|
||||
ICodeGenerator codeGenerator = options.AvaloniaNameGeneratorBehavior switch
|
||||
{
|
||||
Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(
|
||||
options.AvaloniaNameGeneratorClassFieldModifier),
|
||||
Behavior.InitializeComponent => new InitializeComponentCodeGenerator(
|
||||
options.AvaloniaNameGeneratorAttachDevTools && info.CanAttachDevTools && view.IsWindow,
|
||||
options.AvaloniaNameGeneratorClassFieldModifier),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
var fileName = options.AvaloniaNameGeneratorViewFileNamingStrategy switch
|
||||
{
|
||||
ViewFileNamingStrategy.ClassName => $"{view.ClassName}.g.cs",
|
||||
ViewFileNamingStrategy.NamespaceAndClassName => $"{view.Namespace}.{view.ClassName}.g.cs",
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(ViewFileNamingStrategy), options.AvaloniaNameGeneratorViewFileNamingStrategy,
|
||||
"Unknown naming strategy!")
|
||||
};
|
||||
|
||||
var generatedPartialClass = codeGenerator.GenerateCode(
|
||||
info.View.ClassName,
|
||||
info.View.Namespace,
|
||||
info.View.Names);
|
||||
|
||||
context.AddSource(fileName, generatedPartialClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal record XmlClassInfo(
|
||||
ResolvedXmlView? XmlView,
|
||||
EquatableList<DiagnosticDescriptor> Diagnostics);
|
||||
|
||||
internal record ResolvedClassInfo(
|
||||
ResolvedView? View,
|
||||
bool CanAttachDevTools,
|
||||
EquatableList<DiagnosticDescriptor> Diagnostics);
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
|
||||
[Generator]
|
||||
public class AvaloniaNameSourceGenerator : ISourceGenerator
|
||||
{
|
||||
private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup";
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context) { }
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var generator = CreateNameGenerator(context);
|
||||
if (generator is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var partials = generator.GenerateNameReferences(ResolveAdditionalFiles(context), context.CancellationToken);
|
||||
foreach (var (fileName, content) in partials)
|
||||
{
|
||||
if(context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
context.AddSource(fileName, content);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
context.ReportNameGeneratorUnhandledError(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<AdditionalText> ResolveAdditionalFiles(GeneratorExecutionContext context)
|
||||
{
|
||||
return context
|
||||
.AdditionalFiles
|
||||
.Where(f => context.AnalyzerConfigOptions
|
||||
.GetOptions(f)
|
||||
.TryGetValue(SourceItemGroupMetadata, out var sourceItemGroup)
|
||||
&& sourceItemGroup == "AvaloniaXaml");
|
||||
}
|
||||
|
||||
private static INameGenerator? CreateNameGenerator(GeneratorExecutionContext context)
|
||||
{
|
||||
var options = new GeneratorOptions(context);
|
||||
if (!options.AvaloniaNameGeneratorIsEnabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var types = new RoslynTypeSystem((CSharpCompilation)context.Compilation);
|
||||
ICodeGenerator generator = options.AvaloniaNameGeneratorBehavior switch {
|
||||
Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(),
|
||||
Behavior.InitializeComponent => new InitializeComponentCodeGenerator(types, options.AvaloniaNameGeneratorAttachDevTools),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute);
|
||||
return new AvaloniaNameGenerator(
|
||||
options.AvaloniaNameGeneratorViewFileNamingStrategy,
|
||||
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath),
|
||||
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace),
|
||||
new XamlXViewResolver(types, compiler, true,
|
||||
type => context.ReportNameGeneratorInvalidType(type),
|
||||
error => context.ReportNameGeneratorUnhandledError(error)),
|
||||
new XamlXNameResolver(options.AvaloniaNameGeneratorClassFieldModifier),
|
||||
generator);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Avalonia.Generators.Common;
|
||||
using Avalonia.Generators.Common.Domain;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Avalonia.Generators.NameGenerator;
|
||||
|
||||
internal interface INameGenerator
|
||||
{
|
||||
IEnumerable<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
internal record GeneratedPartialClass(string FileName, string Content);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Generators.Common.Domain;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Avalonia.Generators.NameGenerator;
|
||||
|
||||
internal class InitializeComponentCodeGenerator : ICodeGenerator
|
||||
internal class InitializeComponentCodeGenerator(bool avaloniaNameGeneratorAttachDevTools, NamedFieldModifier defaultNamedFieldModifier = NamedFieldModifier.Internal) : ICodeGenerator
|
||||
{
|
||||
private string _generatorName = typeof(InitializeComponentCodeGenerator).FullName;
|
||||
private string _generatorVersion = typeof(InitializeComponentCodeGenerator).Assembly.GetName().Version.ToString();
|
||||
private readonly bool _diagnosticsAreConnected;
|
||||
|
||||
private const string AttachDevToolsCodeBlock = @"
|
||||
#if DEBUG
|
||||
if (attachDevTools)
|
||||
@@ -22,12 +21,7 @@ internal class InitializeComponentCodeGenerator : ICodeGenerator
|
||||
";
|
||||
|
||||
|
||||
public InitializeComponentCodeGenerator(IXamlTypeSystem types, bool avaloniaNameGeneratorAttachDevTools)
|
||||
{
|
||||
_diagnosticsAreConnected = avaloniaNameGeneratorAttachDevTools && types.FindAssembly("Avalonia.Diagnostics") != null;
|
||||
}
|
||||
|
||||
public string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names)
|
||||
public string GenerateCode(string className, string nameSpace, IEnumerable<ResolvedName> names)
|
||||
{
|
||||
var properties = new List<string>();
|
||||
var initializations = new List<string>();
|
||||
@@ -45,7 +39,7 @@ internal class InitializeComponentCodeGenerator : ICodeGenerator
|
||||
var propertySource =
|
||||
$"""
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("{_generatorName}", "{_generatorVersion}")]
|
||||
{fieldModifier} {typeName} {name};
|
||||
{fieldModifier ?? defaultNamedFieldModifier.ToString().ToLowerInvariant()} {typeName} {name};
|
||||
""";
|
||||
properties.Add(propertySource);
|
||||
initializations.Add($" {name} = __thisNameScope__?.Find<{typeName}>(\"{name}\");");
|
||||
@@ -53,7 +47,7 @@ internal class InitializeComponentCodeGenerator : ICodeGenerator
|
||||
hasNames = true;
|
||||
}
|
||||
|
||||
var attachDevTools = _diagnosticsAreConnected && IsWindow(xamlType);
|
||||
var attachDevTools = avaloniaNameGeneratorAttachDevTools;
|
||||
|
||||
return $@"// <auto-generated />
|
||||
|
||||
@@ -87,17 +81,4 @@ namespace {nameSpace}
|
||||
}}
|
||||
";
|
||||
}
|
||||
|
||||
private static bool IsWindow(IXamlType xamlType)
|
||||
{
|
||||
var type = xamlType;
|
||||
bool isWindow;
|
||||
do
|
||||
{
|
||||
isWindow = type.FullName == "Avalonia.Controls.Window";
|
||||
type = type.BaseType;
|
||||
} while (!isWindow && type != null);
|
||||
|
||||
return isWindow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,18 @@ using XamlX.TypeSystem;
|
||||
|
||||
namespace Avalonia.Generators.NameGenerator;
|
||||
|
||||
internal class OnlyPropertiesCodeGenerator : ICodeGenerator
|
||||
internal class OnlyPropertiesCodeGenerator(NamedFieldModifier defaultNamedFieldModifier = NamedFieldModifier.Internal) : 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<ResolvedName> names)
|
||||
public string GenerateCode(string className, string nameSpace, IEnumerable<ResolvedName> names)
|
||||
{
|
||||
var namedControls = names
|
||||
.Select(info => " " +
|
||||
$"[global::System.CodeDom.Compiler.GeneratedCode(\"{_generatorName}\", \"{_generatorVersion}\")]\n" +
|
||||
" " +
|
||||
$"{info.FieldModifier} {info.TypeName} {info.Name} => " +
|
||||
$"{info.FieldModifier ?? defaultNamedFieldModifier.ToString().ToLowerInvariant()} {info.TypeName} {info.Name} => " +
|
||||
$"this.FindNameScope()?.Find<{info.TypeName}>(\"{info.Name}\");")
|
||||
.ToList();
|
||||
var lines = string.Join("\n", namedControls);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Avalonia.Generators.NameGenerator;
|
||||
|
||||
internal static class TrackingNames
|
||||
{
|
||||
public const string ResolvedNamesProvider = nameof(ResolvedNamesProvider);
|
||||
public const string XamlGeneratorOptionsProvider = nameof(XamlGeneratorOptionsProvider);
|
||||
public const string InputXamlFilesProvider = nameof(InputXamlFilesProvider);
|
||||
public const string ParsedXamlClasses = nameof(ParsedXamlClasses);
|
||||
public const string XamlTypeSystem = nameof(XamlTypeSystem);
|
||||
}
|
||||
@@ -1,29 +1,10 @@
|
||||
[](https://www.nuget.org/packages/XamlNameReferenceGenerator) [](https://www.nuget.org/packages/XamlNameReferenceGenerator)   
|
||||
|
||||
### C# `SourceGenerator` for Typed Avalonia `x:Name` References
|
||||
|
||||
This is a [C# `SourceGenerator`](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/) built for generating strongly-typed references to controls with `x:Name` (or just `Name`) attributes declared in XAML (or, in `.axaml`). The source generator will look for the `xaml` (or `axaml`) file with the same name as your partial C# class that is a subclass of `Avalonia.INamed` and parses the XAML markup, finds all XAML tags with `x:Name` attributes and generates the C# code.
|
||||
|
||||
### Getting Started
|
||||
|
||||
In order to get started, just install the NuGet package:
|
||||
|
||||
```
|
||||
dotnet add package XamlNameReferenceGenerator
|
||||
```
|
||||
|
||||
Or, if you are using [submodules](https://git-scm.com/docs/git-submodule), you can reference the generator as such:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<!-- Remember to ensure XAML files are included via <AdditionalFiles>,
|
||||
otherwise C# source generator won't see XAML files. -->
|
||||
<AdditionalFiles Include="**\*.xaml"/>
|
||||
<ProjectReference Include="..\Avalonia.NameGenerator\Avalonia.NameGenerator.csproj"
|
||||
OutputItemType="Analyzer"
|
||||
ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
```
|
||||
In order to get started, just create project with Avalonia NuGet package:
|
||||
|
||||
### Usage
|
||||
|
||||
|
||||
@@ -3,15 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../../../build/AnalyzerProject.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -4,18 +4,12 @@
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
<Compile Include="..\..\Shared\SourceGeneratorAttributes.cs" />
|
||||
<Compile Include="..\..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../../../build/AnalyzerProject.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
|
||||
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
|
||||
<ProjectReference Include="..\..\src\tools\Avalonia.Generators\Avalonia.Generators.csproj" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Views\*.xml" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
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;
|
||||
@@ -23,7 +26,6 @@ public class InitializeComponentTests
|
||||
[InlineData(InitializeComponentCode.FieldModifier, View.FieldModifier, false)]
|
||||
[InlineData(InitializeComponentCode.AttachedPropsWithDevTools, View.AttachedProps, true)]
|
||||
[InlineData(InitializeComponentCode.AttachedProps, View.AttachedProps, false)]
|
||||
[InlineData(InitializeComponentCode.ControlWithoutWindow, View.ControlWithoutWindow, true)]
|
||||
[InlineData(InitializeComponentCode.ControlWithoutWindow, View.ControlWithoutWindow, false)]
|
||||
public async Task Should_Generate_FindControl_Refs_From_Avalonia_Markup_File(
|
||||
string expectation,
|
||||
@@ -31,28 +33,28 @@ public class InitializeComponentTests
|
||||
bool devToolsMode)
|
||||
{
|
||||
var excluded = devToolsMode ? null : "Avalonia.Diagnostics";
|
||||
|
||||
// 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, CancellationToken.None);
|
||||
Assert.NotNull(classInfo);
|
||||
var nameResolver = new XamlXNameResolver();
|
||||
var names = nameResolver.ResolveXmlNames(classInfo.Xaml, CancellationToken.None);
|
||||
|
||||
// Step 2: use compilation context to resolve types
|
||||
var compilation =
|
||||
View.CreateAvaloniaCompilation(excluded)
|
||||
.WithCustomTextBox();
|
||||
var resolvedNames = names.ResolveNames(compilation, nameResolver).ToArray();
|
||||
|
||||
var types = new RoslynTypeSystem(compilation);
|
||||
var classResolver = new XamlXViewResolver(
|
||||
types,
|
||||
MiniCompiler.CreateDefault(
|
||||
new RoslynTypeSystem(compilation),
|
||||
MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
|
||||
|
||||
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 generator = new InitializeComponentCodeGenerator(types, devToolsMode);
|
||||
// Step 3: run generator
|
||||
var generator = new InitializeComponentCodeGenerator(devToolsMode);
|
||||
var generatorVersion = typeof(InitializeComponentCodeGenerator).Assembly.GetName().Version?.ToString();
|
||||
|
||||
var code = generator
|
||||
.GenerateCode("SampleView", "Sample.App", classInfo.XamlType, names)
|
||||
.GenerateCode("SampleView", "Sample.App", resolvedNames)
|
||||
.Replace("\r", string.Empty);
|
||||
|
||||
var expected = (await InitializeComponentCode.Load(expectation))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
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 +28,27 @@ public class OnlyPropertiesTests
|
||||
[InlineData(OnlyPropertiesCode.ControlWithoutWindow, View.ControlWithoutWindow)]
|
||||
public async Task Should_Generate_FindControl_Refs_From_Avalonia_Markup_File(string expectation, string markup)
|
||||
{
|
||||
// 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, CancellationToken.None);
|
||||
Assert.NotNull(classInfo);
|
||||
var nameResolver = new XamlXNameResolver();
|
||||
var names = nameResolver.ResolveXmlNames(classInfo.Xaml, CancellationToken.None);
|
||||
|
||||
// Step 2: use compilation context to resolve types
|
||||
var compilation =
|
||||
View.CreateAvaloniaCompilation()
|
||||
.WithCustomTextBox();
|
||||
var resolvedNames = names.ResolveNames(compilation, nameResolver).ToArray();
|
||||
|
||||
var classResolver = new XamlXViewResolver(
|
||||
new RoslynTypeSystem(compilation),
|
||||
MiniCompiler.CreateDefault(
|
||||
new RoslynTypeSystem(compilation),
|
||||
MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
|
||||
|
||||
var xaml = await View.Load(markup);
|
||||
var classInfo = classResolver.ResolveView(xaml);
|
||||
Assert.NotNull(classInfo);
|
||||
var nameResolver = new XamlXNameResolver();
|
||||
var names = nameResolver.ResolveNames(classInfo.Xaml);
|
||||
|
||||
// Step 3: run generator
|
||||
var generator = new OnlyPropertiesCodeGenerator();
|
||||
var generatorVersion = typeof(OnlyPropertiesCodeGenerator).Assembly.GetName().Version?.ToString();
|
||||
|
||||
var code = generator
|
||||
.GenerateCode("SampleView", "Sample.App", classInfo.XamlType, names)
|
||||
.GenerateCode("SampleView", "Sample.App", resolvedNames)
|
||||
.Replace("\r", string.Empty);
|
||||
|
||||
var expected = (await OnlyPropertiesCode.Load(expectation))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Generators.Common;
|
||||
using Avalonia.Generators.Compiler;
|
||||
@@ -23,17 +24,9 @@ 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 resolver = new XamlXViewResolver(MiniCompiler.CreateNoop());
|
||||
|
||||
var types = new RoslynTypeSystem(compilation);
|
||||
var resolver = new XamlXViewResolver(
|
||||
types,
|
||||
MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute));
|
||||
|
||||
var resolvedClass = resolver.ResolveView(xaml);
|
||||
var resolvedClass = resolver.ResolveView(xaml, CancellationToken.None);
|
||||
Assert.NotNull(resolvedClass);
|
||||
Assert.Equal(className, resolvedClass.ClassName);
|
||||
Assert.Equal(nameSpace, resolvedClass.Namespace);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Generators.Common;
|
||||
@@ -6,6 +8,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 +126,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, CancellationToken.None);
|
||||
Assert.NotNull(classInfo);
|
||||
var names = nameResolver.ResolveXmlNames(classInfo.Xaml, CancellationToken.None);
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user