/
ParallelizableUsageAnalyzer.cs
166 lines (144 loc) · 7.78 KB
/
ParallelizableUsageAnalyzer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using NUnit.Analyzers.Constants;
using NUnit.Analyzers.Extensions;
namespace NUnit.Analyzers.ParallelizableUsage
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ParallelizableUsageAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor scopeSelfNoEffectOnAssemblyUsage = DiagnosticDescriptorCreator.Create(
id: AnalyzerIdentifiers.ParallelScopeSelfNoEffectOnAssemblyUsage,
title: ParallelizableUsageAnalyzerConstants.ParallelScopeSelfNoEffectOnAssemblyTitle,
messageFormat: ParallelizableUsageAnalyzerConstants.ParallelScopeSelfNoEffectOnAssemblyMessage,
category: Categories.Structure,
defaultSeverity: DiagnosticSeverity.Warning,
description: ParallelizableUsageAnalyzerConstants.ParallelScopeSelfNoEffectOnAssemblyDescription);
private static readonly DiagnosticDescriptor scopeChildrenOnNonParameterizedTest = DiagnosticDescriptorCreator.Create(
id: AnalyzerIdentifiers.ParallelScopeChildrenOnNonParameterizedTestMethodUsage,
title: ParallelizableUsageAnalyzerConstants.ParallelScopeChildrenOnNonParameterizedTestMethodTitle,
messageFormat: ParallelizableUsageAnalyzerConstants.ParallelScopeChildrenOnNonParameterizedTestMethodMessage,
category: Categories.Structure,
defaultSeverity: DiagnosticSeverity.Error,
description: ParallelizableUsageAnalyzerConstants.ParallelScopeChildrenOnNonParameterizedTestMethodDescription);
private static readonly DiagnosticDescriptor scopeFixturesOnTest = DiagnosticDescriptorCreator.Create(
id: AnalyzerIdentifiers.ParallelScopeFixturesOnTestMethodUsage,
title: ParallelizableUsageAnalyzerConstants.ParallelScopeFixturesOnTestMethodTitle,
messageFormat: ParallelizableUsageAnalyzerConstants.ParallelScopeFixturesOnTestMethodMessage,
category: Categories.Structure,
defaultSeverity: DiagnosticSeverity.Error,
description: ParallelizableUsageAnalyzerConstants.ParallelScopeFixturesOnTestMethodDescription);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
scopeSelfNoEffectOnAssemblyUsage,
scopeChildrenOnNonParameterizedTest,
scopeFixturesOnTest);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(AnalyzeCompilationStart);
context.RegisterCompilationAction(AnalyzeCompilation);
}
private static void AnalyzeCompilationStart(CompilationStartAnalysisContext context)
{
var parallelizableAttribute = context.Compilation.GetTypeByMetadataName(NUnitFrameworkConstants.FullNameOfTypeParallelizableAttribute);
if (parallelizableAttribute is null)
{
return;
}
context.RegisterSymbolAction(symbolContext => AnalyzeMethod(symbolContext, parallelizableAttribute), SymbolKind.Method);
}
private static void AnalyzeCompilation(CompilationAnalysisContext context)
{
var parallelizableAttribute = context.Compilation.GetTypeByMetadataName(NUnitFrameworkConstants.FullNameOfTypeParallelizableAttribute);
if (parallelizableAttribute is null)
{
return;
}
if (!TryGetAttributeEnumValue(context.Compilation, context.Compilation.Assembly, parallelizableAttribute,
out int enumValue,
out var attributeData))
{
return;
}
if (HasExactFlag(enumValue, ParallelizableUsageAnalyzerConstants.ParallelScope.Self))
{
// Specifying ParallelScope.Self on an assembly level attribute has no effect
context.ReportDiagnostic(Diagnostic.Create(
scopeSelfNoEffectOnAssemblyUsage,
attributeData.ApplicationSyntaxReference.GetLocation()));
}
}
private static void AnalyzeMethod(SymbolAnalysisContext context, INamedTypeSymbol parallelizableAttribute)
{
if (!TryGetAttributeEnumValue(context.Compilation, context.Symbol, parallelizableAttribute,
out int enumValue,
out var attributeData))
{
return;
}
var methodSymbol = (IMethodSymbol)context.Symbol;
if (HasFlag(enumValue, ParallelizableUsageAnalyzerConstants.ParallelScope.Children))
{
// One may not specify ParallelScope.Children on a non-parameterized test method
if (IsNonParameterizedTestMethod(context, methodSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(scopeChildrenOnNonParameterizedTest,
attributeData.ApplicationSyntaxReference.GetLocation()));
}
}
else if (HasFlag(enumValue, ParallelizableUsageAnalyzerConstants.ParallelScope.Fixtures))
{
// One may not specify ParallelScope.Fixtures on a test method
context.ReportDiagnostic(Diagnostic.Create(scopeFixturesOnTest,
attributeData.ApplicationSyntaxReference.GetLocation()));
}
}
private static bool TryGetAttributeEnumValue(Compilation compilation, ISymbol symbol, INamedTypeSymbol parallelizableAttributeType,
out int enumValue,
[NotNullWhen(true)] out AttributeData? attributeData)
{
enumValue = 0;
attributeData = symbol.GetAttributes()
.FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, parallelizableAttributeType));
if (attributeData?.ApplicationSyntaxReference is null)
return false;
var optionalEnumValue = GetOptionalEnumValue(attributeData);
if (optionalEnumValue is null)
return false;
enumValue = optionalEnumValue.Value;
return true;
}
private static int? GetOptionalEnumValue(AttributeData attributeData)
{
var attributePositionalArguments = attributeData.ConstructorArguments;
var noExplicitEnumArgument = attributePositionalArguments.Length == 0;
if (noExplicitEnumArgument)
{
return ParallelizableUsageAnalyzerConstants.ParallelScope.Self;
}
else
{
var arg = attributePositionalArguments[0];
return arg.Value as int?;
}
}
private static bool IsNonParameterizedTestMethod(SymbolAnalysisContext context, IMethodSymbol methodSymbol)
{
// The method is only a parametric method if (see DefaultTestCaseBuilder.BuildFrom)
// * it has parameters
// * is marked with one or more attributes deriving from ITestBuilder
// * the attributes defines tests (difficult to access without evaluating the code)
bool noParameters = methodSymbol.Parameters.IsEmpty;
bool noITestBuilders = !methodSymbol.GetAttributes().Any(a => a.DerivesFromITestBuilder(context.Compilation));
return noParameters && noITestBuilders;
}
private static bool HasFlag(int enumValue, int flag)
=> (enumValue & flag) == flag;
private static bool HasExactFlag(int enumValue, int flag)
=> enumValue == flag;
}
}