Skip to content

Commit 9e0ff62

Browse files
committed
More progress
1 parent 2e0241c commit 9e0ff62

7 files changed

Lines changed: 463 additions & 0 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace StyleCop.Analyzers.Status.Generator
2+
{
3+
/// <summary>
4+
/// This enum is used to indicate whether or not a code fix is implemented
5+
/// </summary>
6+
public enum CodeFixStatus
7+
{
8+
/// <summary>
9+
/// This value indicates, that a code fix is implemented
10+
/// </summary>
11+
Implemented,
12+
13+
/// <summary>
14+
/// This value indicates, that a code fix is not implemented and
15+
/// will not be implemented because it either can't be implemented
16+
/// or a code fix would not be able to fix it rationally.
17+
/// </summary>
18+
NotImplemented,
19+
20+
/// <summary>
21+
/// This value indicates, that a code fix is not implemented because
22+
/// no one implemented it yet, or it is not yet decided if a code fix
23+
/// is going to be implemented in the future.
24+
/// </summary>
25+
NotYetImplemented
26+
}
27+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace StyleCop.Analyzers.Status.Generator
2+
{
3+
using System;
4+
5+
/// <summary>
6+
/// An exception that gets thrown if the compilation failed.
7+
/// </summary>
8+
[Serializable]
9+
public class CompilationFailedException : System.Exception
10+
{
11+
/// <summary>
12+
/// Initializes a new instance of the <see cref="CompilationFailedException"/> class.
13+
/// </summary>
14+
public CompilationFailedException()
15+
{
16+
17+
}
18+
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="CompilationFailedException"/> class.
21+
/// </summary>
22+
/// <param name="message">The message that should be reported</param>
23+
public CompilationFailedException(string message) : base(message)
24+
{
25+
26+
}
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="CompilationFailedException"/> class.
30+
/// </summary>
31+
/// <param name="message">The message that should be reported</param>
32+
/// <param name="inner">The exception that caused this exception to be thrown</param>
33+
public CompilationFailedException(string message, System.Exception inner) : base(message, inner)
34+
{
35+
36+
}
37+
}
38+
}

StyleCop.Analyzers.Status.Generator/Program.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
namespace StyleCop.Analyzers.Status.Generator
22
{
3+
using System;
4+
using Newtonsoft.Json;
5+
36
/// <summary>
47
/// The starting point of this application.
58
/// </summary>
@@ -11,6 +14,15 @@ internal class Program
1114
/// <param name="args">The command line parameters.</param>
1215
internal static void Main(string[] args)
1316
{
17+
if (args.Length < 1)
18+
{
19+
Console.WriteLine("Path to sln file required.");
20+
return;
21+
}
22+
23+
SolutionReader reader = SolutionReader.CreateAsync(args[0]).Result;
24+
25+
Console.WriteLine(JsonConvert.SerializeObject(reader.GetDiagnosticsAsync().Result));
1426
}
1527
}
1628
}
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
namespace StyleCop.Analyzers.Status.Generator
2+
{
3+
using System;
4+
using System.Collections.Immutable;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Linq;
8+
using System.Reflection;
9+
using System.Text.RegularExpressions;
10+
using System.Threading.Tasks;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CodeFixes;
13+
using Microsoft.CodeAnalysis.MSBuild;
14+
using Microsoft.CodeAnalysis.Diagnostics;
15+
using Microsoft.CodeAnalysis.CSharp;
16+
using Microsoft.CodeAnalysis.CSharp.Syntax;
17+
18+
/// <summary>
19+
/// A class that is used to parse the StyleCop.Analyzers solution to get an overview
20+
/// about the implemented diagnostics.
21+
/// </summary>
22+
public class SolutionReader
23+
{
24+
private static Regex diagnosticPathRegex = new Regex(@"(?<type>[A-Za-z]+)Rules\\(?<id>SA[0-9]{4})(?<name>[A-Za-z]+)\.cs$");
25+
private INamedTypeSymbol diagnosticAnalyzerTypeSymbol;
26+
private INamedTypeSymbol noCodeFixAttributeTypeSymbol;
27+
28+
private Solution solution;
29+
private Project project;
30+
private MSBuildWorkspace workspace;
31+
private Assembly assembly;
32+
private Compilation compilation;
33+
private ITypeSymbol booleanType;
34+
35+
private SolutionReader()
36+
{
37+
38+
}
39+
40+
private string SlnPath { get; set; }
41+
42+
private ImmutableArray<CodeFixProvider> CodeFixProviders { get; set; }
43+
44+
/// <summary>
45+
/// Creates a new instance of the <see cref="SolutionReader"/> class.
46+
/// </summary>
47+
/// <param name="pathToSln">The path to the StyleCop.Analayzers sln</param>
48+
/// <param name="projectName">The project name of the main project</param>
49+
/// <returns>A <see cref="Task{SolutionReader}"/> representing the asynchronous operation</returns>
50+
public static async Task<SolutionReader> CreateAsync(string pathToSln, string projectName = "StyleCop.Analyzers")
51+
{
52+
SolutionReader reader = new SolutionReader();
53+
54+
reader.SlnPath = pathToSln;
55+
reader.workspace = MSBuildWorkspace.Create(properties: new Dictionary<string, string> { { "Configuration", "Release" } });
56+
57+
await reader.InitializeAsync();
58+
59+
return reader;
60+
}
61+
62+
private async Task InitializeAsync()
63+
{
64+
this.solution = await this.workspace.OpenSolutionAsync(this.SlnPath);
65+
this.project = this.solution.Projects.Single(x => x.Name == "StyleCop.Analyzers");
66+
this.compilation = await this.project.GetCompilationAsync();
67+
this.compilation = this.compilation.WithOptions(this.compilation.Options.WithOutputKind(OutputKind.DynamicallyLinkedLibrary));
68+
this.booleanType = this.compilation.GetSpecialType(SpecialType.System_Boolean);
69+
this.Compile();
70+
71+
this.noCodeFixAttributeTypeSymbol = this.compilation.GetTypeByMetadataName("StyleCop.Analyzers.NoCodeFixAttribute");
72+
this.diagnosticAnalyzerTypeSymbol = this.compilation.GetTypeByMetadataName(typeof(DiagnosticAnalyzer).FullName);
73+
74+
this.InitializeCodeFixTypes();
75+
}
76+
77+
private void InitializeCodeFixTypes()
78+
{
79+
var codeFixTypes = this.assembly.ExportedTypes.Where(x => x.FullName.EndsWith("CodeFixProvider"));
80+
this.CodeFixProviders = ImmutableArray.Create(
81+
codeFixTypes
82+
.Select(t => Activator.CreateInstance(t, true))
83+
.OfType<CodeFixProvider>()
84+
.Where(x => x != null)
85+
.ToArray());
86+
}
87+
88+
private void Compile()
89+
{
90+
MemoryStream memStream = new MemoryStream();
91+
92+
var emitResult = this.compilation.Emit(memStream);
93+
94+
if (!emitResult.Success)
95+
{
96+
throw new CompilationFailedException();
97+
}
98+
99+
this.assembly = Assembly.Load(memStream.ToArray());
100+
}
101+
102+
/// <summary>
103+
/// Analyzes the project and returns information about the diagnostics in it.
104+
/// </summary>
105+
/// <returns>A <see cref="Task{ImmutableList{StyleCopDiagnostic}}"/> representing the asynchronous operation</returns>
106+
public async Task<ImmutableList<StyleCopDiagnostic>> GetDiagnosticsAsync()
107+
{
108+
var diagnostics = ImmutableList.CreateBuilder<StyleCopDiagnostic>();
109+
110+
var syntaxTrees = this.compilation.SyntaxTrees;
111+
112+
foreach (var syntaxTree in syntaxTrees)
113+
{
114+
var match = diagnosticPathRegex.Match(syntaxTree.FilePath);
115+
if (!match.Success)
116+
{
117+
continue;
118+
}
119+
120+
string id = match.Groups["id"].Value;
121+
string shortName = match.Groups["name"].Value;
122+
string type = match.Groups["type"].Value;
123+
CodeFixStatus codeFixStatus;
124+
string noCodeFixReason = null;
125+
bool hasImplementation = true;
126+
127+
// Check if this syntax tree represents a diagnostic
128+
SyntaxNode syntaxRoot = await syntaxTree.GetRootAsync();
129+
SemanticModel semanticModel = this.compilation.GetSemanticModel(syntaxTree);
130+
SyntaxNode classSyntaxNode = syntaxRoot.DescendantNodes().First(x => x.IsKind(SyntaxKind.ClassDeclaration));
131+
132+
INamedTypeSymbol classSymbol = semanticModel.GetDeclaredSymbol(classSyntaxNode) as INamedTypeSymbol;
133+
134+
if (classSymbol == null || !this.InheritsFrom(classSymbol, this.diagnosticAnalyzerTypeSymbol))
135+
{
136+
continue;
137+
}
138+
139+
foreach (var trivia in syntaxRoot.DescendantTrivia())
140+
{
141+
if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia))
142+
{
143+
if (trivia.ToFullString().Contains("TODO: Implement analysis"))
144+
{
145+
hasImplementation = false;
146+
}
147+
}
148+
}
149+
150+
codeFixStatus = this.HasCodeFix(id, classSymbol, out noCodeFixReason);
151+
152+
DiagnosticDescriptor descriptorInfo = this.GetDescriptor(classSymbol);
153+
154+
string status = this.GetStatus(classSymbol, syntaxRoot, semanticModel);
155+
156+
var diagnostic = new StyleCopDiagnostic
157+
{
158+
Id = descriptorInfo.Id,
159+
Category = descriptorInfo.Category,
160+
HasImplementation = hasImplementation,
161+
Status = status,
162+
Name = shortName,
163+
Title = descriptorInfo.Title.ToString(),
164+
HelpLink = descriptorInfo.HelpLinkUri,
165+
CodeFixStatus = codeFixStatus,
166+
NoCodeFixReason = noCodeFixReason
167+
};
168+
diagnostics.Add(diagnostic);
169+
}
170+
171+
return diagnostics.ToImmutable();
172+
}
173+
174+
private string GetStatus(INamedTypeSymbol classSymbol, SyntaxNode root, SemanticModel model)
175+
{
176+
// Some analyzers use multiple descriptors. We analyze the first one and hope that
177+
// thats enough.
178+
var members = classSymbol.GetMembers().FirstOrDefault(x => x.Name.Contains("Descriptor"));
179+
180+
VariableDeclaratorSyntax node = root.FindNode(members.Locations.FirstOrDefault().SourceSpan) as VariableDeclaratorSyntax;
181+
182+
ObjectCreationExpressionSyntax initializer = node.Initializer.Value as ObjectCreationExpressionSyntax;
183+
184+
// We use the fact that the only parameter that returns a boolean is the one we are interested in
185+
var enabledByDefaultParameter = from argument in initializer.ArgumentList.Arguments
186+
where model.GetTypeInfo(argument.Expression).Type == this.booleanType
187+
select argument.Expression;
188+
var parameter = enabledByDefaultParameter.FirstOrDefault();
189+
string parameterString = parameter.ToString();
190+
var analyzerConstantLength = "AnalyzerConstants.".Length;
191+
192+
if (parameterString.Length < analyzerConstantLength)
193+
{
194+
return parameterString;
195+
}
196+
197+
return parameter.ToString().Substring(analyzerConstantLength);
198+
}
199+
200+
private DiagnosticDescriptor GetDescriptor(INamedTypeSymbol classSymbol)
201+
{
202+
var analyzer = (DiagnosticAnalyzer)Activator.CreateInstance(this.assembly.GetType(classSymbol.ToString()));
203+
204+
// This currently only supports one diagnostic for each analyzer.
205+
return analyzer.SupportedDiagnostics.FirstOrDefault();
206+
}
207+
208+
private CodeFixStatus HasCodeFix(string diagnosticId, INamedTypeSymbol classSymbol, out string noCodeFixReason)
209+
{
210+
CodeFixStatus status;
211+
212+
noCodeFixReason = null;
213+
214+
var noCodeFixAttribute = classSymbol.GetAttributes().SingleOrDefault(x => x.AttributeClass == this.noCodeFixAttributeTypeSymbol);
215+
216+
bool hasCodeFix = noCodeFixAttribute == null;
217+
if (!hasCodeFix)
218+
{
219+
status = CodeFixStatus.NotImplemented;
220+
if (noCodeFixAttribute.ConstructorArguments.Length > 0)
221+
{
222+
noCodeFixReason = noCodeFixAttribute.ConstructorArguments[0].Value as string;
223+
}
224+
}
225+
else
226+
{
227+
// Check if the code fix actually exists
228+
hasCodeFix = this.CodeFixProviders.Any(x => x.FixableDiagnosticIds.Contains(diagnosticId));
229+
230+
status = hasCodeFix ? CodeFixStatus.Implemented : CodeFixStatus.NotYetImplemented;
231+
}
232+
233+
return status;
234+
}
235+
236+
private bool InheritsFrom(INamedTypeSymbol declaration, INamedTypeSymbol possibleBaseType)
237+
{
238+
while (true)
239+
{
240+
if (declaration == null)
241+
{
242+
return false;
243+
}
244+
245+
if (declaration == possibleBaseType)
246+
{
247+
return true;
248+
}
249+
250+
declaration = declaration.BaseType;
251+
}
252+
}
253+
}
254+
}

StyleCop.Analyzers.Status.Generator/StyleCop.Analyzers.Status.Generator.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
<HintPath>..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0-rc2\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll</HintPath>
6565
<Private>True</Private>
6666
</Reference>
67+
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
68+
<HintPath>..\packages\Newtonsoft.Json.7.0.1-beta3\lib\net45\Newtonsoft.Json.dll</HintPath>
69+
<Private>True</Private>
70+
</Reference>
6771
<Reference Include="System" />
6872
<Reference Include="System.Collections.Immutable, Version=1.1.33.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
6973
<HintPath>..\packages\System.Collections.Immutable.1.1.33-beta\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
@@ -102,8 +106,12 @@
102106
<Reference Include="System.Xml" />
103107
</ItemGroup>
104108
<ItemGroup>
109+
<Compile Include="CodeFixStatus.cs" />
110+
<Compile Include="CompilationFailedException.cs" />
105111
<Compile Include="Program.cs" />
106112
<Compile Include="Properties\AssemblyInfo.cs" />
113+
<Compile Include="SolutionReader.cs" />
114+
<Compile Include="StyleCopDiagnostic.cs" />
107115
</ItemGroup>
108116
<ItemGroup>
109117
<None Include="App.config" />

0 commit comments

Comments
 (0)