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 concurrency behavior of the generated code cache #1304

Merged
merged 3 commits into from
Aug 29, 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
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