无法将方法的参数标记为只读,因此无法在方法中重新分配,我开始考虑为此创建一个分析器。参数将归结为
[AttributeUsage(AttributeTargets.Parameter)]
public class ReadOnlyAttribute: Attribute
{
// ...
}
并且方法的签名将是
public class Foo
{
public void Bar([ReadOnly] MyObject o)
{
o.DoSomething() // this is OK
o = new MyObject() // this would be flagged by the analyzer
}
}
到目前为止,我有一个包含这些成员的DiagnosticAnalyzer类:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class MyDiagnosticAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Info, true, Description, HelpLink);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Descriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method);
}
private static void AnalyzeSymbol(SymbolAnalysisContext context)
{
var methodSymbol = context.Symbol as IMethodSymbol;
var parameters = methodSymbol.Parameters;
foreach (var p in parameters)
{
var attr = p.GetAttributes();
foreach (var a in attr)
{
if (a.AttributeClass.Name == "ReadOnlyAttribute")
{
// now I have to find all references of p in order to check if any of them is a assignment
}
}
}
}
}
如何在方法体中找到参数的所有引用?我怎么知道哪一个是分配?
答案 0 :(得分:1)
我建议您获取当前IMethodSymbol的SyntaxNode,如果它有&#34; ReadOnlyAttribute&#34;然后,参数从SyntaxNode获取AssignmentExpressionSyntax的所有后代节点。所以,你只需要比较声明&#39;标识符和您的参数。如果您有任何问题,请告诉我。
...
var methodSymbol = context.Symbol as IMethodSymbol;;
var parameters = methodSymbol.Parameters;
if (!parameters.SelectMany(p => p.GetAttributes()).Any(s => s.AttributeClass.Name == "ReadOnlyAttribute"))
{
// So you don't have params with ReadOnly attribute
return;
}
// So you have params with ReadOnly attribute
var location = methodSymbol.Locations.FirstOrDefault();
if (location == null)
{
// throw or return
}
// Can cahce CompilationRoot
var methodNode = location.SourceTree.GetCompilationUnitRoot().FindNode(location.SourceSpan);
var childNodes = methodNode.ChildNodes().ToList();
// Expression-bodied memeber
var blockNode = childNodes.FirstOrDefault(s => s is BlockSyntax) ?? childNodes.FirstOrDefault(s => s is ArrowExpressionClauseSyntax);
if (blockNode == null)
{
// throw or return
}
// You can convert this linq to foreach and improve performance removing a where functions
var assignmentIndetifiers = blockNode.DescendantNodes()
.Select(s => s as AssignmentExpressionSyntax)
.Where(s => s != null)
.Select(s => s.Left as IdentifierNameSyntax)
.Where(s => s != null)
.Select(s => s.Identifier)
.ToList();
// You retrive all left identifiers in assignment expressions from current block syntax and its descendant nodes
// You only need to check if assignmentIdentifiers contains your readonly parameter
// For example, you can compare by name (you can use custom equality comparer)
var names = assignmentIndetifiers.ToLookup(s => s.ValueText, EqualityComparer<string>.Default);
foreach (var parameter in parameters)
{
if (names.Contains(parameter.Name))
{
foreach (var token in names[parameter.Name])
{
// throw that readonly argument is a assignment
context.ReportDiagnostic(Diagnostic.Create(rule, token.GetLocation()));
}
}
}