Skip to content

Instantly share code, notes, and snippets.

@emir01
Created November 2, 2020 08:02
Show Gist options
  • Save emir01/9858246f84964fd5bde923fc334fe4fe to your computer and use it in GitHub Desktop.
Save emir01/9858246f84964fd5bde923fc334fe4fe to your computer and use it in GitHub Desktop.
The full Code for the Roslyn Blog Post Part 2 - Analyzer
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace MultipleMethodCallAnalyzer.Diagnostics
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class Analyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "MultipleMethodCallDiagnosticAnalyzer";
private static readonly string Title = "Multiple Method Invocation";
public static readonly string MessageFormat =
@"{0} called multiple times with identical arguments: [{1}]";
private static readonly string Description = "Multiple Identical Method Invocation";
private const string Category = "Usage";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
Category, DiagnosticSeverity.Warning, true, Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze |
GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCodeBlockStartAction<SyntaxKind>(analysisContext =>
{
if (analysisContext.OwningSymbol.Kind != SymbolKind.Method)
{
return;
}
// create a new analyzer for this code block
var analyzer = new StatefulNodeAnalyzer();
analysisContext.RegisterSyntaxNodeAction(
ctx => analyzer.AnalyzeSyntaxNode(ctx, analysisContext.CodeBlock),
SyntaxKind.InvocationExpression);
});
}
private class StatefulNodeAnalyzer
{
public void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context,
SyntaxNode methodCodeBlock)
{
// we are going to run the analysis in the context of the code block
// which for us would be the method.
//
// We want to search for any invocations within that method code block - for now!
var semanticModel = context.SemanticModel;
var node = (InvocationExpressionSyntax)context.Node;
var methodSymbol = (IMethodSymbol)semanticModel.GetSymbolInfo(node).Symbol;
// todo We should maybe only be interested in Methods returning Reference Types ?
if (methodSymbol != null && methodSymbol.ReturnsVoid)
{
return;
}
// traverse the code block
var allInvocationExpressions =
methodCodeBlock.DescendantNodes().OfType<InvocationExpressionSyntax>().ToList();
// if there are no other invocations except the current one tracked by the 'node'
// don't do any other processing. Example we only have a call to our single Invocation
if (allInvocationExpressions.Count == 1)
{
return;
}
// I want to get only the nodes that refer to the same function I'm checking for in the current
// If for example we had a call to OurMethod(input) and Console.WriteLine()
var currentNodeExpression = node.Expression.ToString();
var invocationsOnlyWithCurrentNodeExpression =
allInvocationExpressions.Where(expressionNode =>
expressionNode.Expression.ToString().Equals(currentNodeExpression)).ToList();
// if we end up with 1 then there are no other invocations matching current node.
if (invocationsOnlyWithCurrentNodeExpression.Count == 1)
{
return;
}
// Now we get the invocations that are not the Current one by looking at
// the location where they start in the code block
var otherInvocationsMatchingCurrent =
invocationsOnlyWithCurrentNodeExpression.Where(inv => inv.Span.Start != node.Span.Start).ToList();
// simple argument matching
var currentArgumentsList = node.ArgumentList.Arguments;
var invocationsWithMatchingArgumentList = otherInvocationsMatchingCurrent.Where(inv =>
ArgumentListsMatch(currentArgumentsList, inv.ArgumentList.Arguments)).ToList();
if (invocationsWithMatchingArgumentList.Count > 0)
{
var methodName = currentNodeExpression;
var argumentList = node.ArgumentList.Arguments.ToString();
var diagnostic = Diagnostic.Create(Rule, node.GetLocation(), methodName, argumentList);
context.ReportDiagnostic(diagnostic);
}
}
// todo: move to helpers
private bool ArgumentListsMatch(SeparatedSyntaxList<ArgumentSyntax> originalArgumentList,
SeparatedSyntaxList<ArgumentSyntax> toMatchArgumentList)
{
if (originalArgumentList.Count != toMatchArgumentList.Count)
{
return false;
}
for (int i = 0; i < originalArgumentList.Count; i++)
{
var originalCandidate = originalArgumentList[i];
var toMatchCandidate = toMatchArgumentList[i];
if (originalCandidate.Expression.ToString() != toMatchCandidate.Expression.ToString())
{
return false;
}
}
return true;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment