Skip to content

Commit 3042d91

Browse files
Fix the TypeClassParameter analyzer (#242)
- Ignore methods and local functions used as a Method Reference - Update documentation
1 parent cd117d6 commit 3042d91

9 files changed

Lines changed: 300 additions & 55 deletions

File tree

Analyzers/Refactoring/TypeClassParameter/Analyzer.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace StyleChecker.Analyzers.Refactoring.TypeClassParameter;
22

33
using System;
4+
using System.Collections.Frozen;
45
using System.Collections.Generic;
56
using System.Collections.Immutable;
67
using System.Linq;
@@ -90,7 +91,9 @@ static Func<IMethodSymbol, IEnumerable<Call>>
9091
}
9192

9293
var toCalls = NewCallsSupplier(global.GetAllOperations());
94+
var referenceSymbolSet = global.GetAllReferenceSymbols();
9395
var all = global.GetAllSymbols()
96+
.Where(s => !referenceSymbolSet.Contains(s))
9497
.SelectMany(toCalls)
9598
.SelectMany(c => c.ToDiagnostics())
9699
.ToList();
@@ -158,10 +161,20 @@ static Func<IMethodSymbol, IEnumerable<Call>> NewCallsSupplier<T>(
158161

159162
var root = context.GetCompilationUnitRoot();
160163
var allNodes = root.DescendantNodes();
164+
var methodReferenceSet = Enumerable.Empty<SyntaxNode>()
165+
.Concat(allNodes.OfType<IdentifierNameSyntax>())
166+
.Concat(allNodes.OfType<MemberAccessExpressionSyntax>())
167+
.Select(toOperation)
168+
.OfType<IMethodReferenceOperation>()
169+
.Select(o => o.Method)
170+
.ToFrozenSet();
171+
global.AddReferenceSymbols(methodReferenceSet);
172+
161173
var localFunctions = allNodes.OfType<LocalFunctionStatementSyntax>()
162174
.Select(toOperation)
163175
.OfType<ILocalFunctionOperation>()
164176
.Select(o => o.Symbol)
177+
.Where(m => !methodReferenceSet.Contains(m))
165178
.Where(HasTypeClassParameter);
166179

167180
var methodGroups = allNodes.OfType<MethodDeclarationSyntax>()
@@ -171,7 +184,8 @@ static Func<IMethodSymbol, IEnumerable<Call>> NewCallsSupplier<T>(
171184
&& !m.IsExtern
172185
&& m.PartialDefinitionPart is null
173186
&& m.PartialImplementationPart is null
174-
&& m.ContainingType.TypeKind is not TypeKind.Interface)
187+
&& m.ContainingType.TypeKind is not TypeKind.Interface
188+
&& !methodReferenceSet.Contains(m))
175189
.Where(HasTypeClassParameter)
176190
.GroupBy(IsPrivate);
177191
var (privateMethods, unitMethods) = Split(methodGroups);

Analyzers/Refactoring/TypeClassParameter/MethodInvocationBank.cs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace StyleChecker.Analyzers.Refactoring.TypeClassParameter;
22

3+
using System.Collections.Frozen;
34
using System.Collections.Generic;
45
using System.Collections.Immutable;
56
using Microsoft.CodeAnalysis;
@@ -16,10 +17,6 @@ namespace StyleChecker.Analyzers.Refactoring.TypeClassParameter;
1617
/// </remarks>
1718
public sealed class MethodInvocationBank
1819
{
19-
private readonly List<IMethodSymbol> symbols = [];
20-
21-
private readonly List<IInvocationOperation> operations = [];
22-
2320
/// <summary>
2421
/// Initializes a new instance of the <see cref="MethodInvocationBank"/>
2522
/// class.
@@ -28,6 +25,12 @@ public MethodInvocationBank()
2825
{
2926
}
3027

28+
private List<IMethodSymbol> Symbols { get; } = [];
29+
30+
private List<IInvocationOperation> Operations { get; } = [];
31+
32+
private HashSet<IMethodSymbol> ReferenceSymbolSet { get; } = [];
33+
3134
/// <summary>
3235
/// Adds a collection of method symbols to the bank.
3336
/// </summary>
@@ -36,7 +39,7 @@ public MethodInvocationBank()
3639
/// </param>
3740
public void AddSymbols(IEnumerable<IMethodSymbol> all)
3841
{
39-
AddRange(symbols, all);
42+
AddRange(Symbols, all);
4043
}
4144

4245
/// <summary>
@@ -47,7 +50,21 @@ public void AddSymbols(IEnumerable<IMethodSymbol> all)
4750
/// </param>
4851
public void AddOperations(IEnumerable<IInvocationOperation> all)
4952
{
50-
AddRange(operations, all);
53+
AddRange(Operations, all);
54+
}
55+
56+
/// <summary>
57+
/// Adds a collection of Method Reference symbols to the bank.
58+
/// </summary>
59+
/// <param name="all">
60+
/// The collection of method reference symbols to add.
61+
/// </param>
62+
public void AddReferenceSymbols(IEnumerable<IMethodSymbol> all)
63+
{
64+
lock (ReferenceSymbolSet)
65+
{
66+
ReferenceSymbolSet.UnionWith(all);
67+
}
5168
}
5269

5370
/// <summary>
@@ -56,9 +73,9 @@ public void AddOperations(IEnumerable<IInvocationOperation> all)
5673
/// <returns>
5774
/// An immutable array of all method symbols in the bank.
5875
/// </returns>
59-
public ImmutableArray<IMethodSymbol> GetAllSymbols()
76+
public FrozenSet<IMethodSymbol> GetAllSymbols()
6077
{
61-
return ToImmutableArray(symbols);
78+
return ToFrozenSet(Symbols);
6279
}
6380

6481
/// <summary>
@@ -69,7 +86,18 @@ public ImmutableArray<IMethodSymbol> GetAllSymbols()
6986
/// </returns>
7087
public ImmutableArray<IInvocationOperation> GetAllOperations()
7188
{
72-
return ToImmutableArray(operations);
89+
return ToImmutableArray(Operations);
90+
}
91+
92+
/// <summary>
93+
/// Gets all Method Reference symbols in the bank.
94+
/// </summary>
95+
/// <returns>
96+
/// A frozen set of all Method Reference symbols in the bank.
97+
/// </returns>
98+
public FrozenSet<IMethodSymbol> GetAllReferenceSymbols()
99+
{
100+
return ToFrozenSet(ReferenceSymbolSet);
73101
}
74102

75103
private static void AddRange<T>(List<T> a, IEnumerable<T> all)
@@ -87,4 +115,12 @@ private static ImmutableArray<T> ToImmutableArray<T>(IEnumerable<T> a)
87115
return [.. a];
88116
}
89117
}
118+
119+
private static FrozenSet<T> ToFrozenSet<T>(IEnumerable<T> a)
120+
{
121+
lock (a)
122+
{
123+
return a.ToFrozenSet();
124+
}
125+
}
90126
}

TestSuite/Refactoring/TypeClassParameter/AnalyzerTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ public AnalyzerTest()
2626
public void Okay()
2727
=> VerifyDiagnostic(ReadText("Okay"), Atmosphere.Default);
2828

29+
[TestMethod]
30+
public void LocalFunctionOkay()
31+
=> VerifyDiagnostic(ReadText("LocalFunctionOkay"), Atmosphere.Default);
32+
33+
[TestMethod]
34+
public void GlobalOkay()
35+
=> VerifyDiagnostic(
36+
[
37+
ReadText("ReferencedOkay"),
38+
ReadText("ReferencingOkay"),
39+
],
40+
Atmosphere.Default);
41+
2942
[TestMethod]
3043
public void Code()
3144
=> Check("Code");
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#pragma warning disable CS8321
2+
3+
namespace StyleChecker.Test.Refactoring.TypeClassParameter;
4+
5+
using System;
6+
7+
public sealed class LocalFunctionOkay
8+
{
9+
public void IncludesStaticClass()
10+
{
11+
void Print(Type t)
12+
{
13+
Console.WriteLine(t.FullName);
14+
}
15+
16+
Print(typeof(string));
17+
Print(typeof(StaticClass));
18+
}
19+
20+
public void NotAllArgumentsAreTypeof()
21+
{
22+
void Print(Type t)
23+
{
24+
Console.WriteLine(t.FullName);
25+
}
26+
27+
Print(typeof(string));
28+
Print(GetType());
29+
}
30+
31+
public void NobodyInvokes()
32+
{
33+
void NeverUsed(Type t)
34+
=> Console.WriteLine(t.FullName);
35+
}
36+
37+
public void MethodRederence()
38+
{
39+
string ToName(Type t) => t.FullName;
40+
41+
void Print(Func<Type, string> toName, Type[] array)
42+
{
43+
foreach (var i in array)
44+
{
45+
Console.Write(toName(i));
46+
}
47+
}
48+
49+
var name = ToName(typeof(object));
50+
Print(ToName, [typeof(string), typeof(int)]);
51+
}
52+
53+
public static class StaticClass;
54+
}

TestSuite/Refactoring/TypeClassParameter/Okay.cs

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,49 +6,85 @@ namespace StyleChecker.Test.Refactoring.TypeClassParameter;
66

77
public sealed class Okay
88
{
9-
public void IncludesStaticClass()
9+
public class IncludesStaticClass
1010
{
11-
void Print(Type t)
11+
public void Print(Type t)
1212
{
1313
Console.WriteLine(t.FullName);
1414
}
1515

16-
Print(typeof(string));
17-
Print(typeof(StaticClass));
16+
public void Invoke()
17+
{
18+
Print(typeof(string));
19+
Print(typeof(StaticClass));
20+
}
1821
}
1922

20-
public void NotAllArgumentsAreTypeofLocalFunction()
23+
public class NotAllArgumentsAreTypeof
2124
{
22-
void Print(Type t)
25+
public void Print(Type t)
2326
{
2427
Console.WriteLine(t.FullName);
2528
}
2629

27-
Print(typeof(string));
28-
Print(GetType());
30+
public void Invoke()
31+
{
32+
Print(typeof(string));
33+
Print(GetType());
34+
}
2935
}
3036

31-
public void NobodyInvokesLocalFunction()
37+
public void NobodyInvokes(Type type)
3238
{
33-
void NeverUsed(Type t)
34-
=> Console.WriteLine(t.FullName);
39+
Console.WriteLine(type.FullName);
3540
}
3641

37-
public void NotAllArgumentsAreTypeof(Type type)
42+
public class MethodReference
3843
{
39-
Console.WriteLine(type.FullName);
44+
public string ToName(Type t)
45+
=> t.FullName;
46+
47+
void Print(Func<Type, string> toName, Type[] array)
48+
{
49+
foreach (var i in array)
50+
{
51+
Console.Write(toName(i));
52+
}
53+
}
54+
55+
public void Invoke()
56+
{
57+
var name = ToName(typeof(object));
58+
Print(ToName, [typeof(string), typeof(int)]);
59+
}
4060
}
4161

42-
public void NobodyInvokes(Type type)
62+
public class InstanceMethodReference
4363
{
44-
Console.WriteLine(type.FullName);
64+
public string ToName(Type t)
65+
=> t.FullName;
66+
67+
public void Invoke()
68+
{
69+
var name = ToName(typeof(object));
70+
}
4571
}
4672

47-
public void Invoke()
73+
private Func<Type, string> instanceAction
74+
= new InstanceMethodReference().ToName;
75+
76+
public class StaticMethodReference
4877
{
49-
NotAllArgumentsAreTypeof(typeof(string));
50-
NotAllArgumentsAreTypeof(GetType());
78+
public static string ToName(Type t)
79+
=> t.FullName;
80+
81+
public void Invoke()
82+
{
83+
var name = ToName(typeof(object));
84+
}
5185
}
5286

87+
private Func<Type, string> staticAction = StaticMethodReference.ToName;
88+
5389
public static class StaticClass;
5490
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace StyleChecker.Test.Refactoring.TypeClassParameter;
2+
3+
using System;
4+
5+
public sealed class ReferencedOkay
6+
{
7+
private static void Log(string message)
8+
{
9+
}
10+
11+
/// <summary>
12+
/// Print the specified type.
13+
/// </summary>
14+
/// <param name="type">
15+
/// The type to be printed.
16+
/// </param>
17+
public static void PrintMethod(Type type)
18+
{
19+
Log(type.FullName);
20+
}
21+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace StyleChecker.Test.Refactoring.TypeClassParameter;
2+
3+
public sealed class ReferencingOkay
4+
{
5+
public void CallWithStringType()
6+
{
7+
ReferencedOkay.PrintMethod(typeof(string));
8+
}
9+
10+
public void CallWithIntType()
11+
{
12+
ReferencedOkay.PrintMethod(typeof(int));
13+
}
14+
15+
public void MethodReference()
16+
{
17+
var method = ReferencedOkay.PrintMethod;
18+
}
19+
}

0 commit comments

Comments
 (0)