Skip to content

Commit

Permalink
Merge pull request #1304 from sharwell/concurrent-cache
Browse files Browse the repository at this point in the history
Improve concurrency behavior of the generated code cache
  • Loading branch information
sharwell committed Aug 29, 2015
2 parents f85b366 + f2f6a3c commit 3253066
Show file tree
Hide file tree
Showing 141 changed files with 1,322 additions and 739 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxTreeActionHonorExclusions(this.AnalyzeTree);
context.RegisterCompilationStartAction(HandleCompilationStart);
}

private void AnalyzeTree(SyntaxTreeAnalysisContext context)
private static void HandleCompilationStart(CompilationStartAnalysisContext context)
{
context.RegisterSyntaxTreeActionHonorExclusions(AnalyzeTree);
}

private static void AnalyzeTree(SyntaxTreeAnalysisContext context)
{
// Report a diagnostic if we got called
context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Tree.GetLocation(TextSpan.FromBounds(0, 0))));
Expand Down
102 changes: 47 additions & 55 deletions StyleCop.Analyzers/StyleCop.Analyzers/AnalyzerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
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 @@ -15,29 +18,14 @@ namespace StyleCop.Analyzers
public static class AnalyzerExtensions
{
/// <summary>
/// Register an action to be executed at completion of parsing of a code document. A syntax tree action reports
/// diagnostics about the <see cref="SyntaxTree"/> of a document.
/// A cache of the result of computing whether a document has an auto-generated header.
/// </summary>
/// <remarks>This method honors exclusions.</remarks>
/// <param name="context">The analysis context.</param>
/// <param name="action">Action to be executed at completion of parsing of a document.</param>
public static void RegisterSyntaxTreeActionHonorExclusions(this AnalysisContext context, Action<SyntaxTreeAnalysisContext> action)
{
context.RegisterSyntaxTreeAction(
c =>
{
if (c.IsGeneratedDocument())
{
return;
}

// Honor the containing document item's ExcludeFromStylecop=True
// MSBuild metadata, if analyzers have access to it.
//// TODO: code here

action(c);
});
}
/// <remarks>
/// 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>>();

/// <summary>
/// Register an action to be executed at completion of parsing of a code document. A syntax tree action reports
Expand All @@ -48,10 +36,13 @@ public static void RegisterSyntaxTreeActionHonorExclusions(this AnalysisContext
/// <param name="action">Action to be executed at completion of parsing of a document.</param>
public static void RegisterSyntaxTreeActionHonorExclusions(this CompilationStartAnalysisContext context, Action<SyntaxTreeAnalysisContext> action)
{
Compilation compilation = context.Compilation;
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);

context.RegisterSyntaxTreeAction(
c =>
{
if (c.IsGeneratedDocument())
if (c.IsGeneratedDocument(cache))
{
return;
}
Expand All @@ -70,20 +61,23 @@ public static void RegisterSyntaxTreeActionHonorExclusions(this CompilationStart
/// collect state information to be used by other syntax node actions or code block end actions.
/// </summary>
/// <remarks>This method honors exclusions.</remarks>
/// <param name="context">Action will be executed only if a <see cref="SyntaxNode"/>'s kind matches one of the
/// <paramref name="syntaxKinds"/> values.</param>
/// <param name="context">Action will be executed only if the kind of a <see cref="SyntaxNode"/> matches one of
/// the <paramref name="syntaxKinds"/> values.</param>
/// <param name="action">Action to be executed at completion of semantic analysis of a
/// <see cref="SyntaxNode"/>.</param>
/// <param name="syntaxKinds">The kinds of syntax that should be analyzed.</param>
/// <typeparam name="TLanguageKindEnum">Enum type giving the syntax node kinds of the source language for which
/// the action applies.</typeparam>
public static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(this AnalysisContext context, Action<SyntaxNodeAnalysisContext> action, params TLanguageKindEnum[] syntaxKinds)
public static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(this CompilationStartAnalysisContext context, Action<SyntaxNodeAnalysisContext> action, params TLanguageKindEnum[] syntaxKinds)
where TLanguageKindEnum : struct
{
Compilation compilation = context.Compilation;
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);

context.RegisterSyntaxNodeAction(
c =>
{
if (c.IsGenerated())
if (c.IsGenerated(cache))
{
return;
}
Expand All @@ -98,36 +92,34 @@ public static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(th
}

/// <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
/// collect state information to be used by other syntax node actions or code block end actions.
/// Checks whether the given document is auto generated by a tool (based on filename or comment header).
/// </summary>
/// <remarks>This method honors exclusions.</remarks>
/// <param name="context">Action will be executed only if the kind of a <see cref="SyntaxNode"/> matches one of
/// the <paramref name="syntaxKinds"/> values.</param>
/// <param name="action">Action to be executed at completion of semantic analysis of a
/// <see cref="SyntaxNode"/>.</param>
/// <param name="syntaxKinds">The kinds of syntax that should be analyzed.</param>
/// <typeparam name="TLanguageKindEnum">Enum type giving the syntax node kinds of the source language for which
/// the action applies.</typeparam>
public static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(this CompilationStartAnalysisContext context, Action<SyntaxNodeAnalysisContext> action, params TLanguageKindEnum[] syntaxKinds)
where TLanguageKindEnum : struct
/// <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)
{
context.RegisterSyntaxNodeAction(
c =>
{
if (c.IsGenerated())
{
return;
}

// Honor the containing document item's ExcludeFromStylecop=True
// MSBuild metadata, if analyzers have access to it.
//// TODO: code here

action(c);
},
syntaxKinds);
ConcurrentDictionary<SyntaxTree, bool> cache = GeneratedHeaderCache.GetOrCreateValue(compilation);
return tree.IsGeneratedDocument(cache, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ public abstract class ElementDocumentationSummaryBase : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(this.HandleCompilationStart);
}

/// <summary>
/// Analyzes the top-level <c>&lt;summary&gt;</c> element of a documentation comment.
/// </summary>
/// <param name="context">The current analysis context.</param>
/// <param name="syntax">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node
/// to examine.</param>
/// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param>
protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, params Location[] diagnosticLocations);

private void HandleCompilationStart(CompilationStartAnalysisContext context)
{
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.StructDeclaration);
Expand All @@ -30,15 +44,6 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleFieldDeclaration, SyntaxKind.EventFieldDeclaration);
}

/// <summary>
/// Analyzes the top-level <c>&lt;summary&gt;</c> element of a documentation comment.
/// </summary>
/// <param name="context">The current analysis context.</param>
/// <param name="syntax">The <see cref="XmlElementSyntax"/> or <see cref="XmlEmptyElementSyntax"/> of the node
/// to examine.</param>
/// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param>
protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, params Location[] diagnosticLocations);

private void HandleTypeDeclaration(SyntaxNodeAnalysisContext context)
{
var node = (BaseTypeDeclarationSyntax)context.Node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ public abstract class PartialElementDocumentationSummaryBase : DiagnosticAnalyze
/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.StructDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.InterfaceDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleMethodDeclaration, SyntaxKind.MethodDeclaration);
context.RegisterCompilationStartAction(this.HandleCompilationStart);
}

/// <summary>
Expand All @@ -31,6 +28,14 @@ public override void Initialize(AnalysisContext context)
/// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param>
protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, params Location[] diagnosticLocations);

private void HandleCompilationStart(CompilationStartAnalysisContext context)
{
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.StructDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleTypeDeclaration, SyntaxKind.InterfaceDeclaration);
context.RegisterSyntaxNodeActionHonorExclusions(this.HandleMethodDeclaration, SyntaxKind.MethodDeclaration);
}

private void HandleTypeDeclaration(SyntaxNodeAnalysisContext context)
{
var node = (BaseTypeDeclarationSyntax)context.Node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public abstract class PropertyDocumentationSummaryBase : DiagnosticAnalyzer
/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeActionHonorExclusions(this.HandlePropertyDeclaration, SyntaxKind.PropertyDeclaration);
context.RegisterCompilationStartAction(this.HandleCompilationStart);
}

/// <summary>
Expand All @@ -26,6 +26,11 @@ public override void Initialize(AnalysisContext context)
/// <param name="diagnosticLocations">The location(s) where diagnostics, if any, should be reported.</param>
protected abstract void HandleXmlElement(SyntaxNodeAnalysisContext context, XmlNodeSyntax syntax, params Location[] diagnosticLocations);

private void HandleCompilationStart(CompilationStartAnalysisContext context)
{
context.RegisterSyntaxNodeActionHonorExclusions(this.HandlePropertyDeclaration, SyntaxKind.PropertyDeclaration);
}

private void HandlePropertyDeclaration(SyntaxNodeAnalysisContext context)
{
var node = (PropertyDeclarationSyntax)context.Node;
Expand Down
Loading

0 comments on commit 3253066

Please sign in to comment.