Skip to content

Commit

Permalink
Optimize using alias cache to only check using statements once per sy…
Browse files Browse the repository at this point in the history
…ntax tree, by filling the dictionary directly when it is created. In the previous commit, it was checked for global using aliases once and then again for normal using aliases.

DotNetAnalyzers#3594
  • Loading branch information
bjornhellander committed Jun 19, 2023
1 parent 0101151 commit f2a69ed
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 30 deletions.
52 changes: 28 additions & 24 deletions StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
namespace StyleCop.Analyzers.Helpers
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
Expand All @@ -23,18 +24,17 @@ internal static class SyntaxTreeHelpers
/// <para>This allows many analyzers that run on every token in the file to avoid checking
/// the same state in the document repeatedly.</para>
/// </remarks>
private static Tuple<WeakReference<Compilation>, ConcurrentDictionary<SyntaxTree, bool>> usingAliasCache
= Tuple.Create(new WeakReference<Compilation>(null), default(ConcurrentDictionary<SyntaxTree, bool>));
private static Tuple<WeakReference<Compilation>, IReadOnlyDictionary<SyntaxTree, bool>> usingAliasCache
= Tuple.Create(new WeakReference<Compilation>(null), default(IReadOnlyDictionary<SyntaxTree, bool>));

public static ConcurrentDictionary<SyntaxTree, bool> GetOrCreateUsingAliasCache(this Compilation compilation)
public static IReadOnlyDictionary<SyntaxTree, bool> GetOrCreateUsingAliasCache(this Compilation compilation)
{
var cache = usingAliasCache;

Compilation cachedCompilation;
if (!cache.Item1.TryGetTarget(out cachedCompilation) || cachedCompilation != compilation)
{
var containsGlobalUsingAlias = ContainsGlobalUsingAliasNoCache(compilation);
var replacementDictionary = containsGlobalUsingAlias ? null : new ConcurrentDictionary<SyntaxTree, bool>();
var replacementDictionary = CreateDictionary(compilation);
var replacementCache = Tuple.Create(new WeakReference<Compilation>(compilation), replacementDictionary);

while (true)
Expand Down Expand Up @@ -75,7 +75,7 @@ public static bool IsWhitespaceOnly(this SyntaxTree tree, CancellationToken canc
&& TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia) == -1;
}

internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary<SyntaxTree, bool> cache)
internal static bool ContainsUsingAlias(this SyntaxTree tree, IReadOnlyDictionary<SyntaxTree, bool> cache)
{
if (tree == null)
{
Expand All @@ -88,35 +88,39 @@ internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictiona
return true;
}

bool result;
if (cache.TryGetValue(tree, out result))
if (cache.TryGetValue(tree, out var result))
{
return result;
}

bool generated = ContainsUsingAliasNoCache(tree);
cache.TryAdd(tree, generated);
return generated;
Debug.Assert(false, "This should not happen. Syntax tree could not be found in cache!");
return false;
}

private static bool ContainsUsingAliasNoCache(SyntaxTree tree)
private static IReadOnlyDictionary<SyntaxTree, bool> CreateDictionary(Compilation compilation)
{
var nodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration));
var result = new Dictionary<SyntaxTree, bool>();

return nodes.OfType<UsingDirectiveSyntax>().Any(x => x.Alias != null);
}
foreach (var tree in compilation.SyntaxTrees)
{
CheckUsingAliases(tree, out var containsUsingAlias, out var containsGlobalUsingAlias);
if (containsGlobalUsingAlias)
{
return null;
}

private static bool ContainsGlobalUsingAliasNoCache(Compilation compilation)
{
return compilation.SyntaxTrees.Any(ContainsGlobalUsingAliasNoCache);
result.Add(tree, containsUsingAlias);
}

return result;
}

private static bool ContainsGlobalUsingAliasNoCache(SyntaxTree tree)
private static void CheckUsingAliases(SyntaxTree tree, out bool containsUsingAlias, out bool containsGlobalUsingAlias)
{
var nodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration));

var relevantNodes = nodes.OfType<UsingDirectiveSyntax>().ToArray();
return relevantNodes.Any(x => x.Alias != null && !x.GlobalKeyword().IsKind(SyntaxKind.None));
var usingNodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration)).OfType<UsingDirectiveSyntax>();
var usingAliasNodes = usingNodes.Where(x => x.Alias != null).ToList();
containsUsingAlias = usingAliasNodes.Any();
containsGlobalUsingAlias = usingAliasNodes.Any(x => !x.GlobalKeyword().IsKind(SyntaxKind.None));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace StyleCop.Analyzers.MaintainabilityRules
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -78,15 +78,15 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte
/// </summary>
private sealed class AnalyzerInstance
{
private readonly ConcurrentDictionary<SyntaxTree, bool> usingAliasCache;
private readonly IReadOnlyDictionary<SyntaxTree, bool> usingAliasCache;

/// <summary>
/// A lazily-initialized reference to <see cref="SuppressMessageAttribute"/> within the context of a
/// particular <see cref="Compilation"/>.
/// </summary>
private INamedTypeSymbol suppressMessageAttribute;

public AnalyzerInstance(ConcurrentDictionary<SyntaxTree, bool> usingAliasCache)
public AnalyzerInstance(IReadOnlyDictionary<SyntaxTree, bool> usingAliasCache)
{
this.usingAliasCache = usingAliasCache;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace StyleCop.Analyzers.ReadabilityRules
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -159,9 +159,9 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte

private sealed class Analyzer
{
private readonly ConcurrentDictionary<SyntaxTree, bool> usingAliasCache;
private readonly IReadOnlyDictionary<SyntaxTree, bool> usingAliasCache;

public Analyzer(ConcurrentDictionary<SyntaxTree, bool> usingAliasCache)
public Analyzer(IReadOnlyDictionary<SyntaxTree, bool> usingAliasCache)
{
this.usingAliasCache = usingAliasCache;
}
Expand Down

0 comments on commit f2a69ed

Please sign in to comment.