mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Xaml UI (#1446)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
21
Robust.Client.NameGenerator/NameReferenceSyntaxReceiver.cs
Normal file
21
Robust.Client.NameGenerator/NameReferenceSyntaxReceiver.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace Robust.Client.NameGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Taken from https://github.com/AvaloniaUI/Avalonia.NameGenerator/blob/ecc9677a23de5cbc90af07ccac14e31c0da41d6a/src/Avalonia.NameGenerator/NameReferenceSyntaxReceiver.cs
|
||||
/// </summary>
|
||||
internal class NameReferenceSyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&
|
||||
classDeclarationSyntax.AttributeLists.Count > 0)
|
||||
CandidateClasses.Add(classDeclarationSyntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0-3.final" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/obj/**" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
287
Robust.Client.NameGenerator/RoslynTypeSystem.cs
Normal file
287
Robust.Client.NameGenerator/RoslynTypeSystem.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Client.NameGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Taken from https://github.com/AvaloniaUI/Avalonia.NameGenerator/blob/ecc9677a23de5cbc90af07ccac14e31c0da41d6a/src/Avalonia.NameGenerator/Infrastructure/RoslynTypeSystem.cs
|
||||
/// </summary>
|
||||
public class RoslynTypeSystem : IXamlTypeSystem
|
||||
{
|
||||
private readonly List<IXamlAssembly> _assemblies = new List<IXamlAssembly>();
|
||||
|
||||
public RoslynTypeSystem(CSharpCompilation compilation)
|
||||
{
|
||||
_assemblies.Add(new RoslynAssembly(compilation.Assembly));
|
||||
|
||||
var assemblySymbols = compilation
|
||||
.References
|
||||
.Select(compilation.GetAssemblyOrModuleSymbol)
|
||||
.OfType<IAssemblySymbol>()
|
||||
.Select(assembly => new RoslynAssembly(assembly))
|
||||
.ToList();
|
||||
|
||||
_assemblies.AddRange(assemblySymbols);
|
||||
}
|
||||
|
||||
public IReadOnlyList<IXamlAssembly> Assemblies => _assemblies;
|
||||
|
||||
public IXamlAssembly FindAssembly(string substring) => _assemblies[0];
|
||||
|
||||
public IXamlType FindType(string name)
|
||||
{
|
||||
foreach (var assembly in _assemblies)
|
||||
{
|
||||
var type = assembly.FindType(name);
|
||||
if (type != null)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IXamlType FindType(string name, string assembly)
|
||||
{
|
||||
foreach (var assemblyInstance in _assemblies)
|
||||
{
|
||||
var type = assemblyInstance.FindType(name);
|
||||
if (type != null)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class RoslynAssembly : IXamlAssembly
|
||||
{
|
||||
private readonly IAssemblySymbol _symbol;
|
||||
|
||||
public RoslynAssembly(IAssemblySymbol symbol) => _symbol = symbol;
|
||||
|
||||
public bool Equals(IXamlAssembly other) =>
|
||||
other is RoslynAssembly roslynAssembly &&
|
||||
SymbolEqualityComparer.Default.Equals(_symbol, roslynAssembly._symbol);
|
||||
|
||||
public string Name => _symbol.Name;
|
||||
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes =>
|
||||
_symbol.GetAttributes()
|
||||
.Select(data => new RoslynAttribute(data, this))
|
||||
.ToList();
|
||||
|
||||
public IXamlType FindType(string fullName)
|
||||
{
|
||||
var type = _symbol.GetTypeByMetadataName(fullName);
|
||||
return type is null ? null : new RoslynType(type, this);
|
||||
}
|
||||
}
|
||||
|
||||
public class RoslynAttribute : IXamlCustomAttribute
|
||||
{
|
||||
private readonly AttributeData _data;
|
||||
private readonly RoslynAssembly _assembly;
|
||||
|
||||
public RoslynAttribute(AttributeData data, RoslynAssembly assembly)
|
||||
{
|
||||
_data = data;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlCustomAttribute other) =>
|
||||
other is RoslynAttribute attribute &&
|
||||
_data == attribute._data;
|
||||
|
||||
public IXamlType Type => new RoslynType(_data.AttributeClass, _assembly);
|
||||
|
||||
public List<object> Parameters =>
|
||||
_data.ConstructorArguments
|
||||
.Select(argument => argument.Value)
|
||||
.ToList();
|
||||
|
||||
public Dictionary<string, object> Properties =>
|
||||
_data.NamedArguments.ToDictionary(
|
||||
pair => pair.Key,
|
||||
pair => pair.Value.Value);
|
||||
}
|
||||
|
||||
public class RoslynType : IXamlType
|
||||
{
|
||||
private static readonly SymbolDisplayFormat SymbolDisplayFormat = new SymbolDisplayFormat(
|
||||
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
|
||||
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters |
|
||||
SymbolDisplayGenericsOptions.IncludeTypeConstraints |
|
||||
SymbolDisplayGenericsOptions.IncludeVariance);
|
||||
|
||||
private readonly RoslynAssembly _assembly;
|
||||
private readonly INamedTypeSymbol _symbol;
|
||||
|
||||
public RoslynType(INamedTypeSymbol symbol, RoslynAssembly assembly)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlType other) =>
|
||||
other is RoslynType roslynType &&
|
||||
SymbolEqualityComparer.Default.Equals(_symbol, roslynType._symbol);
|
||||
|
||||
public object Id => _symbol;
|
||||
|
||||
public string Name => _symbol.Name;
|
||||
|
||||
public string Namespace => _symbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat);
|
||||
|
||||
public string FullName => $"{Namespace}.{Name}";
|
||||
|
||||
public IXamlAssembly Assembly => _assembly;
|
||||
|
||||
public IReadOnlyList<IXamlProperty> Properties =>
|
||||
_symbol.GetMembers()
|
||||
.Where(member => member.Kind == SymbolKind.Property)
|
||||
.OfType<IPropertySymbol>()
|
||||
.Select(property => new RoslynProperty(property, _assembly))
|
||||
.ToList();
|
||||
|
||||
public IReadOnlyList<IXamlEventInfo> Events { get; } = new List<IXamlEventInfo>();
|
||||
|
||||
public IReadOnlyList<IXamlField> Fields { get; } = new List<IXamlField>();
|
||||
|
||||
public IReadOnlyList<IXamlMethod> Methods { get; } = new List<IXamlMethod>();
|
||||
|
||||
public IReadOnlyList<IXamlConstructor> Constructors =>
|
||||
_symbol.Constructors
|
||||
.Select(method => new RoslynConstructor(method, _assembly))
|
||||
.ToList();
|
||||
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
|
||||
|
||||
public IReadOnlyList<IXamlType> GenericArguments { get; } = new List<IXamlType>();
|
||||
|
||||
public bool IsAssignableFrom(IXamlType type) => type == this;
|
||||
|
||||
public IXamlType MakeGenericType(IReadOnlyList<IXamlType> typeArguments) => this;
|
||||
|
||||
public IXamlType GenericTypeDefinition => this;
|
||||
|
||||
public bool IsArray => false;
|
||||
|
||||
public IXamlType ArrayElementType { get; } = null;
|
||||
|
||||
public IXamlType MakeArrayType(int dimensions) => null;
|
||||
|
||||
public IXamlType BaseType => _symbol.BaseType == null ? null : new RoslynType(_symbol.BaseType, _assembly);
|
||||
|
||||
public bool IsValueType { get; } = false;
|
||||
|
||||
public bool IsEnum { get; } = false;
|
||||
|
||||
public IReadOnlyList<IXamlType> Interfaces =>
|
||||
_symbol.AllInterfaces
|
||||
.Select(abstraction => new RoslynType(abstraction, _assembly))
|
||||
.ToList();
|
||||
|
||||
public bool IsInterface => _symbol.IsAbstract;
|
||||
|
||||
public IXamlType GetEnumUnderlyingType() => null;
|
||||
|
||||
public IReadOnlyList<IXamlType> GenericParameters { get; } = new List<IXamlType>();
|
||||
}
|
||||
|
||||
public class RoslynConstructor : IXamlConstructor
|
||||
{
|
||||
private readonly IMethodSymbol _symbol;
|
||||
private readonly RoslynAssembly _assembly;
|
||||
|
||||
public RoslynConstructor(IMethodSymbol symbol, RoslynAssembly assembly)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlConstructor other) =>
|
||||
other is RoslynConstructor roslynConstructor &&
|
||||
SymbolEqualityComparer.Default.Equals(_symbol, roslynConstructor._symbol);
|
||||
|
||||
public bool IsPublic => true;
|
||||
|
||||
public bool IsStatic => false;
|
||||
|
||||
public IReadOnlyList<IXamlType> Parameters =>
|
||||
_symbol.Parameters
|
||||
.Select(parameter => parameter.Type)
|
||||
.OfType<INamedTypeSymbol>()
|
||||
.Select(type => new RoslynType(type, _assembly))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public class RoslynProperty : IXamlProperty
|
||||
{
|
||||
private readonly IPropertySymbol _symbol;
|
||||
private readonly RoslynAssembly _assembly;
|
||||
|
||||
public RoslynProperty(IPropertySymbol symbol, RoslynAssembly assembly)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlProperty other) =>
|
||||
other is RoslynProperty roslynProperty &&
|
||||
SymbolEqualityComparer.Default.Equals(_symbol, roslynProperty._symbol);
|
||||
|
||||
public string Name => _symbol.Name;
|
||||
|
||||
public IXamlType PropertyType =>
|
||||
_symbol.Type is INamedTypeSymbol namedTypeSymbol
|
||||
? new RoslynType(namedTypeSymbol, _assembly)
|
||||
: null;
|
||||
|
||||
public IXamlMethod Getter => _symbol.GetMethod == null ? null : new RoslynMethod(_symbol.GetMethod, _assembly);
|
||||
|
||||
public IXamlMethod Setter => _symbol.SetMethod == null ? null : new RoslynMethod(_symbol.SetMethod, _assembly);
|
||||
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
|
||||
|
||||
public IReadOnlyList<IXamlType> IndexerParameters { get; } = new List<IXamlType>();
|
||||
}
|
||||
|
||||
public class RoslynMethod : IXamlMethod
|
||||
{
|
||||
private readonly IMethodSymbol _symbol;
|
||||
private readonly RoslynAssembly _assembly;
|
||||
|
||||
public RoslynMethod(IMethodSymbol symbol, RoslynAssembly assembly)
|
||||
{
|
||||
_symbol = symbol;
|
||||
_assembly = assembly;
|
||||
}
|
||||
|
||||
public bool Equals(IXamlMethod other) =>
|
||||
other is RoslynMethod roslynMethod &&
|
||||
SymbolEqualityComparer.Default.Equals(roslynMethod._symbol, _symbol);
|
||||
|
||||
public string Name => _symbol.Name;
|
||||
|
||||
public bool IsPublic => true;
|
||||
|
||||
public bool IsStatic => false;
|
||||
|
||||
public IXamlType ReturnType => new RoslynType((INamedTypeSymbol) _symbol.ReturnType, _assembly);
|
||||
|
||||
public IReadOnlyList<IXamlType> Parameters =>
|
||||
_symbol.Parameters.Select(parameter => parameter.Type)
|
||||
.OfType<INamedTypeSymbol>()
|
||||
.Select(type => new RoslynType(type, _assembly))
|
||||
.ToList();
|
||||
|
||||
public IXamlType DeclaringType => new RoslynType((INamedTypeSymbol)_symbol.ReceiverType, _assembly);
|
||||
|
||||
public IXamlMethod MakeGenericMethod(IReadOnlyList<IXamlType> typeArguments) => null;
|
||||
|
||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = new List<IXamlCustomAttribute>();
|
||||
}
|
||||
}
|
||||
251
Robust.Client.NameGenerator/XamlUiPartialClassGenerator.cs
Normal file
251
Robust.Client.NameGenerator/XamlUiPartialClassGenerator.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
using XamlX.Parsers;
|
||||
using XamlX.Transform;
|
||||
using XamlX.Transform.Transformers;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Client.NameGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Based on https://github.com/AvaloniaUI/Avalonia.NameGenerator/blob/ecc9677a23de5cbc90af07ccac14e31c0da41d6a/src/Avalonia.NameGenerator/NameReferenceGenerator.cs
|
||||
/// Adjusted for our UI-Framework & needs.
|
||||
/// </summary>
|
||||
[Generator]
|
||||
public class XamlUiPartialClassGenerator : ISourceGenerator
|
||||
{
|
||||
private const string AttributeName = "Robust.Client.AutoGenerated.GenerateTypedNameReferencesAttribute";
|
||||
private const string AttributeFile = "GenerateTypedNameReferencesAttribute";
|
||||
private const string AttributeCode = @"// <auto-generated />
|
||||
using System;
|
||||
namespace Robust.Client.AutoGenerated
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class GenerateTypedNameReferencesAttribute : Attribute { }
|
||||
}
|
||||
";
|
||||
|
||||
class NameVisitor : IXamlAstVisitor
|
||||
{
|
||||
private List<(string name, string type)> _names = new List<(string name, string type)>();
|
||||
|
||||
public static List<(string name, string type)> GetNames(IXamlAstNode node)
|
||||
{
|
||||
var visitor = new NameVisitor();
|
||||
node.Visit(visitor);
|
||||
return visitor._names;
|
||||
}
|
||||
|
||||
private bool IsControl(IXamlType type) => type.FullName != "System.Object" &&
|
||||
(type.FullName == "Robust.Client.UserInterface.Control" ||
|
||||
IsControl(type.BaseType));
|
||||
|
||||
public IXamlAstNode Visit(IXamlAstNode node)
|
||||
{
|
||||
if (node is XamlAstObjectNode objectNode)
|
||||
{
|
||||
var clrtype = objectNode.Type.GetClrType();
|
||||
var isControl = IsControl(clrtype);
|
||||
//clrtype.Interfaces.Any(i =>
|
||||
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
|
||||
|
||||
if (!isControl)
|
||||
return node;
|
||||
|
||||
foreach (var child in objectNode.Children)
|
||||
{
|
||||
if (child is XamlAstXamlPropertyValueNode propertyValueNode &&
|
||||
propertyValueNode.Property is XamlAstNamePropertyReference namedProperty &&
|
||||
namedProperty.Name == "Name" &&
|
||||
propertyValueNode.Values.Count > 0 &&
|
||||
propertyValueNode.Values[0] is XamlAstTextNode text)
|
||||
{
|
||||
var reg = (text.Text, $@"{clrtype.Namespace}.{clrtype.Name}");
|
||||
if (!_names.Contains(reg))
|
||||
{
|
||||
_names.Add(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public void Push(IXamlAstNode node) { }
|
||||
|
||||
public void Pop() { }
|
||||
}
|
||||
|
||||
private static string GenerateSourceCode(
|
||||
INamedTypeSymbol classSymbol,
|
||||
string xamlFile,
|
||||
CSharpCompilation comp)
|
||||
{
|
||||
var className = classSymbol.Name;
|
||||
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
|
||||
var parsed = XDocumentXamlParser.Parse(xamlFile);
|
||||
var typeSystem = new RoslynTypeSystem(comp);
|
||||
var compiler =
|
||||
new XamlILCompiler(
|
||||
new TransformerConfiguration(typeSystem, typeSystem.Assemblies[0],
|
||||
new XamlLanguageTypeMappings(typeSystem)),
|
||||
new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>(), false);
|
||||
compiler.Transformers.Add(new TypeReferenceResolver());
|
||||
compiler.Transform(parsed);
|
||||
var initialRoot = (XamlAstObjectNode) parsed.Root;
|
||||
var names = NameVisitor.GetNames(initialRoot);
|
||||
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
|
||||
var namedControls = names.Select(info => " " +
|
||||
$"protected global::{info.type} {info.name} => " +
|
||||
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
|
||||
return $@"// <auto-generated />
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
namespace {nameSpace}
|
||||
{{
|
||||
partial class {className}
|
||||
{{
|
||||
{string.Join("\n", namedControls)}
|
||||
}}
|
||||
}}
|
||||
";
|
||||
}
|
||||
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
var comp = (CSharpCompilation) context.Compilation;
|
||||
if(comp.GetTypeByMetadataName(AttributeName) == null)
|
||||
context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));
|
||||
if (!(context.SyntaxReceiver is NameReferenceSyntaxReceiver receiver))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var symbols = UnpackAnnotatedTypes(context, comp, receiver);
|
||||
if(symbols == null)
|
||||
return;
|
||||
|
||||
foreach (var typeSymbol in symbols)
|
||||
{
|
||||
var xamlFileName = $"{typeSymbol.Name}.xaml";
|
||||
var relevantXamlFile = context.AdditionalFiles.FirstOrDefault(t => t.Path.EndsWith(xamlFileName));
|
||||
|
||||
if (relevantXamlFile == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0001",
|
||||
$"Unable to discover the relevant Robust XAML file for {typeSymbol}.",
|
||||
"Unable to discover the relevant Robust XAML file " +
|
||||
$"expected at {xamlFileName}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
var txt = relevantXamlFile.GetText()?.ToString();
|
||||
if (txt == null)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0002",
|
||||
$"Unexpected empty Xaml-File was found at {xamlFileName}",
|
||||
"Expected Content due to a Class with the same name being annotated with [GenerateTypedNameReferences].",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.Create(xamlFileName, new TextSpan(0,0), new LinePositionSpan(new LinePosition(0,0),new LinePosition(0,0)))));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp);
|
||||
context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"AXN0003",
|
||||
"Unhandled exception occured while generating typed Name references.",
|
||||
$"Unhandled exception occured while generating typed Name references: {e}",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
typeSymbol.Locations[0]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context, CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
|
||||
{
|
||||
var options = (CSharpParseOptions) comp.SyntaxTrees[0].Options;
|
||||
var compilation =
|
||||
comp.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(AttributeCode, Encoding.UTF8), options));
|
||||
var symbols = new List<INamedTypeSymbol>();
|
||||
var attributeSymbol = compilation.GetTypeByMetadataName(AttributeName);
|
||||
foreach (var candidateClass in receiver.CandidateClasses)
|
||||
{
|
||||
var model = compilation.GetSemanticModel(candidateClass.SyntaxTree);
|
||||
var typeSymbol = (INamedTypeSymbol) model.GetDeclaredSymbol(candidateClass);
|
||||
var relevantAttribute = typeSymbol.GetAttributes().FirstOrDefault(attr =>
|
||||
attr.AttributeClass != null &&
|
||||
attr.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
|
||||
|
||||
if (relevantAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var isPartial = candidateClass.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
|
||||
|
||||
if (isPartial)
|
||||
{
|
||||
symbols.Add(typeSymbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
var missingPartialKeywordMessage =
|
||||
$"The type {typeSymbol.Name} should be declared with the 'partial' keyword " +
|
||||
"as it is annotated with the [GenerateTypedNameReferences] attribute.";
|
||||
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
new DiagnosticDescriptor(
|
||||
"RXN0004",
|
||||
missingPartialKeywordMessage,
|
||||
missingPartialKeywordMessage,
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true),
|
||||
Location.None));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterForSyntaxNotifications(() => new NameReferenceSyntaxReceiver());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user