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;
+ }
+}
+