From cc8ce746420077504b98130939797d8eeccfd9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Hellander?= Date: Mon, 19 Jun 2023 21:00:55 +0200 Subject: [PATCH] Optimize using alias cache to only check using statements once per syntax 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. #3594 --- .../Helpers/SyntaxTreeHelpers.cs | 52 ++++++++++--------- ...nalysisSuppressionMustHaveJustification.cs | 6 +-- .../SA1121UseBuiltInTypeAlias.cs | 6 +-- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs index ff0b17281..9e2470720 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs @@ -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; @@ -23,18 +24,17 @@ internal static class SyntaxTreeHelpers /// This allows many analyzers that run on every token in the file to avoid checking /// the same state in the document repeatedly. /// - private static Tuple, ConcurrentDictionary> usingAliasCache - = Tuple.Create(new WeakReference(null), default(ConcurrentDictionary)); + private static Tuple, IReadOnlyDictionary> usingAliasCache + = Tuple.Create(new WeakReference(null), default(IReadOnlyDictionary)); - public static ConcurrentDictionary GetOrCreateUsingAliasCache(this Compilation compilation) + public static IReadOnlyDictionary 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(); + var replacementDictionary = CreateDictionary(compilation); var replacementCache = Tuple.Create(new WeakReference(compilation), replacementDictionary); while (true) @@ -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 cache) + internal static bool ContainsUsingAlias(this SyntaxTree tree, IReadOnlyDictionary cache) { if (tree == null) { @@ -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 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(); - return nodes.OfType().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().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(); + var usingAliasNodes = usingNodes.Where(x => x.Alias != null).ToList(); + containsUsingAlias = usingAliasNodes.Any(); + containsGlobalUsingAlias = usingAliasNodes.Any(x => !x.GlobalKeyword().IsKind(SyntaxKind.None)); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs index 10b7e65d2..b0d1d8d5d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs @@ -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; @@ -78,7 +78,7 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte /// private sealed class AnalyzerInstance { - private readonly ConcurrentDictionary usingAliasCache; + private readonly IReadOnlyDictionary usingAliasCache; /// /// A lazily-initialized reference to within the context of a @@ -86,7 +86,7 @@ private sealed class AnalyzerInstance /// private INamedTypeSymbol suppressMessageAttribute; - public AnalyzerInstance(ConcurrentDictionary usingAliasCache) + public AnalyzerInstance(IReadOnlyDictionary usingAliasCache) { this.usingAliasCache = usingAliasCache; } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs index 8f86c7322..6c820eddd 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs @@ -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; @@ -159,9 +159,9 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte private sealed class Analyzer { - private readonly ConcurrentDictionary usingAliasCache; + private readonly IReadOnlyDictionary usingAliasCache; - public Analyzer(ConcurrentDictionary usingAliasCache) + public Analyzer(IReadOnlyDictionary usingAliasCache) { this.usingAliasCache = usingAliasCache; }