Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve state management in analyzers #1671

Merged
merged 4 commits into from
Oct 23, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 38 additions & 36 deletions StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ namespace StyleCop.Analyzers
{
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
Expand All @@ -22,8 +21,8 @@ internal static class AnalyzerExtensions
/// This allows many analyzers that run on every token in the file to avoid checking
/// the same state in the document repeatedly.
/// </remarks>
private static readonly ConditionalWeakTable<Compilation, ConcurrentDictionary<SyntaxTree, bool>> GeneratedHeaderCache
= new ConditionalWeakTable<Compilation, ConcurrentDictionary<SyntaxTree, bool>>();
private static Tuple<WeakReference<Compilation>, ConcurrentDictionary<SyntaxTree, bool>> generatedHeaderCache
= Tuple.Create(new WeakReference<Compilation>(null), default(ConcurrentDictionary<SyntaxTree, bool>));

/// <summary>
/// Register an action to be executed at completion of parsing of a code document. A syntax tree action reports
Expand All @@ -35,7 +34,7 @@ private static readonly ConditionalWeakTable<Compilation, ConcurrentDictionary<S
public static void RegisterSyntaxTreeActionHonorExclusions(this CompilationStartAnalysisContext context, Action<SyntaxTreeAnalysisContext> action)
{
Compilation compilation = context.Compilation;
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);
ConcurrentDictionary<SyntaxTree, bool> cache = GetOrCreateGeneratedDocumentCache(compilation);

context.RegisterSyntaxTreeAction(
c =>
Expand All @@ -53,6 +52,40 @@ public static void RegisterSyntaxTreeActionHonorExclusions(this CompilationStart
});
}

/// <summary>
/// Gets or creates a cache which can be used with <see cref="GeneratedCodeAnalysisExtensions"/> methods to
/// efficiently determine whether or not a source file is considered generated.
/// </summary>
/// <param name="compilation">The compilation which the cache applies to.</param>
/// <returns>A cache which tracks the syntax trees in a compilation which are considered generated.</returns>
public static ConcurrentDictionary<SyntaxTree, bool> GetOrCreateGeneratedDocumentCache(this Compilation compilation)
{
var headerCache = generatedHeaderCache;

Compilation cachedCompilation;
if (!headerCache.Item1.TryGetTarget(out cachedCompilation) || cachedCompilation != compilation)
{
var replacementCache = Tuple.Create(new WeakReference<Compilation>(compilation), new ConcurrentDictionary<SyntaxTree, bool>());
while (true)
{
var prior = Interlocked.CompareExchange(ref generatedHeaderCache, replacementCache, headerCache);
if (prior == headerCache)
{
headerCache = replacementCache;
break;
}

headerCache = prior;
if (headerCache.Item1.TryGetTarget(out cachedCompilation) && cachedCompilation == compilation)
{
break;
}
}
}

return headerCache.Item2;
}

/// <summary>
/// Register an action to be executed at completion of semantic analysis of a <see cref="SyntaxNode"/> with an
/// appropriate kind. A syntax node action can report diagnostics about a <see cref="SyntaxNode"/>, and can also
Expand All @@ -70,7 +103,7 @@ public static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(th
where TLanguageKindEnum : struct
{
Compilation compilation = context.Compilation;
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);
ConcurrentDictionary<SyntaxTree, bool> cache = GetOrCreateGeneratedDocumentCache(compilation);

context.RegisterSyntaxNodeAction(
c =>
Expand All @@ -88,36 +121,5 @@ public static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(th
},
syntaxKinds);
}

/// <summary>
/// Checks whether the given document is auto generated by a tool (based on filename or comment header).
/// </summary>
/// <remarks>
/// <para>The exact conditions used to identify generated code are subject to change in future releases. The
/// current algorithm uses the following checks.</para>
/// <para>Code is considered generated if it meets any of the following conditions.</para>
/// <list type="bullet">
/// <item>The code is contained in a file which starts with a comment containing the text
/// <c>&lt;auto-generated</c>.</item>
/// <item>The code is contained in a file with a name matching certain patterns (case-insensitive):
/// <list type="bullet">
/// <item>*.designer.cs</item>
/// </list>
/// </item>
/// </list>
/// </remarks>
/// <param name="tree">The syntax tree to examine.</param>
/// <param name="compilation">The <see cref="Compilation"/> containing the specified <paramref name="tree"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
/// <returns>
/// <para><see langword="true"/> if <paramref name="tree"/> is located in generated code; otherwise,
/// <see langword="false"/>. If <paramref name="tree"/> is <see langword="null"/>, this method returns
/// <see langword="false"/>.</para>
/// </returns>
public static bool IsGeneratedDocument(this SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken)
{
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);
return tree.IsGeneratedDocument(cache, cancellationToken);
}
}
}
Loading