diff --git a/src/.idea/.idea.Acuminator/.idea/.gitignore b/src/.idea/.idea.Acuminator/.idea/.gitignore new file mode 100644 index 000000000..a03673464 --- /dev/null +++ b/src/.idea/.idea.Acuminator/.idea/.gitignore @@ -0,0 +1,15 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/.idea.Acuminator.iml +/contentModel.xml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/.idea/.idea.Acuminator/.idea/.name b/src/.idea/.idea.Acuminator/.idea/.name new file mode 100644 index 000000000..5df8cc921 --- /dev/null +++ b/src/.idea/.idea.Acuminator/.idea/.name @@ -0,0 +1 @@ +Acuminator \ No newline at end of file diff --git a/src/.idea/.idea.Acuminator/.idea/indexLayout.xml b/src/.idea/.idea.Acuminator/.idea/indexLayout.xml new file mode 100644 index 000000000..7b08163ce --- /dev/null +++ b/src/.idea/.idea.Acuminator/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Acuminator/.idea/vcs.xml b/src/.idea/.idea.Acuminator/.idea/vcs.xml new file mode 100644 index 000000000..6c0b86358 --- /dev/null +++ b/src/.idea/.idea.Acuminator/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Acuminator/Acuminator.Utilities/Roslyn/NestedInvocationWalker.cs b/src/Acuminator/Acuminator.Utilities/Roslyn/NestedInvocationWalker.cs index 73d396e43..d466399b0 100644 --- a/src/Acuminator/Acuminator.Utilities/Roslyn/NestedInvocationWalker.cs +++ b/src/Acuminator/Acuminator.Utilities/Roslyn/NestedInvocationWalker.cs @@ -53,6 +53,8 @@ public abstract class NestedInvocationWalker : CSharpSyntaxWalker private readonly ISet<(SyntaxNode, DiagnosticDescriptor)> _reportedDiagnostics = new HashSet<(SyntaxNode, DiagnosticDescriptor)>(); + private readonly SymbolInfoCache _symbolsCache; + /// /// Cancellation token /// @@ -87,6 +89,8 @@ protected NestedInvocationWalker(PXContext pxContext, CancellationToken cancella //Use lazy to avoid calling virtual methods inside the constructor _typesToBypass = new Lazy>(valueFactory: GetTypesToBypass, isThreadSafe: false); + + _symbolsCache = new SymbolInfoCache(); } /// @@ -119,20 +123,22 @@ protected virtual HashSet GetTypesToBypass() => protected virtual T? GetSymbol(ExpressionSyntax node) where T : class, ISymbol { - var semanticModel = GetSemanticModel(node.SyntaxTree); - - if (semanticModel != null) + SymbolInfo? cached = _symbolsCache.GetOrCreate(node, () => { - var symbolInfo = semanticModel.GetSymbolInfo(node, CancellationToken); + SemanticModel? semanticModel = GetSemanticModel(node.SyntaxTree); + return semanticModel?.GetSymbolInfo(node, CancellationToken); + }); - if (symbolInfo.Symbol is T symbol) + if (cached is not null) + { + if (cached.Value.Symbol is T symbol) { return symbol; } - if (!symbolInfo.CandidateSymbols.IsEmpty) + if (!cached.Value.CandidateSymbols.IsEmpty) { - return symbolInfo.CandidateSymbols.OfType().FirstOrDefault(); + return cached.Value.CandidateSymbols.OfType().FirstOrDefault(); } } @@ -204,8 +210,16 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node) public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node) { - VisitPropertyOrIndexerAccessExpression(node); - base.VisitMemberAccessExpression(node); + if (node.Parent is InvocationExpressionSyntax invocation && invocation.Expression == node) + { + // we already visit this node by VisitInvocationExpression, so we just skip it here + base.VisitMemberAccessExpression(node); + } + else + { + VisitPropertyOrIndexerAccessExpression(node); + base.VisitMemberAccessExpression(node); + } } public override void VisitElementAccessExpression(ElementAccessExpressionSyntax node) diff --git a/src/Acuminator/Acuminator.Utilities/Roslyn/SymbolInfoCache.cs b/src/Acuminator/Acuminator.Utilities/Roslyn/SymbolInfoCache.cs new file mode 100644 index 000000000..a66215043 --- /dev/null +++ b/src/Acuminator/Acuminator.Utilities/Roslyn/SymbolInfoCache.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Acuminator.Utilities.Roslyn; + +/// +/// Caches Roslyn symbol lookup results for expression syntax nodes visited by a syntax walker. +/// +public sealed class SymbolInfoCache +{ + private readonly Dictionary _map = new(); + + /// + /// Gets the cached symbol information for the specified expression, or creates and stores it using the provided factory. + /// + public SymbolInfo? GetOrCreate(ExpressionSyntax key, Func factory) + { + if (_map.TryGetValue(key, out SymbolInfo? cached)) + { + return cached; + } + + SymbolInfo? potentialValue = factory(); + _map[key] = potentialValue; + + return potentialValue; + } +} +