Nei*_*der 5 c# analyzer roslyn
背景:
我有一个属性,指示对象中字段的属性IsMagic。我还有一个Magician类可以运行在任何对象上,并MakesMagic提取每个字段和属性IsMagic并将其包装在Magic包装器中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace MagicTest
{
/// <summary>
/// An attribute that allows us to decorate a class with information that identifies which member is magic.
/// </summary>
[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field, AllowMultiple = false)]
class IsMagic : Attribute { }
public class Magic
{
// Internal data storage
readonly public dynamic value;
#region My ever-growing list of constructors
public Magic(int input) { value = input; }
public Magic(string input) { value = input; }
public Magic(IEnumerable<bool> input) { value = input; }
// ...
#endregion
public bool CanMakeMagicFromType(Type targetType)
{
if (targetType == null) return false;
ConstructorInfo publicConstructor = typeof(Magic).GetConstructor(new[] { targetType });
if (publicConstructor != null) return true; // We can make Magic from this input type!!!
return false;
}
public override string ToString()
{
return value.ToString();
}
}
public static class Magician
{
/// <summary>
/// A method that returns the members of anObject that have been marked with an IsMagic attribute.
/// Each member will be wrapped in Magic.
/// </summary>
/// <param name="anObject"></param>
/// <returns></returns>
public static List<Magic> MakeMagic(object anObject)
{
Type type = anObject?.GetType() ?? null;
if (type == null) return null; // Sanity check
List<Magic> returnList = new List<Magic>();
// Any field or property of the class that IsMagic gets added to the returnList in a Magic wrapper
MemberInfo[] objectMembers = type.GetMembers();
foreach (MemberInfo mi in objectMembers)
{
bool isMagic = (mi.GetCustomAttributes<IsMagic>().Count() > 0);
if (isMagic)
{
dynamic memberValue = null;
if (mi.MemberType == MemberTypes.Property) memberValue = ((PropertyInfo)mi).GetValue(anObject);
else if (mi.MemberType == MemberTypes.Field) memberValue = ((FieldInfo)mi).GetValue(anObject);
if (memberValue == null) continue;
returnList.Add(new Magic(memberValue)); // This could fail at run-time!!!
}
}
return returnList;
}
}
}
Run Code Online (Sandbox Code Playgroud)
魔术师罐MakeMagic上anObject具有至少一个字段或属性,IsMagic以产生一个通用List的Magic,如下所示:
using System;
using System.Collections.Generic;
namespace MagicTest
{
class Program
{
class Mundane
{
[IsMagic] public string foo;
[IsMagic] public int feep;
public float zorp; // If this [IsMagic], we'll have a run-time error
}
static void Main(string[] args)
{
Mundane anObject = new Mundane
{
foo = "this is foo",
feep = -10,
zorp = 1.3f
};
Console.WriteLine("Magic:");
List<Magic> myMagics = Magician.MakeMagic(anObject);
foreach (Magic aMagic in myMagics) Console.WriteLine(" {0}",aMagic.ToString());
Console.WriteLine("More Magic: {0}", new Magic("this works!"));
//Console.WriteLine("More Magic: {0}", new Magic(Mundane)); // build-time error!
Console.WriteLine("\nPress Enter to continue");
Console.ReadLine();
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,Magic包装器只能绕过某些类型的属性或字段。这意味着仅将包含特定类型数据的属性或字段标记为IsMagic。更复杂的是,我希望随着业务需求的发展,特定类型的列表也会发生变化(因为对Magic的编程需求如此之高)。
好消息是它Magic具有一定的构建时间安全性。如果我尝试添加代码,例如new Magic(true)Visual Studio会告诉我这是错误的,因为没有构造函数Magic需要使用bool。还存在一些运行时检查,因为该Magic.CanMakeMagicFromType方法可用于捕获动态变量的问题。
问题描述:
坏消息是没有对IsMagic属性进行构建时检查。我可以很高兴地说出Dictionary<string,bool>某个类中的一个字段,IsMagic直到运行时我都不会被告知这是一个问题。更糟糕的是,我的神奇代码的用户将创建自己的普通类,并使用该IsMagic属性装饰其属性和字段。我想帮助他们在问题变成问题之前就发现问题。
建议的解决方案:
理想情况下,我可以在属性上放置某种AttributeUsage标志,IsMagic以告诉Visual Studio使用该Magic.CanMakeMagicFromType()方法来检查IsMagic附加属性的属性或字段类型。不幸的是,似乎没有这样的属性。
但是,当IsMagic将Roslyn 放在具有Type不能包装在的字段或属性上时,似乎应该可以使用Roslyn来显示错误Magic。
我需要帮助的地方:
我在设计Roslyn分析器时遇到麻烦。问题的核心是Magic.CanMakeMagicFromTypein System.Type,但是Roslyn ITypeSymbol用来表示对象类型。
理想的分析仪将:
Magic。毕竟,Magic有满足此目的的构造函数列表。Magic有一个接受的构造函数IEnumerable<bool>,则Roslyn应该允许IsMagic附加到类型为List<bool>或的属性bool[]。魔术的投射对于魔术师的功能至关重要。对于如何编写“了解”中的构造函数的Roslyn分析器的任何指导,我将不胜感激Magic。
根据 SLAks 的出色建议,我能够编写出完整的解决方案。
发现错误应用属性的代码分析器如下所示:
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
namespace AttributeAnalyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AttributeAnalyzerAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "AttributeAnalyzer";
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: DiagnosticId,
title: "Magic cannot be constructed from Type",
messageFormat: "Magic cannot be built from Type '{0}'.",
category: "Design",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The IsMagic attribue needs to be attached to Types that can be rendered as Magic."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
AnalyzeSyntax,
SyntaxKind.PropertyDeclaration, SyntaxKind.FieldDeclaration
);
}
private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
{
ITypeSymbol memberTypeSymbol = null;
if (context.ContainingSymbol is IPropertySymbol)
{
memberTypeSymbol = (context.ContainingSymbol as IPropertySymbol)?.GetMethod?.ReturnType;
}
else if (context.ContainingSymbol is IFieldSymbol)
{
memberTypeSymbol = (context.ContainingSymbol as IFieldSymbol)?.Type;
}
else throw new InvalidOperationException("Can only analyze property and field declarations.");
// Check if this property of field is decorated with the IsMagic attribute
INamedTypeSymbol isMagicAttribute = context.SemanticModel.Compilation.GetTypeByMetadataName("MagicTest.IsMagic");
ISymbol thisSymbol = context.ContainingSymbol;
ImmutableArray<AttributeData> attributes = thisSymbol.GetAttributes();
bool hasMagic = false;
Location attributeLocation = null;
foreach (AttributeData attribute in attributes)
{
if (attribute.AttributeClass != isMagicAttribute) continue;
hasMagic = true;
attributeLocation = attribute.ApplicationSyntaxReference.SyntaxTree.GetLocation(attribute.ApplicationSyntaxReference.Span);
break;
}
if (!hasMagic) return;
// Check if we can make Magic using the current property or field type
if (!CanMakeMagic(context,memberTypeSymbol))
{
var diagnostic = Diagnostic.Create(Rule, attributeLocation, memberTypeSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}
/// <summary>
/// Check if a given type can be wrapped in Magic in the current context.
/// </summary>
/// <param name="context"></param>
/// <param name="sourceTypeSymbol"></param>
/// <returns></returns>
private static bool CanMakeMagic(SyntaxNodeAnalysisContext context, ITypeSymbol sourceTypeSymbol)
{
INamedTypeSymbol magic = context.SemanticModel.Compilation.GetTypeByMetadataName("MagicTest.Magic");
ImmutableArray<IMethodSymbol> constructors = magic.Constructors;
foreach (IMethodSymbol methodSymbol in constructors)
{
ImmutableArray<IParameterSymbol> parameters = methodSymbol.Parameters;
IParameterSymbol param = parameters[0]; // All Magic constructors take one parameter
ITypeSymbol paramType = param.Type;
Conversion conversion = context.Compilation.ClassifyConversion(sourceTypeSymbol, paramType);
if (conversion.Exists && conversion.IsImplicit) return true; // We've found at least one way to make Magic
}
return false;
}
}
}
Run Code Online (Sandbox Code Playgroud)
CanMakeMagic 函数具有 SLaks 为我阐明的神奇解决方案。
代码修复提供程序如下所示:
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace AttributeAnalyzer
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AttributeAnalyzerCodeFixProvider)), Shared]
public class AttributeAnalyzerCodeFixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(AttributeAnalyzerAnalyzer.DiagnosticId); }
}
public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
return WellKnownFixAllProviders.BatchFixer;
}
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
Diagnostic diagnostic = context.Diagnostics.First();
TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;
context.RegisterCodeFix(
CodeAction.Create(
title: "Remove attribute",
createChangedDocument: c => RemoveAttributeAsync(context.Document, diagnosticSpan, context.CancellationToken),
equivalenceKey: "Remove_Attribute"
),
diagnostic
);
}
private async Task<Document> RemoveAttributeAsync(Document document, TextSpan diagnosticSpan, CancellationToken cancellation)
{
SyntaxNode root = await document.GetSyntaxRootAsync(cancellation).ConfigureAwait(false);
AttributeListSyntax attributeListDeclaration = root.FindNode(diagnosticSpan).FirstAncestorOrSelf<AttributeListSyntax>();
SeparatedSyntaxList<AttributeSyntax> attributes = attributeListDeclaration.Attributes;
if (attributes.Count > 1)
{
AttributeSyntax targetAttribute = root.FindNode(diagnosticSpan).FirstAncestorOrSelf<AttributeSyntax>();
return document.WithSyntaxRoot(
root.RemoveNode(targetAttribute,
SyntaxRemoveOptions.KeepExteriorTrivia | SyntaxRemoveOptions.KeepEndOfLine | SyntaxRemoveOptions.KeepDirectives)
);
}
if (attributes.Count==1)
{
return document.WithSyntaxRoot(
root.RemoveNode(attributeListDeclaration,
SyntaxRemoveOptions.KeepExteriorTrivia | SyntaxRemoveOptions.KeepEndOfLine | SyntaxRemoveOptions.KeepDirectives)
);
}
return document;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这里唯一需要的聪明之处是有时删除单个属性,有时删除整个属性列表。
我将此标记为已接受的答案;但是,为了充分披露,如果没有 SLAks 的帮助,我永远不会弄清楚这一点。
| 归档时间: |
|
| 查看次数: |
591 次 |
| 最近记录: |