Skip to content

Commit 23b6d67

Browse files
authored
Fix: validate supported enum overloads (#1870)
* Validate supported enum overloads * Use nameof for supported enum methods
1 parent 46272eb commit 23b6d67

2 files changed

Lines changed: 161 additions & 10 deletions

File tree

src/Neo.SmartContract.Analyzer/EnumMethodsUsageAnalyzer.cs

Lines changed: 125 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
using Microsoft.CodeAnalysis.CSharp;
1414
using Microsoft.CodeAnalysis.CSharp.Syntax;
1515
using Microsoft.CodeAnalysis.Diagnostics;
16+
using System;
1617
using System.Collections.Immutable;
17-
using System.Linq;
1818

1919
namespace Neo.SmartContract.Analyzer
2020
{
@@ -23,11 +23,6 @@ public class EnumMethodsUsageAnalyzer : DiagnosticAnalyzer
2323
{
2424
public const string DiagnosticId = "NC4025";
2525

26-
private readonly string[] _unsupportedEnumMethods = {
27-
"Format",
28-
"GetUnderlyingType"
29-
};
30-
3126
private static readonly DiagnosticDescriptor Rule = new(
3227
DiagnosticId,
3328
"Unsupported Enum method is used",
@@ -50,16 +45,136 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
5045
{
5146
if (context.Node is not InvocationExpressionSyntax invocationExpression) return;
5247

53-
var methodSymbol = context.SemanticModel.GetSymbolInfo(invocationExpression).Symbol as IMethodSymbol;
54-
55-
if (methodSymbol is not { ContainingType.SpecialType: SpecialType.System_Enum } ||
56-
!_unsupportedEnumMethods.Contains(methodSymbol.Name)) return;
48+
if (context.SemanticModel.GetSymbolInfo(invocationExpression).Symbol is not IMethodSymbol methodSymbol ||
49+
methodSymbol.ContainingType.SpecialType != SpecialType.System_Enum ||
50+
IsSupportedEnumMethod(methodSymbol))
51+
return;
5752

5853
var diagnostic = Diagnostic.Create(Rule,
5954
invocationExpression.GetLocation(),
6055
methodSymbol.Name);
6156

6257
context.ReportDiagnostic(diagnostic);
6358
}
59+
60+
private static bool IsSupportedEnumMethod(IMethodSymbol method)
61+
{
62+
if (!method.IsStatic)
63+
{
64+
return method.Name switch
65+
{
66+
nameof(Enum.ToString) => method.Parameters.Length == 0,
67+
nameof(Enum.HasFlag) => IsSpecialParameter(method, 0, SpecialType.System_Enum) &&
68+
method.Parameters.Length == 1,
69+
_ => false
70+
};
71+
}
72+
73+
if (method.IsGenericMethod)
74+
return IsSupportedGenericMethod(method);
75+
76+
return method.Name switch
77+
{
78+
nameof(Enum.Parse) => IsParseSignature(method),
79+
nameof(Enum.TryParse) => IsTryParseSignature(method),
80+
nameof(Enum.GetNames) or nameof(Enum.GetValues) => method.Parameters.Length == 1 && IsSystemTypeParameter(method, 0),
81+
nameof(Enum.IsDefined) => method.Parameters.Length == 2 &&
82+
IsSystemTypeParameter(method, 0) &&
83+
(IsSpecialParameter(method, 1, SpecialType.System_Object) ||
84+
IsSpecialParameter(method, 1, SpecialType.System_String)),
85+
nameof(Enum.GetName) => method.Parameters.Length == 2 &&
86+
IsSystemTypeParameter(method, 0) &&
87+
IsSpecialParameter(method, 1, SpecialType.System_Object),
88+
_ => false
89+
};
90+
}
91+
92+
private static bool IsSupportedGenericMethod(IMethodSymbol method)
93+
{
94+
if (method.TypeArguments.Length != 1)
95+
return false;
96+
97+
var enumType = method.TypeArguments[0];
98+
return method.Name switch
99+
{
100+
nameof(Enum.Parse) => IsGenericParseSignature(method),
101+
nameof(Enum.TryParse) => IsGenericTryParseSignature(method, enumType),
102+
nameof(Enum.GetNames) or nameof(Enum.GetValues) => method.Parameters.Length == 0,
103+
nameof(Enum.GetName) => method.Parameters.Length == 1 &&
104+
IsParameter(method, 0, enumType),
105+
_ => false
106+
};
107+
}
108+
109+
private static bool IsParseSignature(IMethodSymbol method)
110+
{
111+
return method.Parameters.Length is 2 or 3 &&
112+
IsSystemTypeParameter(method, 0) &&
113+
IsSpecialParameter(method, 1, SpecialType.System_String) &&
114+
(method.Parameters.Length == 2 ||
115+
IsSpecialParameter(method, 2, SpecialType.System_Boolean));
116+
}
117+
118+
private static bool IsTryParseSignature(IMethodSymbol method)
119+
{
120+
return method.Parameters.Length is 3 or 4 &&
121+
IsSystemTypeParameter(method, 0) &&
122+
IsSpecialParameter(method, 1, SpecialType.System_String) &&
123+
(method.Parameters.Length == 3 ||
124+
IsSpecialParameter(method, 2, SpecialType.System_Boolean)) &&
125+
IsSpecialParameter(
126+
method,
127+
method.Parameters.Length - 1,
128+
SpecialType.System_Object,
129+
RefKind.Out);
130+
}
131+
132+
private static bool IsGenericParseSignature(IMethodSymbol method)
133+
{
134+
return method.Parameters.Length is 1 or 2 &&
135+
IsSpecialParameter(method, 0, SpecialType.System_String) &&
136+
(method.Parameters.Length == 1 ||
137+
IsSpecialParameter(method, 1, SpecialType.System_Boolean));
138+
}
139+
140+
private static bool IsGenericTryParseSignature(IMethodSymbol method, ITypeSymbol enumType)
141+
{
142+
return method.Parameters.Length is 2 or 3 &&
143+
IsSpecialParameter(method, 0, SpecialType.System_String) &&
144+
(method.Parameters.Length == 2 ||
145+
IsSpecialParameter(method, 1, SpecialType.System_Boolean)) &&
146+
IsParameter(method, method.Parameters.Length - 1, enumType, RefKind.Out);
147+
}
148+
149+
private static bool IsSystemTypeParameter(IMethodSymbol method, int index)
150+
{
151+
if (index >= method.Parameters.Length || method.Parameters[index].RefKind != RefKind.None)
152+
return false;
153+
154+
return method.Parameters[index].Type is INamedTypeSymbol { Name: "Type" } type &&
155+
type.ContainingNamespace.ToDisplayString() == "System";
156+
}
157+
158+
private static bool IsSpecialParameter(
159+
IMethodSymbol method,
160+
int index,
161+
SpecialType type,
162+
RefKind refKind = RefKind.None)
163+
{
164+
return index < method.Parameters.Length &&
165+
method.Parameters[index].RefKind == refKind &&
166+
method.Parameters[index].Type.SpecialType == type;
167+
}
168+
169+
private static bool IsParameter(
170+
IMethodSymbol method,
171+
int index,
172+
ITypeSymbol type,
173+
RefKind refKind = RefKind.None)
174+
{
175+
return index < method.Parameters.Length &&
176+
method.Parameters[index].RefKind == refKind &&
177+
SymbolEqualityComparer.Default.Equals(method.Parameters[index].Type, type);
178+
}
64179
}
65180
}

tests/Neo.SmartContract.Analyzer.UnitTests/EnumMethodsUsageAnalyzerUnitTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,51 @@ class TestClass
7272
void TestMethod()
7373
{
7474
var parsed = Enum.Parse(typeof(DayOfWeek), ""Monday"");
75+
var parsedIgnoreCase = Enum.Parse(typeof(DayOfWeek), ""monday"", true);
7576
var tryParsed = Enum.TryParse(typeof(DayOfWeek), ""Tuesday"", out var result);
77+
var tryParsedIgnoreCase = Enum.TryParse(typeof(DayOfWeek), ""tuesday"", true, out var result2);
7678
var names = Enum.GetNames(typeof(DayOfWeek));
7779
var values = Enum.GetValues(typeof(DayOfWeek));
7880
var isDefined = Enum.IsDefined(typeof(DayOfWeek), ""Wednesday"");
81+
var isDefinedValue = Enum.IsDefined(typeof(DayOfWeek), DayOfWeek.Wednesday);
7982
var name = Enum.GetName(typeof(DayOfWeek), DayOfWeek.Thursday);
83+
var genericParsed = Enum.Parse<DayOfWeek>(""Friday"");
84+
var genericParsedIgnoreCase = Enum.Parse<DayOfWeek>(""friday"", true);
85+
var genericTryParsed = Enum.TryParse<DayOfWeek>(""Saturday"", out var genericResult);
86+
var genericTryParsedIgnoreCase = Enum.TryParse<DayOfWeek>(""saturday"", true, out var genericResult2);
87+
var text = DayOfWeek.Monday.ToString();
88+
var hasFlag = DayOfWeek.Monday.HasFlag(DayOfWeek.Monday);
8089
}
8190
}";
8291

8392
await VerifyCS.VerifyAnalyzerAsync(test);
8493
}
94+
95+
[TestMethod]
96+
public async Task UnsupportedEnumOverloads_ReportDiagnostics()
97+
{
98+
var test = """
99+
using System;
100+
101+
class TestClass
102+
{
103+
void TestMethod()
104+
{
105+
_ = {|#0:Enum.ToObject(typeof(DayOfWeek), 1)|};
106+
_ = {|#1:DayOfWeek.Monday.ToString("G")|};
107+
_ = {|#2:DayOfWeek.Monday.CompareTo(DayOfWeek.Tuesday)|};
108+
}
109+
}
110+
""";
111+
112+
var expected = new[]
113+
{
114+
VerifyCS.Diagnostic(DiagnosticId).WithLocation(0).WithArguments("ToObject"),
115+
VerifyCS.Diagnostic(DiagnosticId).WithLocation(1).WithArguments("ToString"),
116+
VerifyCS.Diagnostic(DiagnosticId).WithLocation(2).WithArguments("CompareTo")
117+
};
118+
119+
await VerifyCS.VerifyAnalyzerAsync(test, expected);
120+
}
85121
}
86122
}

0 commit comments

Comments
 (0)