Skip to content
This repository was archived by the owner on Nov 8, 2018. It is now read-only.

Commit 17115f8

Browse files
committed
Merge pull request #12 from sharwell/framework
Framework
2 parents 09baea2 + 359de14 commit 17115f8

File tree

6 files changed

+340
-0
lines changed

6 files changed

+340
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace AsyncUsageAnalyzers
2+
{
3+
using Microsoft.CodeAnalysis;
4+
5+
internal static class AnalyzerConstants
6+
{
7+
static AnalyzerConstants()
8+
{
9+
#if DEBUG
10+
// In DEBUG builds, the tests are enabled to simplify development and testing.
11+
DisabledNoTests = true;
12+
#else
13+
DisabledNoTests = false;
14+
#endif
15+
}
16+
17+
/// <summary>
18+
/// Gets a reference value which can be passed to
19+
/// <see cref="DiagnosticDescriptor(string, string, string, string, DiagnosticSeverity, bool, string, string, string[])"/>
20+
/// to disable a diagnostic which is currently untested.
21+
/// </summary>
22+
/// <value>
23+
/// A reference value which can be passed to
24+
/// <see cref="DiagnosticDescriptor(string, string, string, string, DiagnosticSeverity, bool, string, string, string[])"/>
25+
/// to disable a diagnostic which is currently untested.
26+
/// </value>
27+
internal static bool DisabledNoTests { get; }
28+
}
29+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// This file originally obtained from
2+
// https://github.com/code-cracker/code-cracker/blob/08c1a01337964924eeed12be8b14c8ce8ec6b626/src/Common/CodeCracker.Common/Extensions/AnalyzerExtensions.cs
3+
// It is subject to the Apache License 2.0
4+
// This file has been modified since obtaining it from its original source.
5+
6+
namespace AsyncUsageAnalyzers
7+
{
8+
using System;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
11+
internal static class AnalyzerExtensions
12+
{
13+
internal static void RegisterSyntaxTreeActionHonorExclusions(this AnalysisContext context, Action<SyntaxTreeAnalysisContext> action)
14+
{
15+
context.RegisterSyntaxTreeAction(
16+
c =>
17+
{
18+
if (c.IsGeneratedDocument())
19+
{
20+
return;
21+
}
22+
23+
// Honor the containing document item's ExcludeFromStylecop=True
24+
// MSBuild metadata, if analyzers have access to it.
25+
//// TODO: code here
26+
27+
action(c);
28+
});
29+
}
30+
31+
internal static void RegisterSyntaxNodeActionHonorExclusions<TLanguageKindEnum>(this AnalysisContext context, Action<SyntaxNodeAnalysisContext> action, params TLanguageKindEnum[] syntaxKinds) where TLanguageKindEnum : struct
32+
{
33+
context.RegisterSyntaxNodeAction(
34+
c =>
35+
{
36+
if (c.IsGenerated())
37+
{
38+
return;
39+
}
40+
41+
// Honor the containing document item's ExcludeFromStylecop=True
42+
// MSBuild metadata, if analyzers have access to it.
43+
//// TODO: code here
44+
45+
action(c);
46+
},
47+
syntaxKinds);
48+
}
49+
}
50+
}

AsyncUsageAnalyzers/AsyncUsageAnalyzers/AsyncUsageAnalyzers.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242
<AssemblyOriginatorKeyFile Condition="'$(KeyConfiguration)' != 'Final'">..\..\build\keys\AsyncUsageAnalyzers.dev.snk</AssemblyOriginatorKeyFile>
4343
</PropertyGroup>
4444
<ItemGroup>
45+
<Compile Include="AnalyzerConstants.cs" />
46+
<Compile Include="AnalyzerExtensions.cs" />
47+
<Compile Include="GeneratedCodeAnalysisExtensions.cs" />
48+
<Compile Include="Helpers\RenameHelper.cs" />
49+
<Compile Include="NoCodeFixAttribute.cs" />
4550
<Compile Include="Properties\AssemblyInfo.cs" />
4651
<Compile Include="Resources.Designer.cs">
4752
<AutoGen>True</AutoGen>
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// This file originally obtained from
2+
// https://raw.githubusercontent.com/code-cracker/code-cracker/08c1a01337964924eeed12be8b14c8ce8ec6b626/src/Common/CodeCracker.Common/Extensions/GeneratedCodeAnalysisExtensions.cs
3+
// It is subject to the Apache License 2.0
4+
// This file has been modified since obtaining it from its original source.
5+
6+
namespace AsyncUsageAnalyzers
7+
{
8+
using System.IO;
9+
using System.Linq;
10+
using System.Runtime.CompilerServices;
11+
using System.Text.RegularExpressions;
12+
using System.Threading;
13+
using Microsoft.CodeAnalysis;
14+
using Microsoft.CodeAnalysis.CSharp;
15+
using Microsoft.CodeAnalysis.CSharp.Syntax;
16+
using Microsoft.CodeAnalysis.Diagnostics;
17+
18+
internal static class GeneratedCodeAnalysisExtensions
19+
{
20+
/// <summary>
21+
/// A cache of the result of computing whether a document has an auto-generated header.
22+
/// </summary>
23+
/// <remarks>
24+
/// This allows many analyzers that run on every token in the file to avoid checking
25+
/// the same state in the document repeatedly.
26+
/// </remarks>
27+
private static readonly ConditionalWeakTable<SyntaxTree, StrongBox<bool?>> GeneratedHeaderPresentCheck
28+
= new ConditionalWeakTable<SyntaxTree, StrongBox<bool?>>();
29+
30+
/// <summary>
31+
/// Checks whether the given node or its containing document is auto generated by a tool.
32+
/// </summary>
33+
/// <remarks>
34+
/// <para>This method uses <see cref="IsGeneratedDocument(SyntaxTree, CancellationToken)"/> to determine which
35+
/// code is considered "generated".</para>
36+
/// </remarks>
37+
/// <param name="context">The analysis context for a <see cref="SyntaxNode"/>.</param>
38+
/// <returns>
39+
/// <para><see langword="true"/> if the <see cref="SyntaxNode"/> contained in <paramref name="context"/> is
40+
/// located in generated code; otherwise, <see langword="false"/>.</para>
41+
/// </returns>
42+
internal static bool IsGenerated(this SyntaxNodeAnalysisContext context)
43+
{
44+
return IsGeneratedDocument(context.Node.SyntaxTree, context.CancellationToken);
45+
}
46+
47+
/// <summary>
48+
/// Checks whether the given document is auto generated by a tool.
49+
/// </summary>
50+
/// <remarks>
51+
/// <para>This method uses <see cref="IsGeneratedDocument(SyntaxTree, CancellationToken)"/> to determine which
52+
/// code is considered "generated".</para>
53+
/// </remarks>
54+
/// <param name="context">The analysis context for a <see cref="SyntaxTree"/>.</param>
55+
/// <returns>
56+
/// <para><see langword="true"/> if the <see cref="SyntaxTree"/> contained in <paramref name="context"/> is
57+
/// located in generated code; otherwise, <see langword="false"/>.</para>
58+
/// </returns>
59+
internal static bool IsGeneratedDocument(this SyntaxTreeAnalysisContext context)
60+
{
61+
return IsGeneratedDocument(context.Tree, context.CancellationToken);
62+
}
63+
64+
/// <summary>
65+
/// Checks whether the given document is auto generated by a tool
66+
/// (based on filename or comment header).
67+
/// </summary>
68+
/// <remarks>
69+
/// <para>The exact conditions used to identify generated code are subject to change in future releases. The current algorithm uses the following checks.</para>
70+
/// <para>Code is considered generated if it meets any of the following conditions.</para>
71+
/// <list type="bullet">
72+
/// <item>The code is contained in a file which starts with a comment containing the text
73+
/// <c>&lt;auto-generated</c>.</item>
74+
/// <item>The code is contained in a file with a name matching certain patterns (case-insensitive):
75+
/// <list type="bullet">
76+
/// <item>service.cs</item>
77+
/// <item>TemporaryGeneratedFile_*.cs</item>
78+
/// <item>assemblyinfo.cs</item>
79+
/// <item>assemblyattributes.cs</item>
80+
/// <item>*.g.cs</item>
81+
/// <item>*.g.i.cs</item>
82+
/// <item>*.designer.cs</item>
83+
/// <item>*.generated.cs</item>
84+
/// <item>*.assemblyattributes.cs</item>
85+
/// </list>
86+
/// </item>
87+
/// </list>
88+
/// </remarks>
89+
/// <param name="tree">The syntax tree to examine.</param>
90+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
91+
/// <returns>
92+
/// <para><see langword="true"/> if <paramref name="tree"/> is located in generated code; otherwise,
93+
/// <see langword="false"/>. If <paramref name="tree"/> is <see langword="null"/>, this method returns
94+
/// <see langword="false"/>.</para>
95+
/// </returns>
96+
public static bool IsGeneratedDocument(this SyntaxTree tree, CancellationToken cancellationToken)
97+
{
98+
if (tree == null)
99+
{
100+
return false;
101+
}
102+
103+
StrongBox<bool?> cachedResult = GeneratedHeaderPresentCheck.GetOrCreateValue(tree);
104+
if (cachedResult.Value.HasValue)
105+
{
106+
return cachedResult.Value.Value;
107+
}
108+
109+
bool autoGenerated = IsGeneratedDocumentNoCache(tree, cancellationToken);
110+
111+
// Update the strongbox's value with our computed result.
112+
// This doesn't change the strongbox reference, and its presence in the
113+
// ConditionalWeakTable is already assured, so we're updating in-place.
114+
// In the event of a race condition with another thread that set the value,
115+
// we'll just be re-setting it to the same value.
116+
cachedResult.Value = autoGenerated;
117+
118+
return autoGenerated;
119+
}
120+
121+
private static bool IsGeneratedDocumentNoCache(SyntaxTree tree, CancellationToken cancellationToken)
122+
{
123+
return IsGeneratedFileName(tree.FilePath) || HasAutoGeneratedComment(tree, cancellationToken);
124+
}
125+
126+
/// <summary>
127+
/// Checks whether the given document has an auto-generated comment as its header.
128+
/// </summary>
129+
/// <param name="tree">The syntax tree to examine.</param>
130+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
131+
/// <returns>
132+
/// <para><see langword="true"/> if <paramref name="tree"/> starts with a comment containing the text
133+
/// <c>&lt;auto-generated</c>; otherwise, <see langword="false"/>.</para>
134+
/// </returns>
135+
private static bool HasAutoGeneratedComment(SyntaxTree tree, CancellationToken cancellationToken)
136+
{
137+
var root = tree.GetRoot(cancellationToken);
138+
139+
if (root == null)
140+
{
141+
return false;
142+
}
143+
144+
var firstToken = root.GetFirstToken();
145+
SyntaxTriviaList trivia;
146+
if (firstToken == default(SyntaxToken))
147+
{
148+
var token = ((CompilationUnitSyntax)root).EndOfFileToken;
149+
if (!token.HasLeadingTrivia)
150+
{
151+
return false;
152+
}
153+
154+
trivia = token.LeadingTrivia;
155+
}
156+
else
157+
{
158+
if (!firstToken.HasLeadingTrivia)
159+
{
160+
return false;
161+
}
162+
163+
trivia = firstToken.LeadingTrivia;
164+
}
165+
166+
var comments = trivia.Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) || t.IsKind(SyntaxKind.MultiLineCommentTrivia));
167+
return comments.Any(t => t.ToString().Contains("<auto-generated"));
168+
}
169+
170+
/// <summary>
171+
/// Checks whether the given document has a filename that indicates it is a generated file.
172+
/// </summary>
173+
/// <param name="filePath">The source file name, without any path.</param>
174+
/// <returns>
175+
/// <para><see langword="true"/> if <paramref name="filePath"/> is the name of a generated file; otherwise,
176+
/// <see langword="false"/>.</para>
177+
/// </returns>
178+
/// <seealso cref="IsGeneratedDocument(SyntaxTree, CancellationToken)"/>
179+
private static bool IsGeneratedFileName(string filePath)
180+
{
181+
return Regex.IsMatch(
182+
Path.GetFileName(filePath),
183+
@"(^service|^TemporaryGeneratedFile_.*|^assemblyinfo|^assemblyattributes|\.(g\.i|g|designer|generated|assemblyattributes))\.(cs|vb)$",
184+
RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
185+
}
186+
}
187+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace AsyncUsageAnalyzers.Helpers
2+
{
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CodeActions;
7+
using Microsoft.CodeAnalysis.Rename;
8+
9+
internal static class RenameHelper
10+
{
11+
public static async Task<Solution> RenameSymbolAsync(Document document, SyntaxNode root, SyntaxToken declarationToken, string newName, CancellationToken cancellationToken)
12+
{
13+
var annotatedRoot = root.ReplaceToken(declarationToken, declarationToken.WithAdditionalAnnotations(RenameAnnotation.Create()));
14+
var annotatedSolution = document.Project.Solution.WithDocumentSyntaxRoot(document.Id, annotatedRoot);
15+
var annotatedDocument = annotatedSolution.GetDocument(document.Id);
16+
17+
annotatedRoot = await annotatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
18+
var annotatedToken = annotatedRoot.FindToken(declarationToken.SpanStart);
19+
20+
var semanticModel = await annotatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
21+
var symbol = semanticModel?.GetDeclaredSymbol(annotatedToken.Parent, cancellationToken);
22+
23+
var newSolution = await Renamer.RenameSymbolAsync(annotatedSolution, symbol, newName, null, cancellationToken).ConfigureAwait(false);
24+
25+
// TODO: return annotatedSolution instead of newSolution if newSolution contains any new errors (for any project)
26+
return newSolution;
27+
}
28+
}
29+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace AsyncUsageAnalyzers
2+
{
3+
using System;
4+
using Microsoft.CodeAnalysis.Diagnostics;
5+
6+
/// <summary>
7+
/// This attribute is applied to <see cref="DiagnosticAnalyzer"/>'s if will not get a code fix.
8+
/// Reasons for this would be:
9+
/// <list type="bullet">
10+
/// <item>Visual Studio provides a built-in code fix.</item>
11+
/// <item>A code fix could not provide a useful solution.</item>
12+
/// </list>
13+
/// The reason should be provided.
14+
/// </summary>
15+
[AttributeUsage(System.AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
16+
public sealed class NoCodeFixAttribute : System.Attribute
17+
{
18+
private readonly string reason;
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="NoCodeFixAttribute"/> class.
22+
/// </summary>
23+
/// <param name="reason">The reason why the <see cref="DiagnosticAnalyzer"/> does not have a code fix.</param>
24+
public NoCodeFixAttribute(string reason)
25+
{
26+
this.reason = reason;
27+
}
28+
29+
/// <summary>
30+
/// Gets the reason why the <see cref="DiagnosticAnalyzer"/> does not have a code fix.
31+
/// </summary>
32+
/// <value>
33+
/// The reason why the <see cref="DiagnosticAnalyzer"/> does not have a code fix.
34+
/// </value>
35+
public string Reason
36+
{
37+
get { return this.reason; }
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)