Skip to content

Commit ce51697

Browse files
committed
Workaround performance regressions in Visual Studio 2015 Update 1
Fixes #1974
1 parent efd47fd commit ce51697

8 files changed

Lines changed: 131 additions & 38 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/FixAllContextHelper.cs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,29 @@
33

44
namespace StyleCop.Analyzers.Helpers
55
{
6+
using System;
67
using System.Collections.Concurrent;
78
using System.Collections.Immutable;
89
using System.Linq;
10+
using System.Reflection;
911
using System.Threading;
1012
using System.Threading.Tasks;
1113
using Microsoft.CodeAnalysis;
1214
using Microsoft.CodeAnalysis.CodeFixes;
15+
using Microsoft.CodeAnalysis.Diagnostics;
1316

1417
internal static class FixAllContextHelper
1518
{
19+
private static readonly ImmutableDictionary<string, ImmutableArray<Type>> DiagnosticAnalyzers = GetAllAnalyzers();
20+
1621
public static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(FixAllContext fixAllContext)
1722
{
1823
var allDiagnostics = ImmutableArray<Diagnostic>.Empty;
1924
var projectsToFix = ImmutableArray<Project>.Empty;
2025

2126
var document = fixAllContext.Document;
2227
var project = fixAllContext.Project;
28+
var analyzers = GetDiagnosticAnalyzersForContext(fixAllContext);
2329

2430
switch (fixAllContext.Scope)
2531
{
@@ -34,7 +40,10 @@ public static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic
3440

3541
case FixAllScope.Project:
3642
projectsToFix = ImmutableArray.Create(project);
37-
allDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false);
43+
var compilation = await project.GetCompilationAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
44+
var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers, project.AnalyzerOptions, fixAllContext.CancellationToken);
45+
allDiagnostics = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false);
46+
allDiagnostics = allDiagnostics.Where(x => fixAllContext.DiagnosticIds.Contains(x.Id)).ToImmutableArray();
3847
break;
3948

4049
case FixAllScope.Solution:
@@ -51,13 +60,15 @@ public static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic
5160
tasks[i] = Task.Run(
5261
async () =>
5362
{
54-
var projectDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(projectToFix).ConfigureAwait(false);
63+
var projectCompilation = await projectToFix.GetCompilationAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
64+
var projectCompilationWithAnalyzers = projectCompilation.WithAnalyzers(analyzers, projectToFix.AnalyzerOptions, fixAllContext.CancellationToken);
65+
var projectDiagnostics = await projectCompilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false);
5566
diagnostics.TryAdd(projectToFix.Id, projectDiagnostics);
5667
}, fixAllContext.CancellationToken);
5768
}
5869

5970
await Task.WhenAll(tasks).ConfigureAwait(false);
60-
allDiagnostics = allDiagnostics.AddRange(diagnostics.SelectMany(i => i.Value));
71+
allDiagnostics = allDiagnostics.AddRange(diagnostics.SelectMany(i => i.Value.Where(x => fixAllContext.DiagnosticIds.Contains(x.Id))));
6172
break;
6273
}
6374

@@ -102,6 +113,50 @@ public static async Task<ImmutableDictionary<Project, ImmutableArray<Diagnostic>
102113
return ImmutableDictionary<Project, ImmutableArray<Diagnostic>>.Empty;
103114
}
104115

116+
private static ImmutableDictionary<string, ImmutableArray<Type>> GetAllAnalyzers()
117+
{
118+
Assembly assembly = typeof(NoCodeFixAttribute).GetTypeInfo().Assembly;
119+
120+
var diagnosticAnalyzerType = typeof(DiagnosticAnalyzer);
121+
122+
var analyzers = ImmutableDictionary.CreateBuilder<string, ImmutableArray<Type>>();
123+
124+
foreach (var type in assembly.DefinedTypes)
125+
{
126+
if (type.IsSubclassOf(diagnosticAnalyzerType) && !type.IsAbstract)
127+
{
128+
Type analyzerType = type.AsType();
129+
DiagnosticAnalyzer analyzer = (DiagnosticAnalyzer)Activator.CreateInstance(analyzerType);
130+
foreach (var descriptor in analyzer.SupportedDiagnostics)
131+
{
132+
ImmutableArray<Type> types;
133+
if (analyzers.TryGetValue(descriptor.Id, out types))
134+
{
135+
types = types.Add(analyzerType);
136+
}
137+
else
138+
{
139+
types = ImmutableArray.Create(analyzerType);
140+
}
141+
142+
analyzers[descriptor.Id] = types;
143+
}
144+
}
145+
}
146+
147+
return analyzers.ToImmutable();
148+
}
149+
150+
private static ImmutableArray<DiagnosticAnalyzer> GetDiagnosticAnalyzersForContext(FixAllContext fixAllContext)
151+
{
152+
return DiagnosticAnalyzers
153+
.Where(x => fixAllContext.DiagnosticIds.Contains(x.Key))
154+
.SelectMany(x => x.Value)
155+
.Distinct()
156+
.Select(type => (DiagnosticAnalyzer)Activator.CreateInstance(type))
157+
.ToImmutableArray();
158+
}
159+
105160
private static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(
106161
ImmutableArray<Diagnostic> diagnostics,
107162
ImmutableArray<Project> projects,

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1604UnitTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ public void MethodName()
722722
}
723723

724724
/// <inheritdoc/>
725-
protected override Project CreateProject(string[] sources, string language = "C#", string[] filenames = null)
725+
protected override Project ApplyCompilationOptions(Project project)
726726
{
727727
var resolver = new TestXmlReferenceResolver();
728728
string contentWithSummary = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
@@ -759,7 +759,7 @@ Sample method.
759759
";
760760
resolver.XmlReferences.Add("ClassWithoutSummary.xml", contentWithoutSummary);
761761

762-
Project project = base.CreateProject(sources, language, filenames);
762+
project = base.ApplyCompilationOptions(project);
763763
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
764764
return project;
765765
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1606UnitTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ public void MethodName()
746746
}
747747

748748
/// <inheritdoc/>
749-
protected override Project CreateProject(string[] sources, string language = "C#", string[] filenames = null)
749+
protected override Project ApplyCompilationOptions(Project project)
750750
{
751751
var resolver = new TestXmlReferenceResolver();
752752
string contentWithSummary = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
@@ -785,7 +785,7 @@ Sample method.
785785
";
786786
resolver.XmlReferences.Add("ClassWithEmptySummary.xml", contentWithEmptySummary);
787787

788-
Project project = base.CreateProject(sources, language, filenames);
788+
project = base.ApplyCompilationOptions(project);
789789
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
790790
return project;
791791
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1615UnitTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ public int MethodName()
331331
}
332332

333333
/// <inheritdoc/>
334-
protected override Project CreateProject(string[] sources, string language = "C#", string[] filenames = null)
334+
protected override Project ApplyCompilationOptions(Project project)
335335
{
336336
var resolver = new TestXmlReferenceResolver();
337337
string contentWithReturns = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
@@ -368,7 +368,7 @@ Sample method.
368368
";
369369
resolver.XmlReferences.Add("MethodWithoutReturns.xml", contentWithoutReturns);
370370

371-
Project project = base.CreateProject(sources, language, filenames);
371+
project = base.ApplyCompilationOptions(project);
372372
project = project.WithCompilationOptions(project.CompilationOptions.WithXmlReferenceResolver(resolver));
373373
return project;
374374
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/DiagnosticVerifier.Helper.cs

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -47,29 +47,11 @@ protected static async Task<ImmutableArray<Diagnostic>> GetSortedDiagnosticsFrom
4747
projects.Add(document.Project);
4848
}
4949

50-
var supportedDiagnosticsSpecificOptions = new Dictionary<string, ReportDiagnostic>();
51-
foreach (var analyzer in analyzers)
52-
{
53-
foreach (var diagnostic in analyzer.SupportedDiagnostics)
54-
{
55-
// make sure the analyzers we are testing are enabled
56-
supportedDiagnosticsSpecificOptions[diagnostic.Id] = ReportDiagnostic.Default;
57-
}
58-
}
59-
60-
// Report exceptions during the analysis process as errors
61-
supportedDiagnosticsSpecificOptions.Add("AD0001", ReportDiagnostic.Error);
62-
6350
var diagnostics = ImmutableArray.CreateBuilder<Diagnostic>();
6451
foreach (var project in projects)
6552
{
66-
// update the project compilation options
67-
var modifiedSpecificDiagnosticOptions = supportedDiagnosticsSpecificOptions.ToImmutableDictionary().SetItems(project.CompilationOptions.SpecificDiagnosticOptions);
68-
var modifiedCompilationOptions = project.CompilationOptions.WithSpecificDiagnosticOptions(modifiedSpecificDiagnosticOptions);
69-
var processedProject = project.WithCompilationOptions(modifiedCompilationOptions);
70-
71-
var compilation = await processedProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
72-
var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers, processedProject.AnalyzerOptions, cancellationToken);
53+
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
54+
var compilationWithAnalyzers = compilation.WithAnalyzers(analyzers, project.AnalyzerOptions, cancellationToken);
7355
var compilerDiagnostics = compilation.GetDiagnostics(cancellationToken);
7456
var compilerErrors = compilerDiagnostics.Where(i => i.Severity == DiagnosticSeverity.Error);
7557
var diags = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().ConfigureAwait(false);
@@ -130,10 +112,6 @@ protected virtual Solution CreateSolution(ProjectId projectId, string language)
130112
{
131113
var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true);
132114

133-
var additionalDiagnosticOptions = this.GetDisabledDiagnostics().Select(id => new KeyValuePair<string, ReportDiagnostic>(id, ReportDiagnostic.Suppress));
134-
var newSpecificOptions = compilationOptions.SpecificDiagnosticOptions.AddRange(additionalDiagnosticOptions);
135-
compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(newSpecificOptions);
136-
137115
Solution solution = new AdhocWorkspace()
138116
.CurrentSolution
139117
.AddProject(projectId, TestProjectName, TestProjectName, language)
@@ -195,13 +173,32 @@ protected DiagnosticResult CSharpDiagnostic(DiagnosticDescriptor descriptor)
195173
/// <summary>
196174
/// Create a project using the input strings as sources.
197175
/// </summary>
176+
/// <remarks>
177+
/// <para>This method first creates a <see cref="Project"/> by calling <see cref="CreateProjectImpl"/>, and then
178+
/// applies compilation options to the project by calling <see cref="ApplyCompilationOptions"/>.</para>
179+
/// </remarks>
198180
/// <param name="sources">Classes in the form of strings.</param>
199181
/// <param name="language">The language the source classes are in. Values may be taken from the
200182
/// <see cref="LanguageNames"/> class.</param>
201183
/// <param name="filenames">The filenames or null if the default filename should be used</param>
202184
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
203185
/// strings.</returns>
204-
protected virtual Project CreateProject(string[] sources, string language = LanguageNames.CSharp, string[] filenames = null)
186+
protected Project CreateProject(string[] sources, string language = LanguageNames.CSharp, string[] filenames = null)
187+
{
188+
Project project = this.CreateProjectImpl(sources, language, filenames);
189+
return this.ApplyCompilationOptions(project);
190+
}
191+
192+
/// <summary>
193+
/// Create a project using the input strings as sources.
194+
/// </summary>
195+
/// <param name="sources">Classes in the form of strings.</param>
196+
/// <param name="language">The language the source classes are in. Values may be taken from the
197+
/// <see cref="LanguageNames"/> class.</param>
198+
/// <param name="filenames">The filenames or null if the default filename should be used</param>
199+
/// <returns>A <see cref="Project"/> created out of the <see cref="Document"/>s created from the source
200+
/// strings.</returns>
201+
protected virtual Project CreateProjectImpl(string[] sources, string language, string[] filenames)
205202
{
206203
string fileNamePrefix = DefaultFilePathPrefix;
207204
string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt;
@@ -222,6 +219,47 @@ protected virtual Project CreateProject(string[] sources, string language = Lang
222219
return solution.GetProject(projectId);
223220
}
224221

222+
/// <summary>
223+
/// Applies compilation options to a project.
224+
/// </summary>
225+
/// <remarks>
226+
/// <para>The default implementation configures the project by enabling all supported diagnostics of analyzers
227+
/// included in <see cref="GetCSharpDiagnosticAnalyzers"/> as well as <c>AD0001</c>. After configuring these
228+
/// diagnostics, any diagnostic IDs indicated in <see cref="GetDisabledDiagnostics"/> are explictly supressed
229+
/// using <see cref="ReportDiagnostic.Suppress"/>.</para>
230+
/// </remarks>
231+
/// <param name="project">The project.</param>
232+
/// <returns>The modified project.</returns>
233+
protected virtual Project ApplyCompilationOptions(Project project)
234+
{
235+
var analyzers = this.GetCSharpDiagnosticAnalyzers();
236+
237+
var supportedDiagnosticsSpecificOptions = new Dictionary<string, ReportDiagnostic>();
238+
foreach (var analyzer in analyzers)
239+
{
240+
foreach (var diagnostic in analyzer.SupportedDiagnostics)
241+
{
242+
// make sure the analyzers we are testing are enabled
243+
supportedDiagnosticsSpecificOptions[diagnostic.Id] = ReportDiagnostic.Default;
244+
}
245+
}
246+
247+
// Report exceptions during the analysis process as errors
248+
supportedDiagnosticsSpecificOptions.Add("AD0001", ReportDiagnostic.Error);
249+
250+
foreach (var id in this.GetDisabledDiagnostics())
251+
{
252+
supportedDiagnosticsSpecificOptions[id] = ReportDiagnostic.Suppress;
253+
}
254+
255+
// update the project compilation options
256+
var modifiedSpecificDiagnosticOptions = supportedDiagnosticsSpecificOptions.ToImmutableDictionary().SetItems(project.CompilationOptions.SpecificDiagnosticOptions);
257+
var modifiedCompilationOptions = project.CompilationOptions.WithSpecificDiagnosticOptions(modifiedSpecificDiagnosticOptions);
258+
259+
Solution solution = project.Solution.WithProjectCompilationOptions(project.Id, modifiedCompilationOptions);
260+
return solution.GetProject(project.Id);
261+
}
262+
225263
/// <summary>
226264
/// Sort <see cref="Diagnostic"/>s by location in source document.
227265
/// </summary>

StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1412UnitTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ protected override CodeFixProvider GetCSharpCodeFixProvider()
131131
return new SA1412CodeFixProvider();
132132
}
133133

134-
protected override Project CreateProject(string[] sources, string language = "C#", string[] filenames = null)
134+
protected override Project CreateProjectImpl(string[] sources, string language, string[] filenames)
135135
{
136136
string fileNamePrefix = "Test";
137137
string fileExt = "cs";

StyleCop.Analyzers/StyleCop.Analyzers.Test/Verifiers/CodeFixVerifier.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ private static async Task<Document> GetFixAllAnalyzerAsync(FixAllScope scope, Im
289289

290290
FixAllContext.DiagnosticProvider fixAllDiagnosticProvider = TestDiagnosticProvider.Create(analyzerDiagnostics);
291291

292-
FixAllContext fixAllContext = new FixAllContext(document, codeFixProvider, scope, equivalenceKey, codeFixProvider.FixableDiagnosticIds, fixAllDiagnosticProvider, cancellationToken);
292+
FixAllContext fixAllContext = new FixAllContext(document, codeFixProvider, scope, equivalenceKey, analyzers.SelectMany(x => x.SupportedDiagnostics).Select(x => x.Id), fixAllDiagnosticProvider, cancellationToken);
293293

294294
CodeAction action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false);
295295
if (action == null)

StyleCop.Analyzers/StyleCopTester/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ private static ImmutableArray<DiagnosticAnalyzer> GetAllAnalyzers()
328328

329329
var diagnosticAnalyzerType = typeof(DiagnosticAnalyzer);
330330

331-
List<DiagnosticAnalyzer> analyzers = new List<DiagnosticAnalyzer>();
331+
var analyzers = ImmutableArray.CreateBuilder<DiagnosticAnalyzer>();
332332

333333
foreach (var type in assembly.GetTypes())
334334
{
@@ -338,7 +338,7 @@ private static ImmutableArray<DiagnosticAnalyzer> GetAllAnalyzers()
338338
}
339339
}
340340

341-
return analyzers.ToImmutableArray();
341+
return analyzers.ToImmutable();
342342
}
343343

344344
private static ImmutableDictionary<string, ImmutableList<CodeFixProvider>> GetAllCodeFixers()

0 commit comments

Comments
 (0)