A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

135 lines
5.8 KiB

using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
using Microsoft.CodeAnalysis.CSharp;
namespace XamlNameReferenceGenerator
{
[Generator]
public class NameReferenceGenerator : ISourceGenerator
{
private const string AttributeName = "XamlNameReferenceGenerator.GenerateTypedNameReferencesAttribute";
private const string AttributeFile = "GenerateTypedNameReferencesAttribute";
private const string AttributeCode = @"// <auto-generated />
using System;
namespace XamlNameReferenceGenerator
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
sealed class GenerateTypedNameReferencesAttribute : Attribute
{
public GenerateTypedNameReferencesAttribute() { }
public string[] AdditionalNamespaces { get; set; } = null;
}
}
";
private const string DebugPath = @"C:\Users\prizr\Documents\GitHub\XamlNameReferenceGenerator\debug.txt";
private static readonly NameReferenceXamlParser XamlParser = new NameReferenceXamlParser();
private static readonly NameReferenceDebugger Debugger = new NameReferenceDebugger(DebugPath);
private static readonly SymbolDisplayFormat SymbolDisplayFormat = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters |
SymbolDisplayGenericsOptions.IncludeTypeConstraints |
SymbolDisplayGenericsOptions.IncludeVariance);
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new NameReferenceSyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));
if (!(context.SyntaxReceiver is NameReferenceSyntaxReceiver receiver))
return;
var symbols = UnpackAnnotatedTypes((CSharpCompilation) context.Compilation, receiver);
foreach (var (typeSymbol, additionalNamespaces) in symbols)
{
var relevantXamlFile = context.AdditionalFiles
.First(text =>
text.Path.EndsWith($"{typeSymbol.Name}.xaml") ||
text.Path.EndsWith($"{typeSymbol.Name}.axaml"));
var sourceCode = Debugger.Debug(
() => GenerateSourceCode(typeSymbol, relevantXamlFile, additionalNamespaces));
context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
}
}
private static string GenerateSourceCode(
INamedTypeSymbol classSymbol,
AdditionalText xamlFile,
IList<string> additionalNamespaces)
{
var className = classSymbol.Name;
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat);
var namespaces = additionalNamespaces.Select(name => $"using {name};");
var namedControls = XamlParser
.GetNamedControls(xamlFile)
.Select(info => " " +
$"public {info.TypeName} {info.Name} => " +
$"this.FindControl<{info.TypeName}>(\"{info.Name}\");");
return $@"// <auto-generated />
using System;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
{string.Join("\n", namespaces)}
namespace {nameSpace}
{{
public partial class {className}
{{
{string.Join("\n", namedControls)}
}}
}}
";
}
private static IReadOnlyList<(INamedTypeSymbol Type, IList<string> Namespaces)> UnpackAnnotatedTypes(
CSharpCompilation existingCompilation,
NameReferenceSyntaxReceiver nameReferenceSyntaxReceiver)
{
var options = (CSharpParseOptions)existingCompilation.SyntaxTrees[0].Options;
var compilation = existingCompilation.AddSyntaxTrees(
CSharpSyntaxTree.ParseText(
SourceText.From(AttributeCode, Encoding.UTF8),
options));
var attributeSymbol = compilation.GetTypeByMetadataName(AttributeName);
var symbols = new List<(INamedTypeSymbol Type, IList<string> Namespaces)>();
foreach (var candidateClass in nameReferenceSyntaxReceiver.CandidateClasses)
{
var model = compilation.GetSemanticModel(candidateClass.SyntaxTree);
var typeSymbol = (INamedTypeSymbol) model.GetDeclaredSymbol(candidateClass);
var relevantAttribute = typeSymbol!
.GetAttributes()
.FirstOrDefault(attr => attr.AttributeClass!.Equals(attributeSymbol, SymbolEqualityComparer.Default));
if (relevantAttribute != null)
{
var additionalNamespaces = new List<string>();
if (relevantAttribute.NamedArguments.Any(kvp => kvp.Key == "AdditionalNamespaces"))
{
additionalNamespaces = relevantAttribute
.NamedArguments
.First(kvp => kvp.Key == "AdditionalNamespaces")
.Value.Values
.Where(constant => !constant.IsNull)
.Select(constant => constant.Value!.ToString())
.ToList();
}
symbols.Add((typeSymbol, additionalNamespaces));
}
}
return symbols;
}
}
}