Skip to content

Commit 841ceab

Browse files
committed
Encapsulate the using alias cache
1 parent 65ab771 commit 841ceab

File tree

4 files changed

+95
-47
lines changed

4 files changed

+95
-47
lines changed

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxTreeHelpers.cs

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,9 @@
66
namespace StyleCop.Analyzers.Helpers
77
{
88
using System;
9-
using System.Collections.Concurrent;
10-
using System.Linq;
119
using System.Threading;
1210
using Microsoft.CodeAnalysis;
1311
using Microsoft.CodeAnalysis.CSharp;
14-
using Microsoft.CodeAnalysis.CSharp.Syntax;
15-
using StyleCop.Analyzers.Lightup;
1612

1713
internal static class SyntaxTreeHelpers
1814
{
@@ -23,17 +19,17 @@ internal static class SyntaxTreeHelpers
2319
/// <para>This allows many analyzers that run on every token in the file to avoid checking
2420
/// the same state in the document repeatedly.</para>
2521
/// </remarks>
26-
private static Tuple<WeakReference<Compilation>, ConcurrentDictionary<SyntaxTree, bool>> usingAliasCache
27-
= Tuple.Create(new WeakReference<Compilation>(null), default(ConcurrentDictionary<SyntaxTree, bool>));
22+
private static Tuple<WeakReference<Compilation>, UsingAliasCache> usingAliasCache
23+
= Tuple.Create(new WeakReference<Compilation>(null), default(UsingAliasCache));
2824

29-
public static ConcurrentDictionary<SyntaxTree, bool> GetOrCreateUsingAliasCache(this Compilation compilation)
25+
public static UsingAliasCache GetOrCreateUsingAliasCache(this Compilation compilation)
3026
{
3127
var cache = usingAliasCache;
3228

3329
Compilation cachedCompilation;
3430
if (!cache.Item1.TryGetTarget(out cachedCompilation) || cachedCompilation != compilation)
3531
{
36-
var replacementCache = Tuple.Create(new WeakReference<Compilation>(compilation), new ConcurrentDictionary<SyntaxTree, bool>());
32+
var replacementCache = Tuple.Create(new WeakReference<Compilation>(compilation), new UsingAliasCache());
3733
while (true)
3834
{
3935
var prior = Interlocked.CompareExchange(ref usingAliasCache, replacementCache, cache);
@@ -71,37 +67,5 @@ public static bool IsWhitespaceOnly(this SyntaxTree tree, CancellationToken canc
7167
return firstToken.IsKind(SyntaxKind.EndOfFileToken)
7268
&& TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia) == -1;
7369
}
74-
75-
internal static bool ContainsUsingAlias(this SyntaxTree tree, ConcurrentDictionary<SyntaxTree, bool> cache, SemanticModel semanticModel, CancellationToken cancellationToken)
76-
{
77-
if (tree == null)
78-
{
79-
return false;
80-
}
81-
82-
bool result;
83-
if (cache.TryGetValue(tree, out result))
84-
{
85-
return result;
86-
}
87-
88-
bool generated = ContainsUsingAliasNoCache(tree, semanticModel, cancellationToken);
89-
cache.TryAdd(tree, generated);
90-
return generated;
91-
}
92-
93-
private static bool ContainsUsingAliasNoCache(SyntaxTree tree, SemanticModel semanticModel, CancellationToken cancellationToken)
94-
{
95-
// Check for "local" using aliases
96-
var nodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration));
97-
if (nodes.OfType<UsingDirectiveSyntax>().Any(x => x.Alias != null))
98-
{
99-
return true;
100-
}
101-
102-
// Check for global using aliases
103-
var scopes = semanticModel.GetImportScopes(0, cancellationToken);
104-
return scopes.Any(x => x.Aliases.Length > 0);
105-
}
10670
}
10771
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Helpers
5+
{
6+
using System;
7+
using System.Collections.Concurrent;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Text;
11+
using System.Threading;
12+
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CSharp;
14+
using Microsoft.CodeAnalysis.CSharp.Syntax;
15+
using StyleCop.Analyzers.Lightup;
16+
17+
internal sealed class UsingAliasCache
18+
{
19+
/// <summary>
20+
/// Caches a value indicating whether the compilation has any global using aliases.
21+
/// </summary>
22+
/// <value>
23+
/// <para>One of the following:</para>
24+
///
25+
/// <list type="bullet">
26+
/// <item><description>-1: if the value has not yet been computed</description></item>
27+
/// <item><description>0: if the compilation does not have any global using aliases</description></item>
28+
/// <item><description>1: if the compilation has one or more global using aliases</description></item>
29+
/// </list>
30+
/// </value>
31+
private int perCompilationCache = -1;
32+
33+
/// <summary>
34+
/// A cache of individual syntax trees to a value indicating whether that syntax tree has any local using
35+
/// aliases.
36+
/// </summary>
37+
/// <remarks><para>This field is only initialized to a non-null value when <see cref="perCompilationCache"/> is
38+
/// 0 (i.e. the compilation is known to not have any global using aliases).</para></remarks>
39+
private ConcurrentDictionary<SyntaxTree, bool>? perSyntaxTreeCache;
40+
41+
internal bool ContainsUsingAlias(SyntaxTree tree, SemanticModel semanticModel, CancellationToken cancellationToken)
42+
{
43+
if (tree == null)
44+
{
45+
return false;
46+
}
47+
48+
if (this.ContainsGlobalUsingAlias(semanticModel, cancellationToken))
49+
{
50+
return true;
51+
}
52+
53+
var cache = LazyInitializer.EnsureInitialized(ref this.perSyntaxTreeCache, static () => new ConcurrentDictionary<SyntaxTree, bool>())!;
54+
55+
bool result;
56+
if (cache.TryGetValue(tree, out result))
57+
{
58+
return result;
59+
}
60+
61+
bool generated = ContainsLocalUsingAliasNoCache(tree);
62+
cache.TryAdd(tree, generated);
63+
return generated;
64+
}
65+
66+
private static bool ContainsLocalUsingAliasNoCache(SyntaxTree tree)
67+
{
68+
// Check for "local" using aliases
69+
var nodes = tree.GetRoot().DescendantNodes(node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration));
70+
return nodes.OfType<UsingDirectiveSyntax>().Any(x => x.Alias != null);
71+
}
72+
73+
private bool ContainsGlobalUsingAlias(SemanticModel semanticModel, CancellationToken cancellationToken)
74+
{
75+
if (this.perCompilationCache == -1)
76+
{
77+
// Check for global using aliases
78+
var scopes = semanticModel.GetImportScopes(0, cancellationToken);
79+
Interlocked.CompareExchange(ref this.perCompilationCache, scopes.Any(x => x.Aliases.Length > 0) ? 1 : 0, -1);
80+
}
81+
82+
return this.perCompilationCache == 1;
83+
}
84+
}
85+
}

StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1404CodeAnalysisSuppressionMustHaveJustification.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
namespace StyleCop.Analyzers.MaintainabilityRules
77
{
88
using System;
9-
using System.Collections.Concurrent;
109
using System.Collections.Immutable;
1110
using System.Diagnostics.CodeAnalysis;
1211
using Microsoft.CodeAnalysis;
@@ -78,15 +77,15 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte
7877
/// </summary>
7978
private sealed class AnalyzerInstance
8079
{
81-
private readonly ConcurrentDictionary<SyntaxTree, bool> usingAliasCache;
80+
private readonly UsingAliasCache usingAliasCache;
8281

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

89-
public AnalyzerInstance(ConcurrentDictionary<SyntaxTree, bool> usingAliasCache)
88+
public AnalyzerInstance(UsingAliasCache usingAliasCache)
9089
{
9190
this.usingAliasCache = usingAliasCache;
9291
}
@@ -96,7 +95,7 @@ public void HandleAttributeNode(SyntaxNodeAnalysisContext context)
9695
var attribute = (AttributeSyntax)context.Node;
9796

9897
// Return fast if the name doesn't match and the file doesn't contain any using alias directives
99-
if (!attribute.SyntaxTree.ContainsUsingAlias(this.usingAliasCache, context.SemanticModel, context.CancellationToken))
98+
if (!this.usingAliasCache.ContainsUsingAlias(attribute.SyntaxTree, context.SemanticModel, context.CancellationToken))
10099
{
101100
if (!(attribute.Name is SimpleNameSyntax simpleNameSyntax))
102101
{

StyleCop.Analyzers/StyleCop.Analyzers/ReadabilityRules/SA1121UseBuiltInTypeAlias.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ private static void HandleCompilationStart(CompilationStartAnalysisContext conte
159159

160160
private sealed class Analyzer
161161
{
162-
private readonly ConcurrentDictionary<SyntaxTree, bool> usingAliasCache;
162+
private readonly UsingAliasCache usingAliasCache;
163163

164-
public Analyzer(ConcurrentDictionary<SyntaxTree, bool> usingAliasCache)
164+
public Analyzer(UsingAliasCache usingAliasCache)
165165
{
166166
this.usingAliasCache = usingAliasCache;
167167
}
@@ -211,7 +211,7 @@ public void HandleIdentifierNameSyntax(SyntaxNodeAnalysisContext context, StyleC
211211
// Most source files will not have any using alias directives. Then we don't have to use semantics
212212
// if the identifier name doesn't match the name of a special type
213213
if (settings.ReadabilityRules.AllowBuiltInTypeAliases
214-
|| !identifierNameSyntax.SyntaxTree.ContainsUsingAlias(this.usingAliasCache, context.SemanticModel, context.CancellationToken))
214+
|| !this.usingAliasCache.ContainsUsingAlias(identifierNameSyntax.SyntaxTree, context.SemanticModel, context.CancellationToken))
215215
{
216216
switch (identifierNameSyntax.Identifier.ValueText)
217217
{

0 commit comments

Comments
 (0)