使用[ReadOnly]属性标记参数并让分析器断言,方法体中没有重新分配

时间:2017-06-23 18:56:52

标签: c# roslyn roslyn-code-analysis

无法将方法的参数标记为只读,因此无法在方法中重新分配,我开始考虑为此创建一个分析器。参数将归结为

[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
        }
      }
    }
  }
}

如何在方法体中找到参数的所有引用?我怎么知道哪一个是分配?

1 个答案:

答案 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(locati‌​on.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()));
        }
    }
}