📄
Core/ReachabilityAnalyzer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Reacher.MemberCollectors; namespace Reacher; internal sealed class ReachabilityAnalyzer(IReadOnlyCollection<Compilation> compilations) { private readonly HashSet<ISymbol> reachableMembers = []; public void Analyze(ISymbol member, CancellationToken cancellationToken) { if ( !IsUserDefined(member) || !TryGetAssociatedCompilation(member, out var compilation) || !reachableMembers.Add(member) ) { return; } foreach (var declaration in member.DeclaringSyntaxReferences) { AnalyzeDeclaration(compilation, declaration, cancellationToken); } } private static bool IsUserDefined(ISymbol member) => !member.IsImplicitlyDeclared && (member.CanBeReferencedByName || member.Name is "<Main>$"); private bool TryGetAssociatedCompilation(ISymbol member, [NotNullWhen(true)] out Compilation? compilation) { compilation = compilations.FirstOrDefault(c => member.DeclaringSyntaxReferences.Any(d => !d.Span.IsEmpty && c.ContainsSyntaxTree(d.SyntaxTree)) ); return compilation is not null; } private void AnalyzeDeclaration( Compilation compilation, SyntaxReference declaration, CancellationToken cancellationToken ) { var originalNode = declaration.GetSyntax(cancellationToken); var expressions = originalNode .DescendantNodes(ShouldDescendInto(originalNode)) .OfType<ExpressionSyntax>() .Where(e => e is IdentifierNameSyntax or ImplicitObjectCreationExpressionSyntax or ObjectCreationExpressionSyntax ); var semanticModel = compilation.GetSemanticModel(declaration.SyntaxTree); foreach (var identifier in expressions) { if (!semanticModel.IsReachable(identifier)) { continue; } if ( semanticModel.GetSymbolInfo(identifier, cancellationToken) is not { Symbol: ISymbol member } || member is not (IMethodSymbol or IPropertySymbol) ) { continue; } Analyze(member, cancellationToken); } } private static Func<SyntaxNode, bool> ShouldDescendInto(SyntaxNode originalNode) => syntaxNode => syntaxNode == originalNode || !( syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement) || syntaxNode.IsKind(SyntaxKind.SimpleLambdaExpression) || syntaxNode.IsKind(SyntaxKind.ParenthesizedLambdaExpression) ); public ReachabilityAnalysis GetAnalysis() => new(reachableMembers, GetUnreachableMembers()); private IReadOnlySet<ISymbol> GetUnreachableMembers() { var collector = new PredicateMembersCollector(m => IsUserDefined(m) && !reachableMembers.Contains(m)); compilations.Accept(collector); return collector.Members; } }