Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion Analyzers/Refactoring/TypeClassParameter/Analyzer.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace StyleChecker.Analyzers.Refactoring.TypeClassParameter;

using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
Expand Down Expand Up @@ -90,7 +91,9 @@ static Func<IMethodSymbol, IEnumerable<Call>>
}

var toCalls = NewCallsSupplier(global.GetAllOperations());
var referenceSymbolSet = global.GetAllReferenceSymbols();
var all = global.GetAllSymbols()
.Where(s => !referenceSymbolSet.Contains(s))
.SelectMany(toCalls)
.SelectMany(c => c.ToDiagnostics())
.ToList();
Expand Down Expand Up @@ -158,10 +161,20 @@ static Func<IMethodSymbol, IEnumerable<Call>> NewCallsSupplier<T>(

var root = context.GetCompilationUnitRoot();
var allNodes = root.DescendantNodes();
var methodReferenceSet = Enumerable.Empty<SyntaxNode>()
.Concat(allNodes.OfType<IdentifierNameSyntax>())
.Concat(allNodes.OfType<MemberAccessExpressionSyntax>())
.Select(toOperation)
.OfType<IMethodReferenceOperation>()
.Select(o => o.Method)
.ToFrozenSet();
global.AddReferenceSymbols(methodReferenceSet);

var localFunctions = allNodes.OfType<LocalFunctionStatementSyntax>()
.Select(toOperation)
.OfType<ILocalFunctionOperation>()
.Select(o => o.Symbol)
.Where(m => !methodReferenceSet.Contains(m))
.Where(HasTypeClassParameter);

var methodGroups = allNodes.OfType<MethodDeclarationSyntax>()
Expand All @@ -171,7 +184,8 @@ static Func<IMethodSymbol, IEnumerable<Call>> NewCallsSupplier<T>(
&& !m.IsExtern
&& m.PartialDefinitionPart is null
&& m.PartialImplementationPart is null
&& m.ContainingType.TypeKind is not TypeKind.Interface)
&& m.ContainingType.TypeKind is not TypeKind.Interface
&& !methodReferenceSet.Contains(m))
.Where(HasTypeClassParameter)
.GroupBy(IsPrivate);
var (privateMethods, unitMethods) = Split(methodGroups);
Expand Down
54 changes: 45 additions & 9 deletions Analyzers/Refactoring/TypeClassParameter/MethodInvocationBank.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace StyleChecker.Analyzers.Refactoring.TypeClassParameter;

using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
Expand All @@ -16,10 +17,6 @@ namespace StyleChecker.Analyzers.Refactoring.TypeClassParameter;
/// </remarks>
public sealed class MethodInvocationBank
{
private readonly List<IMethodSymbol> symbols = [];

private readonly List<IInvocationOperation> operations = [];

/// <summary>
/// Initializes a new instance of the <see cref="MethodInvocationBank"/>
/// class.
Expand All @@ -28,6 +25,12 @@ public MethodInvocationBank()
{
}

private List<IMethodSymbol> Symbols { get; } = [];

private List<IInvocationOperation> Operations { get; } = [];

private HashSet<IMethodSymbol> ReferenceSymbolSet { get; } = [];

/// <summary>
/// Adds a collection of method symbols to the bank.
/// </summary>
Expand All @@ -36,7 +39,7 @@ public MethodInvocationBank()
/// </param>
public void AddSymbols(IEnumerable<IMethodSymbol> all)
{
AddRange(symbols, all);
AddRange(Symbols, all);
}

/// <summary>
Expand All @@ -47,7 +50,21 @@ public void AddSymbols(IEnumerable<IMethodSymbol> all)
/// </param>
public void AddOperations(IEnumerable<IInvocationOperation> all)
{
AddRange(operations, all);
AddRange(Operations, all);
}

/// <summary>
/// Adds a collection of Method Reference symbols to the bank.
/// </summary>
/// <param name="all">
/// The collection of method reference symbols to add.
/// </param>
public void AddReferenceSymbols(IEnumerable<IMethodSymbol> all)
{
lock (ReferenceSymbolSet)
{
ReferenceSymbolSet.UnionWith(all);
}
}

/// <summary>
Expand All @@ -56,9 +73,9 @@ public void AddOperations(IEnumerable<IInvocationOperation> all)
/// <returns>
/// An immutable array of all method symbols in the bank.
/// </returns>
public ImmutableArray<IMethodSymbol> GetAllSymbols()
public FrozenSet<IMethodSymbol> GetAllSymbols()
{
return ToImmutableArray(symbols);
return ToFrozenSet(Symbols);
}

/// <summary>
Expand All @@ -69,7 +86,18 @@ public ImmutableArray<IMethodSymbol> GetAllSymbols()
/// </returns>
public ImmutableArray<IInvocationOperation> GetAllOperations()
{
return ToImmutableArray(operations);
return ToImmutableArray(Operations);
}

/// <summary>
/// Gets all Method Reference symbols in the bank.
/// </summary>
/// <returns>
/// A frozen set of all Method Reference symbols in the bank.
/// </returns>
public FrozenSet<IMethodSymbol> GetAllReferenceSymbols()
{
return ToFrozenSet(ReferenceSymbolSet);
}

private static void AddRange<T>(List<T> a, IEnumerable<T> all)
Expand All @@ -87,4 +115,12 @@ private static ImmutableArray<T> ToImmutableArray<T>(IEnumerable<T> a)
return [.. a];
}
}

private static FrozenSet<T> ToFrozenSet<T>(IEnumerable<T> a)
{
lock (a)
{
return a.ToFrozenSet();
}
}
}
13 changes: 13 additions & 0 deletions TestSuite/Refactoring/TypeClassParameter/AnalyzerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ public AnalyzerTest()
public void Okay()
=> VerifyDiagnostic(ReadText("Okay"), Atmosphere.Default);

[TestMethod]
public void LocalFunctionOkay()
=> VerifyDiagnostic(ReadText("LocalFunctionOkay"), Atmosphere.Default);

[TestMethod]
public void GlobalOkay()
=> VerifyDiagnostic(
[
ReadText("ReferencedOkay"),
ReadText("ReferencingOkay"),
],
Atmosphere.Default);

[TestMethod]
public void Code()
=> Check("Code");
Expand Down
54 changes: 54 additions & 0 deletions TestSuite/Refactoring/TypeClassParameter/LocalFunctionOkay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#pragma warning disable CS8321

namespace StyleChecker.Test.Refactoring.TypeClassParameter;

using System;

public sealed class LocalFunctionOkay
{
public void IncludesStaticClass()
{
void Print(Type t)
{
Console.WriteLine(t.FullName);
}

Print(typeof(string));
Print(typeof(StaticClass));
}

public void NotAllArgumentsAreTypeof()
{
void Print(Type t)
{
Console.WriteLine(t.FullName);
}

Print(typeof(string));
Print(GetType());
}

public void NobodyInvokes()
{
void NeverUsed(Type t)
=> Console.WriteLine(t.FullName);
}

public void MethodRederence()
{
string ToName(Type t) => t.FullName;

void Print(Func<Type, string> toName, Type[] array)
{
foreach (var i in array)
{
Console.Write(toName(i));
}
}

var name = ToName(typeof(object));
Print(ToName, [typeof(string), typeof(int)]);
}

public static class StaticClass;
}
72 changes: 54 additions & 18 deletions TestSuite/Refactoring/TypeClassParameter/Okay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,85 @@ namespace StyleChecker.Test.Refactoring.TypeClassParameter;

public sealed class Okay
{
public void IncludesStaticClass()
public class IncludesStaticClass
{
void Print(Type t)
public void Print(Type t)
{
Console.WriteLine(t.FullName);
}

Print(typeof(string));
Print(typeof(StaticClass));
public void Invoke()
{
Print(typeof(string));
Print(typeof(StaticClass));
}
}

public void NotAllArgumentsAreTypeofLocalFunction()
public class NotAllArgumentsAreTypeof
{
void Print(Type t)
public void Print(Type t)
{
Console.WriteLine(t.FullName);
}

Print(typeof(string));
Print(GetType());
public void Invoke()
{
Print(typeof(string));
Print(GetType());
}
}

public void NobodyInvokesLocalFunction()
public void NobodyInvokes(Type type)
{
void NeverUsed(Type t)
=> Console.WriteLine(t.FullName);
Console.WriteLine(type.FullName);
}

public void NotAllArgumentsAreTypeof(Type type)
public class MethodReference
{
Console.WriteLine(type.FullName);
public string ToName(Type t)
=> t.FullName;

void Print(Func<Type, string> toName, Type[] array)
{
foreach (var i in array)
{
Console.Write(toName(i));
}
}

public void Invoke()
{
var name = ToName(typeof(object));
Print(ToName, [typeof(string), typeof(int)]);
}
}

public void NobodyInvokes(Type type)
public class InstanceMethodReference
{
Console.WriteLine(type.FullName);
public string ToName(Type t)
=> t.FullName;

public void Invoke()
{
var name = ToName(typeof(object));
}
}

public void Invoke()
private Func<Type, string> instanceAction
= new InstanceMethodReference().ToName;

public class StaticMethodReference
{
NotAllArgumentsAreTypeof(typeof(string));
NotAllArgumentsAreTypeof(GetType());
public static string ToName(Type t)
=> t.FullName;

public void Invoke()
{
var name = ToName(typeof(object));
}
}

private Func<Type, string> staticAction = StaticMethodReference.ToName;

public static class StaticClass;
}
21 changes: 21 additions & 0 deletions TestSuite/Refactoring/TypeClassParameter/ReferencedOkay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace StyleChecker.Test.Refactoring.TypeClassParameter;

using System;

public sealed class ReferencedOkay
{
private static void Log(string message)
{
}

/// <summary>
/// Print the specified type.
/// </summary>
/// <param name="type">
/// The type to be printed.
/// </param>
public static void PrintMethod(Type type)
{
Log(type.FullName);
}
}
19 changes: 19 additions & 0 deletions TestSuite/Refactoring/TypeClassParameter/ReferencingOkay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace StyleChecker.Test.Refactoring.TypeClassParameter;

public sealed class ReferencingOkay
{
public void CallWithStringType()
{
ReferencedOkay.PrintMethod(typeof(string));
}

public void CallWithIntType()
{
ReferencedOkay.PrintMethod(typeof(int));
}

public void MethodReference()
{
var method = ReferencedOkay.PrintMethod;
}
}
Loading