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+ }
0 commit comments