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+ }
0 commit comments