I am looking for a way to find every interface methods with only one reference
In other words : methods that are implemented but never called.
My objective is to cleanup dead code.
Thanks,
CodePudding user response:
You can write a command-line tool to do this fairly easily, using Roslyn. Load your project into an MSBuildWorkspace
, explore the semantic tree to get all methods, then use SymbolFinder.FindReferencesAsync
to find all references to each method, and print a warning for each method which has no references.
You can also write this as a Roslyn Analyzer. However, you can't use SymbolFinder
in this context (you don't have a workspace available, and it would be too slow for an analyzer anyway), You can however use RegisterOperationAction
to look for places which call or reference a method, and at the same time use RegisterSymbolAction
to look for all methods, then put that together to find all methods which aren't referened.
Something like this is probably a starting point. It care whether a method is defined on an interface or implemented from an interface (that would be fairly easy to add, once you've clarified the rules here), and it's only been superficially tested, but it appears to work.
(You'll need to create a new project using the Analyzer template: follow a tutorial if you've never written an analyzer before).
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class UnusedMethodAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "UNUSED";
private const string Category = "Unused Methods";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, "Method is not referenced", "Method '{0}' is not referenced", Category, DiagnosticSeverity.Warning, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(Analyze);
}
private void Analyze(CompilationStartAnalysisContext context)
{
// Key: IMethodSymbol. Value: true if we've seen a reference to this method, false if we've just seen its definition
var methods = new ConcurrentDictionary<IMethodSymbol, bool>(SymbolEqualityComparer.Default);
context.RegisterOperationAction(ctx =>
{
methods.AddOrUpdate(((IInvocationOperation)ctx.Operation).TargetMethod, true, (_, _) => true);
}, OperationKind.Invocation);
context.RegisterOperationAction(ctx =>
{
methods.AddOrUpdate(((IMethodReferenceOperation)ctx.Operation).Method, true, (_, _) => true);
}, OperationKind.MethodReference);
context.RegisterSymbolAction(ctx =>
{
methods.TryAdd((IMethodSymbol)ctx.Symbol, false);
}, SymbolKind.Method);
context.RegisterCompilationEndAction(ctx =>
{
foreach (var kvp in methods)
{
if (!kvp.Value)
{
var methodSymbol = kvp.Key;
var diagnostic = Diagnostic.Create(Rule, methodSymbol.Locations[0], methodSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat));
ctx.ReportDiagnostic(diagnostic);
}
}
});
}
}