1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . Collections . Immutable ;
4
- using System . Linq ;
5
4
using Microsoft . CodeAnalysis ;
6
- using Microsoft . CodeAnalysis . CSharp ;
7
- using Microsoft . CodeAnalysis . CSharp . Syntax ;
8
5
using Microsoft . CodeAnalysis . Diagnostics ;
6
+ using Microsoft . CodeAnalysis . Operations ;
9
7
using NUnit . Analyzers . Constants ;
8
+ using NUnit . Analyzers . Helpers ;
9
+ using NUnit . Analyzers . Operations ;
10
10
11
11
namespace NUnit . Analyzers . UseSpecificConstraint
12
12
{
13
13
[ DiagnosticAnalyzer ( LanguageNames . CSharp ) ]
14
- public sealed class UseSpecificConstraintAnalyzer : DiagnosticAnalyzer
14
+ public sealed class UseSpecificConstraintAnalyzer : BaseAssertionAnalyzer
15
15
{
16
16
private static readonly DiagnosticDescriptor simplifyConstraint = DiagnosticDescriptorCreator . Create (
17
17
id : AnalyzerIdentifiers . UseSpecificConstraint ,
@@ -25,57 +25,84 @@ public sealed class UseSpecificConstraintAnalyzer : DiagnosticAnalyzer
25
25
public override ImmutableArray < DiagnosticDescriptor > SupportedDiagnostics =>
26
26
ImmutableArray . Create ( simplifyConstraint ) ;
27
27
28
- public override void Initialize ( AnalysisContext context )
28
+ protected override void AnalyzeAssertInvocation ( Version nunitVersion , OperationAnalysisContext context , IInvocationOperation assertOperation )
29
29
{
30
- context . ConfigureGeneratedCodeAnalysis ( GeneratedCodeAnalysisFlags . None ) ;
31
- context . EnableConcurrentExecution ( ) ;
32
- context . RegisterCompilationStartAction ( this . AnalyzeCompilationStart ) ;
33
- }
30
+ if ( ! AssertHelper . TryGetActualAndConstraintOperations ( assertOperation ,
31
+ out var actualOperation , out var constraintExpression ) )
32
+ {
33
+ return ;
34
+ }
34
35
35
- private static void AnalyzeInvocation ( Version nunitVersion , SyntaxNodeAnalysisContext context )
36
- {
37
- var invocationExpression = ( InvocationExpressionSyntax ) context . Node ;
36
+ var actualType = AssertHelper . GetUnwrappedActualType ( actualOperation ) ;
37
+ if ( actualType is null )
38
+ return ;
38
39
39
- if ( invocationExpression . ArgumentList . Arguments . Count == 1 &&
40
- invocationExpression . Expression is MemberAccessExpressionSyntax memberAccessExpression &&
41
- memberAccessExpression . Name . Identifier . Text == NUnitFrameworkConstants . NameOfIsEqualTo )
40
+ foreach ( var constraintPartExpression in constraintExpression . ConstraintParts )
42
41
{
43
- ExpressionSyntax argument = invocationExpression . ArgumentList . Arguments [ 0 ] . Expression ;
42
+ if ( constraintPartExpression . HasIncompatiblePrefixes ( )
43
+ || constraintPartExpression . HasCustomComparer ( )
44
+ || constraintPartExpression . HasUnknownExpressions ( ) )
45
+ {
46
+ return ;
47
+ }
48
+
49
+ var constraintMethod = constraintPartExpression . GetConstraintMethod ( ) ;
50
+ if ( constraintMethod ? . Name != NUnitFrameworkConstants . NameOfIsEqualTo )
51
+ continue ;
52
+
53
+ var expectedOperation = constraintPartExpression . GetExpectedArgument ( ) ;
54
+ if ( expectedOperation is null )
55
+ continue ;
56
+
44
57
string ? constraint = null ;
45
58
46
- if ( argument is LiteralExpressionSyntax literalExpression )
47
- {
48
- constraint = literalExpression . Kind ( ) switch
49
- {
50
- SyntaxKind . NullLiteralExpression => NUnitFrameworkConstants . NameOfIsNull ,
51
- SyntaxKind . FalseLiteralExpression => NUnitFrameworkConstants . NameOfIsFalse ,
52
- SyntaxKind . TrueLiteralExpression => NUnitFrameworkConstants . NameOfIsTrue ,
53
- _ => null ,
54
- } ;
59
+ // Look for both direct `0` and cast `(short)0` to catch all 0 values.
60
+ ILiteralOperation ? literalOperation = ( expectedOperation is IConversionOperation conversionOperation ?
61
+ conversionOperation . Operand : expectedOperation ) as ILiteralOperation ;
55
62
56
- if ( constraint is null && nunitVersion . Major >= 4 )
63
+ if ( literalOperation is not null )
64
+ {
65
+ if ( literalOperation . ConstantValue . HasValue )
57
66
{
58
- constraint = literalExpression . Kind ( ) switch
67
+ constraint = literalOperation . ConstantValue . Value switch
59
68
{
60
- SyntaxKind . DefaultLiteralExpression => NUnitV4FrameworkConstants . NameOfIsDefault ,
61
- _ => constraint ,
69
+ null => NUnitFrameworkConstants . NameOfIsNull ,
70
+ false => NUnitFrameworkConstants . NameOfIsFalse ,
71
+ true => NUnitFrameworkConstants . NameOfIsTrue ,
72
+ 0 or 0d or 0f or 0m => NUnitFrameworkConstants . NameOfIsZero ,
73
+ _ => null ,
62
74
} ;
75
+
76
+ if ( constraint is null &&
77
+ literalOperation . ConstantValue . Value is IConvertible convertible )
78
+ {
79
+ if ( convertible . ToDouble ( null ) == 0 )
80
+ {
81
+ // Catches all other 0 values: (byte)0, (short)0, 0u, 0L, 0uL
82
+ constraint = NUnitFrameworkConstants . NameOfIsZero ;
83
+ }
84
+ }
63
85
}
64
86
}
65
- else if ( argument is DefaultExpressionSyntax defaultExpression )
87
+ else if ( expectedOperation is IDefaultValueOperation defaultValueOperation )
66
88
{
67
- if ( defaultExpression . Type is PredefinedTypeSyntax predefinedType )
89
+ if ( defaultValueOperation . Type is INamedTypeSymbol defaultType )
68
90
{
69
- if ( predefinedType . Keyword . IsKind ( SyntaxKind . ObjectKeyword ) ||
70
- predefinedType . Keyword . IsKind ( SyntaxKind . StringKeyword ) )
91
+ if ( defaultType . SpecialType == SpecialType . System_Object ||
92
+ defaultType . SpecialType == SpecialType . System_String )
71
93
{
72
94
constraint = NUnitFrameworkConstants . NameOfIsNull ;
73
95
}
74
96
else if ( nunitVersion . Major >= 4 )
75
97
{
98
+ // We cannot use `Is.Default` if the actual type is `object`.
99
+ // Note that the case `default(object)` is handled above.
100
+ if ( actualType . SpecialType == SpecialType . System_Object )
101
+ continue ;
102
+
76
103
constraint = NUnitV4FrameworkConstants . NameOfIsDefault ;
77
104
}
78
- else if ( predefinedType . Keyword . IsKind ( SyntaxKind . BoolKeyword ) )
105
+ else if ( defaultType . SpecialType == SpecialType . System_Boolean )
79
106
{
80
107
constraint = NUnitFrameworkConstants . NameOfIsFalse ;
81
108
}
@@ -84,31 +111,16 @@ invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpr
84
111
85
112
if ( constraint is not null )
86
113
{
87
- var diagnostic = Diagnostic . Create ( simplifyConstraint , invocationExpression . GetLocation ( ) ,
114
+ SyntaxNode syntax = constraintPartExpression . Root ! . Syntax ;
115
+ var diagnostic = Diagnostic . Create ( simplifyConstraint , syntax . GetLocation ( ) ,
88
116
new Dictionary < string , string ? >
89
117
{
90
118
[ AnalyzerPropertyKeys . SpecificConstraint ] = constraint ,
91
119
} . ToImmutableDictionary ( ) ,
92
- argument . ToString ( ) , constraint ) ;
120
+ expectedOperation . Syntax . ToString ( ) , constraint ) ;
93
121
context . ReportDiagnostic ( diagnostic ) ;
94
122
}
95
123
}
96
124
}
97
-
98
- private void AnalyzeCompilationStart ( CompilationStartAnalysisContext context )
99
- {
100
- IEnumerable < AssemblyIdentity > referencedAssemblies = context . Compilation . ReferencedAssemblyNames ;
101
-
102
- AssemblyIdentity ? nunit = referencedAssemblies . FirstOrDefault ( a =>
103
- a . Name . Equals ( NUnitFrameworkConstants . NUnitFrameworkAssemblyName , StringComparison . OrdinalIgnoreCase ) ) ;
104
-
105
- if ( nunit is null )
106
- {
107
- // Who would use NUnit.Analyzers without NUnit?
108
- return ;
109
- }
110
-
111
- context . RegisterSyntaxNodeAction ( ( context ) => AnalyzeInvocation ( nunit . Version , context ) , SyntaxKind . InvocationExpression ) ;
112
- }
113
125
}
114
126
}
0 commit comments