Skip to content

Commit 4e42387

Browse files
committed
REFL019 Check filter types. Fix #95.
1 parent 10d3716 commit 4e42387

File tree

12 files changed

+235
-38
lines changed

12 files changed

+235
-38
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,9 @@ Analyzers checking System.Reflection
7272
<td><a href="https://github.com/DotNetAnalyzers/ReflectionAnalyzers/tree/master/documentation/REFL018.md">REFL018</a></td>
7373
<td>The member is explicitly implemented.</td>
7474
</tr>
75+
<tr>
76+
<td><a href="https://github.com/DotNetAnalyzers/ReflectionAnalyzers/tree/master/documentation/REFL019.md">REFL019</a></td>
77+
<td>No member matches the types.</td>
78+
</tr>
7579
<table>
7680
<!-- end generated table -->
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace ReflectionAnalyzers.Tests.REFL019NoMemberMatchesTheTypesTests
2+
{
3+
using Gu.Roslyn.Asserts;
4+
using Microsoft.CodeAnalysis.Diagnostics;
5+
using NUnit.Framework;
6+
7+
public class Diagnostics
8+
{
9+
private static readonly DiagnosticAnalyzer Analyzer = new GetXAnalyzer();
10+
private static readonly ExpectedDiagnostic ExpectedDiagnostic = ExpectedDiagnostic.Create(REFL019NoMemberMatchesTheTypes.Descriptor);
11+
12+
[TestCase("GetConstructor(↓Type.EmptyTypes)")]
13+
[TestCase("GetConstructor(↓Array.Empty<Type>())")]
14+
[TestCase("GetConstructor(↓new Type[0])")]
15+
[TestCase("GetConstructor(↓new Type[1] { typeof(double) })")]
16+
[TestCase("GetConstructor(↓new Type[] { typeof(double) })")]
17+
[TestCase("GetConstructor(↓new[] { typeof(double) })")]
18+
public void GetConstructor(string call)
19+
{
20+
var code = @"
21+
namespace RoslynSandbox
22+
{
23+
using System;
24+
25+
public class Foo
26+
{
27+
public Foo(int value)
28+
{
29+
var ctor = typeof(Foo).GetConstructor(↓Type.EmptyTypes);
30+
}
31+
}
32+
}".AssertReplace("GetConstructor(↓Type.EmptyTypes)", call);
33+
34+
AnalyzerAssert.Diagnostics(Analyzer, ExpectedDiagnostic, code);
35+
}
36+
}
37+
}

ReflectionAnalyzers.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{1C271AF2
4848
documentation\REFL016.md = documentation\REFL016.md
4949
documentation\REFL017.md = documentation\REFL017.md
5050
documentation\REFL018.md = documentation\REFL018.md
51+
documentation\REFL019.md = documentation\REFL019.md
5152
RELEASE_NOTES.md = RELEASE_NOTES.md
5253
EndProjectSection
5354
EndProject

ReflectionAnalyzers/Helpers/GetX.cs

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace ReflectionAnalyzers
22
{
3+
using System;
34
using System.Collections.Generic;
45
using System.Linq;
56
using Gu.Roslyn.AnalyzerExtensions;
@@ -167,6 +168,7 @@ internal static GetXResult TryGetMember(IMethodSymbol getX, ITypeSymbol targetTy
167168
}
168169

169170
if (getX == KnownSymbol.Type.GetNestedType ||
171+
getX == KnownSymbol.Type.GetConstructor ||
170172
flags.HasFlagFast(BindingFlags.DeclaredOnly) ||
171173
(flags.HasFlagFast(BindingFlags.Static) &&
172174
!flags.HasFlagFast(BindingFlags.Instance) &&
@@ -252,17 +254,14 @@ internal static GetXResult TryGetMember(IMethodSymbol getX, ITypeSymbol targetTy
252254
return GetXResult.UseContainingType;
253255
}
254256

255-
if (member.MetadataName == targetMetadataName &&
256-
!MatchesFilter(member, member.MetadataName, flags, null))
257+
if (IsWrongFlags(member))
257258
{
258259
return GetXResult.WrongFlags;
259260
}
260261

261-
if (!member.ContainingType.Equals(targetType) &&
262-
(member.IsStatic ||
263-
flags.HasFlagFast(BindingFlags.DeclaredOnly)))
262+
if (IsWrongTypes(member))
264263
{
265-
return GetXResult.WrongFlags;
264+
return GetXResult.WrongTypes;
266265
}
267266
}
268267

@@ -347,6 +346,37 @@ bool IsUseContainingType(ISymbol symbol)
347346
symbol.DeclaredAccessibility == Accessibility.Private));
348347
}
349348

349+
bool IsWrongFlags(ISymbol symbol)
350+
{
351+
if (symbol.MetadataName == targetMetadataName &&
352+
!MatchesFilter(symbol, symbol.MetadataName, flags, null))
353+
{
354+
return true;
355+
}
356+
357+
if (!symbol.ContainingType.Equals(targetType) &&
358+
(symbol.IsStatic ||
359+
flags.HasFlagFast(BindingFlags.DeclaredOnly)))
360+
{
361+
return true;
362+
}
363+
364+
return false;
365+
}
366+
367+
bool IsWrongTypes(ISymbol symbol)
368+
{
369+
if (types == null ||
370+
ReferenceEquals(types, AnyTypes))
371+
{
372+
return false;
373+
}
374+
375+
const BindingFlags everything = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
376+
return symbol.MetadataName == targetMetadataName &&
377+
!MatchesFilter(symbol, symbol.MetadataName, everything, types);
378+
}
379+
350380
bool IsExplicitImplementation(out ISymbol result)
351381
{
352382
foreach (var @interface in targetType.AllInterfaces)
@@ -444,10 +474,13 @@ private static bool TryGetFlagsOrDefault(InvocationExpressionSyntax invocation,
444474
TryGetDefaultFlags(getX.MetadataName, out bindingFlags);
445475
}
446476

447-
private static bool TryGetDefaultFlags(string getXName, out BindingFlags defaultFlags)
477+
private static bool TryGetDefaultFlags(string getXName, out BindingFlags flags)
448478
{
449479
switch (getXName)
450480
{
481+
case "GetConstructor":
482+
flags = BindingFlags.Public | BindingFlags.Instance;
483+
return true;
451484
case "GetField":
452485
case "GetFields":
453486
case "GetEvent":
@@ -460,11 +493,11 @@ private static bool TryGetDefaultFlags(string getXName, out BindingFlags default
460493
case "GetNestedTypes":
461494
case "GetProperty":
462495
case "GetProperties":
463-
defaultFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
496+
flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
464497
return true;
465498
}
466499

467-
defaultFlags = 0;
500+
flags = 0;
468501
return false;
469502
}
470503

@@ -498,24 +531,29 @@ private static bool TryGetTypesArgument(InvocationExpressionSyntax invocation, I
498531
private static bool TryGetTypes(ArgumentSyntax argument, SyntaxNodeAnalysisContext context, out IReadOnlyList<ITypeSymbol> types)
499532
{
500533
types = null;
501-
return TryGetTypes(argument, out types);
502-
503-
bool TryGetTypes(ArgumentSyntax array, out IReadOnlyList<ITypeSymbol> result)
534+
switch (argument.Expression)
504535
{
505-
result = null;
506-
switch (array.Expression)
507-
{
508-
case ImplicitArrayCreationExpressionSyntax arrayCreation when arrayCreation.Initializer is InitializerExpressionSyntax initializer:
509-
return TryGetTypesFromInitializer(initializer, out result);
510-
case ArrayCreationExpressionSyntax arrayCreation when arrayCreation.Initializer is InitializerExpressionSyntax initializer:
511-
return TryGetTypesFromInitializer(initializer, out result);
512-
case LiteralExpressionSyntax literal when literal.IsKind(SyntaxKind.NullLiteralExpression):
513-
return true;
514-
}
515-
516-
return false;
536+
case ImplicitArrayCreationExpressionSyntax arrayCreation when arrayCreation.Initializer is InitializerExpressionSyntax initializer:
537+
return TryGetTypesFromInitializer(initializer, out types);
538+
case ArrayCreationExpressionSyntax arrayCreation when arrayCreation.Initializer is InitializerExpressionSyntax initializer:
539+
return TryGetTypesFromInitializer(initializer, out types);
540+
case ArrayCreationExpressionSyntax arrayCreation when arrayCreation.Initializer == null:
541+
types = Array.Empty<ITypeSymbol>();
542+
return true;
543+
case LiteralExpressionSyntax literal when literal.IsKind(SyntaxKind.NullLiteralExpression):
544+
return true;
545+
case MemberAccessExpressionSyntax memberAccess when context.SemanticModel.TryGetSymbol(memberAccess, context.CancellationToken, out ISymbol symbol) &&
546+
symbol == KnownSymbol.Type.EmptyTypes:
547+
types = Array.Empty<ITypeSymbol>();
548+
return true;
549+
case InvocationExpressionSyntax invocation when context.SemanticModel.TryGetSymbol(invocation, context.CancellationToken, out ISymbol symbol) &&
550+
symbol == KnownSymbol.Array.Empty:
551+
types = Array.Empty<ITypeSymbol>();
552+
return true;
517553
}
518554

555+
return false;
556+
519557
bool TryGetTypesFromInitializer(InitializerExpressionSyntax initializer, out IReadOnlyList<ITypeSymbol> result)
520558
{
521559
var temp = new ITypeSymbol[initializer.Expressions.Count];

ReflectionAnalyzers/Helpers/GetXResult.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ internal enum GetXResult
88
ExplicitImplementation,
99
Ambiguous,
1010
WrongFlags,
11+
WrongTypes,
1112
WrongMemberType,
1213
UseContainingType,
1314
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace ReflectionAnalyzers
2+
{
3+
using Gu.Roslyn.AnalyzerExtensions;
4+
5+
internal class ArrayType : QualifiedType
6+
{
7+
internal readonly QualifiedMethod Empty;
8+
9+
public ArrayType()
10+
: base("System.Array")
11+
{
12+
this.Empty = new QualifiedMethod(this, nameof(this.Empty));
13+
}
14+
}
15+
}

ReflectionAnalyzers/Helpers/KnownSymbols/KnownSymbol.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ internal static class KnownSymbol
66
{
77
internal static readonly ObjectType Object = new ObjectType();
88
internal static readonly StringType String = new StringType();
9+
internal static readonly ArrayType Array = new ArrayType();
910
internal static readonly QualifiedType Boolean = Create("System.Boolean", "bool");
1011

1112
internal static readonly TypeType Type = new TypeType();

ReflectionAnalyzers/Helpers/KnownSymbols/TypeType.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace ReflectionAnalyzers
44

55
internal class TypeType : QualifiedType
66
{
7+
internal readonly QualifiedField EmptyTypes;
78
internal readonly QualifiedMethod GetConstructor;
89
internal readonly QualifiedMethod GetEvent;
910
internal readonly QualifiedMethod GetField;
@@ -15,6 +16,7 @@ internal class TypeType : QualifiedType
1516
internal TypeType()
1617
: base("System.Type")
1718
{
19+
this.EmptyTypes = new QualifiedField(this, nameof(this.EmptyTypes));
1820
this.GetConstructor = new QualifiedMethod(this, nameof(this.GetConstructor));
1921
this.GetEvent = new QualifiedMethod(this, nameof(this.GetEvent));
2022
this.GetField = new QualifiedMethod(this, nameof(this.GetField));

ReflectionAnalyzers/NodeAnalzers/GetXAnalyzer.cs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ internal class GetXAnalyzer : DiagnosticAnalyzer
2424
REFL013MemberIsOfWrongType.Descriptor,
2525
REFL014PreferGetMemberThenAccessor.Descriptor,
2626
REFL015UseContainingType.Descriptor,
27-
REFL018ExplicitImplementation.Descriptor);
27+
REFL018ExplicitImplementation.Descriptor,
28+
REFL019NoMemberMatchesTheTypes.Descriptor);
2829

2930
/// <inheritdoc/>
3031
public override void Initialize(AnalysisContext context)
@@ -40,17 +41,17 @@ private static void Handle(SyntaxNodeAnalysisContext context)
4041
context.Node is InvocationExpressionSyntax invocation &&
4142
invocation.ArgumentList is ArgumentListSyntax argumentList)
4243
{
43-
switch (TryGetX(context, out var targetType, out var nameArg, out var targetName, out var target, out var flagsArg, out var effectiveFlags, out var typesArg, out var types))
44+
switch (TryGetX(context, out var type, out var nameArg, out var memberName, out var member, out var flagsArg, out var effectiveFlags, out var typesArg, out var types))
4445
{
4546
case GetXResult.NoMatch:
46-
context.ReportDiagnostic(Diagnostic.Create(REFL003MemberDoesNotExist.Descriptor, nameArg.GetLocation(), targetType, targetName));
47+
context.ReportDiagnostic(Diagnostic.Create(REFL003MemberDoesNotExist.Descriptor, nameArg.GetLocation(), type, memberName));
4748
break;
4849

4950
case GetXResult.Ambiguous:
5051
context.ReportDiagnostic(Diagnostic.Create(REFL004AmbiguousMatch.Descriptor, argumentList.GetLocation()));
5152
break;
5253

53-
case GetXResult.WrongFlags when TryGetExpectedFlags(target, targetType, out var correctFlags):
54+
case GetXResult.WrongFlags when TryGetExpectedFlags(member, type, out var correctFlags):
5455
if (flagsArg != null)
5556
{
5657
context.ReportDiagnostic(
@@ -80,11 +81,11 @@ context.Node is InvocationExpressionSyntax invocation &&
8081
break;
8182

8283
case GetXResult.Single:
83-
if (TryGetExpectedFlags(target, targetType, out var expectedFlags) &&
84+
if (TryGetExpectedFlags(member, type, out var expectedFlags) &&
8485
effectiveFlags != expectedFlags)
8586
{
8687
if (flagsArg != null &&
87-
HasRedundantFlag(target, targetType, effectiveFlags))
88+
HasRedundantFlag(member, type, effectiveFlags))
8889
{
8990
context.ReportDiagnostic(
9091
Diagnostic.Create(
@@ -95,7 +96,7 @@ context.Node is InvocationExpressionSyntax invocation &&
9596
}
9697

9798
if (flagsArg == null ||
98-
HasMissingFlag(target, targetType, effectiveFlags))
99+
HasMissingFlag(member, type, effectiveFlags))
99100
{
100101
context.ReportDiagnostic(
101102
Diagnostic.Create(
@@ -106,7 +107,7 @@ context.Node is InvocationExpressionSyntax invocation &&
106107
}
107108
}
108109

109-
if (IsPreferGetMemberThenAccessor(invocation, target, context, out var call))
110+
if (IsPreferGetMemberThenAccessor(invocation, member, context, out var call))
110111
{
111112
context.ReportDiagnostic(
112113
Diagnostic.Create(
@@ -120,7 +121,18 @@ context.Node is InvocationExpressionSyntax invocation &&
120121

121122
case GetXResult.WrongMemberType:
122123
context.ReportDiagnostic(
123-
Diagnostic.Create(REFL013MemberIsOfWrongType.Descriptor, invocation.GetNameLocation(), targetType, targetName, target.GetType().Name));
124+
Diagnostic.Create(
125+
REFL013MemberIsOfWrongType.Descriptor,
126+
invocation.GetNameLocation(),
127+
type,
128+
memberName,
129+
member.GetType().Name));
130+
break;
131+
case GetXResult.WrongTypes:
132+
context.ReportDiagnostic(
133+
Diagnostic.Create(
134+
REFL019NoMemberMatchesTheTypes.Descriptor,
135+
typesArg?.GetLocation() ?? invocation.GetNameLocation()));
124136
break;
125137
case GetXResult.UseContainingType:
126138
context.ReportDiagnostic(
@@ -129,8 +141,8 @@ context.Node is InvocationExpressionSyntax invocation &&
129141
TargetTypeLocation(),
130142
ImmutableDictionary<string, string>.Empty.Add(
131143
nameof(ISymbol.ContainingType),
132-
target.ContainingType.ToMinimalDisplayString(context.SemanticModel, invocation.SpanStart)),
133-
target.ContainingType.Name));
144+
member.ContainingType.ToMinimalDisplayString(context.SemanticModel, invocation.SpanStart)),
145+
member.ContainingType.Name));
134146
break;
135147
case GetXResult.ExplicitImplementation:
136148
context.ReportDiagnostic(
@@ -139,8 +151,8 @@ context.Node is InvocationExpressionSyntax invocation &&
139151
TargetTypeLocation(),
140152
ImmutableDictionary<string, string>.Empty.Add(
141153
nameof(ISymbol.ContainingType),
142-
target.ContainingType.ToMinimalDisplayString(context.SemanticModel, invocation.SpanStart)),
143-
target.Name));
154+
member.ContainingType.ToMinimalDisplayString(context.SemanticModel, invocation.SpanStart)),
155+
member.Name));
144156
break;
145157
case GetXResult.Unknown:
146158
break;

ReflectionAnalyzers/REFL018ExplicitImplementation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ internal static class REFL018ExplicitImplementation
1616
description: "The member is explicitly implemented.",
1717
helpLinkUri: HelpLink.ForId(DiagnosticId));
1818
}
19-
}
19+
}

0 commit comments

Comments
 (0)