Skip to content

Commit 1d7bfae

Browse files
authored
Merge pull request #4 from delegateas/copilot/fix-3
Add CT0002 rule to warn when enum properties are assigned literal values
2 parents 7cf1afd + 63b9b2a commit 1d7bfae

4 files changed

Lines changed: 436 additions & 0 deletions

File tree

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using System.Collections.Immutable;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
using Microsoft.CodeAnalysis.Diagnostics;
6+
7+
namespace DataverseAnalyzer;
8+
9+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
10+
public sealed class EnumAssignmentAnalyzer : DiagnosticAnalyzer
11+
{
12+
public static readonly DiagnosticDescriptor Rule = new(
13+
"CT0002",
14+
Resources.CT0002_Title,
15+
Resources.CT0002_MessageFormat,
16+
"Usage",
17+
DiagnosticSeverity.Warning,
18+
isEnabledByDefault: true,
19+
description: Resources.CT0002_Description);
20+
21+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
22+
23+
public override void Initialize(AnalysisContext context)
24+
{
25+
ArgumentNullException.ThrowIfNull(context);
26+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
27+
context.EnableConcurrentExecution();
28+
context.RegisterSyntaxNodeAction(AnalyzeAssignmentExpression, SyntaxKind.SimpleAssignmentExpression);
29+
context.RegisterSyntaxNodeAction(AnalyzePropertyDeclaration, SyntaxKind.PropertyDeclaration);
30+
}
31+
32+
private static void AnalyzeAssignmentExpression(SyntaxNodeAnalysisContext context)
33+
{
34+
var assignment = (AssignmentExpressionSyntax)context.Node;
35+
AnalyzeEnumAssignment(context, assignment.Left, assignment.Right);
36+
}
37+
38+
private static void AnalyzePropertyDeclaration(SyntaxNodeAnalysisContext context)
39+
{
40+
var property = (PropertyDeclarationSyntax)context.Node;
41+
if (property.Initializer is not null)
42+
{
43+
AnalyzeEnumAssignmentForProperty(context, property, property.Initializer.Value);
44+
}
45+
}
46+
47+
private static void AnalyzeEnumAssignmentForProperty(SyntaxNodeAnalysisContext context, PropertyDeclarationSyntax property, ExpressionSyntax right)
48+
{
49+
// Check if the right side is a numeric literal
50+
if (right is not LiteralExpressionSyntax literal || !IsNumericLiteral(literal))
51+
{
52+
return;
53+
}
54+
55+
// Get the type of the property from its declaration
56+
var propertyTypeInfo = context.SemanticModel.GetTypeInfo(property.Type);
57+
var targetType = propertyTypeInfo.Type;
58+
59+
// Handle nullable enum types
60+
if (targetType is null)
61+
{
62+
return;
63+
}
64+
65+
if (targetType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
66+
{
67+
targetType = ((INamedTypeSymbol)targetType).TypeArguments[0];
68+
}
69+
70+
// Check if the target type is an enum
71+
if (targetType.TypeKind != TypeKind.Enum)
72+
{
73+
return;
74+
}
75+
76+
var targetName = property.Identifier.ValueText;
77+
var literalValue = literal.Token.ValueText;
78+
79+
var diagnostic = Diagnostic.Create(Rule, right.GetLocation(), targetName, literalValue);
80+
context.ReportDiagnostic(diagnostic);
81+
}
82+
83+
private static void AnalyzeEnumAssignment(SyntaxNodeAnalysisContext context, SyntaxNode left, ExpressionSyntax right)
84+
{
85+
// Check if the right side is a numeric literal
86+
if (right is not LiteralExpressionSyntax literal || !IsNumericLiteral(literal))
87+
{
88+
return;
89+
}
90+
91+
// Get the type of the left side
92+
var leftTypeInfo = context.SemanticModel.GetTypeInfo(left);
93+
var targetType = leftTypeInfo.Type;
94+
95+
// Handle nullable enum types
96+
if (targetType is null)
97+
{
98+
return;
99+
}
100+
101+
if (targetType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
102+
{
103+
targetType = ((INamedTypeSymbol)targetType).TypeArguments[0];
104+
}
105+
106+
// Check if the target type is an enum
107+
if (targetType.TypeKind != TypeKind.Enum)
108+
{
109+
return;
110+
}
111+
112+
var targetName = GetTargetName(left);
113+
var literalValue = literal.Token.ValueText;
114+
115+
var diagnostic = Diagnostic.Create(Rule, right.GetLocation(), targetName, literalValue);
116+
context.ReportDiagnostic(diagnostic);
117+
}
118+
119+
private static bool IsNumericLiteral(LiteralExpressionSyntax literal)
120+
{
121+
return literal.Token.IsKind(SyntaxKind.NumericLiteralToken);
122+
}
123+
124+
private static string GetTargetName(SyntaxNode left)
125+
{
126+
return left switch
127+
{
128+
PropertyDeclarationSyntax property => property.Identifier.ValueText,
129+
IdentifierNameSyntax identifier => identifier.Identifier.ValueText,
130+
MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.ValueText,
131+
_ => "property",
132+
};
133+
}
134+
}

src/DataverseAnalyzer/Resources.Designer.cs

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/DataverseAnalyzer/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,13 @@
7070
<data name="CT0001_CodeFix_Title" xml:space="preserve">
7171
<value>Add braces</value>
7272
</data>
73+
<data name="CT0002_Title" xml:space="preserve">
74+
<value>Enum properties should not be assigned literal values</value>
75+
</data>
76+
<data name="CT0002_MessageFormat" xml:space="preserve">
77+
<value>Enum property '{0}' should not be assigned literal value '{1}'. Use the appropriate enum value instead</value>
78+
</data>
79+
<data name="CT0002_Description" xml:space="preserve">
80+
<value>Enum properties should be assigned enum values rather than literal numeric values to improve code readability and maintainability.</value>
81+
</data>
7382
</root>

0 commit comments

Comments
 (0)