Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
157 views
in Technique[技术] by (71.8m points)

c# - Mark parameters with a [ReadOnly] attribute and let an analyzer assert, that no reassignment is in the method body

Not being able to mark a parameter of a method as readonly so that it is impossible to be reassigned in the method, I started thinking abount creating an analyzer for this. The parameter would be attributed with

[AttributeUsage(AttributeTargets.Parameter)]
public class ReadOnlyAttribute: Attribute
{
  // ...
}

and the signature of a method would be

public class Foo
{
  public void Bar([ReadOnly] MyObject o)
  {
    o.DoSomething()    // this is OK
    o = new MyObject() // this would be flagged by the analyzer
  }
}

What I have so far is an DiagnosticAnalyzer class with these members:

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

How do I find all reference of a parameter within the method body? And how do I know which one of them are assigments?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I sugges you get SyntaxNode for current IMethodSymbol if it has "ReadOnlyAttribute" parameter then get all descendant nodes from SyntaxNode that are AssignmentExpressionSyntax. So, you only need to compare statement' identifiers and your parameter. Give me know if you have some questions.

...
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()));
        }
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...