1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 
8 using System.Collections.Generic;
9 using System.Collections.Immutable;
10 using System.Linq;
11 using Microsoft.CodeAnalysis;
12 using Microsoft.CodeAnalysis.CSharp;
13 using Microsoft.CodeAnalysis.CSharp.Syntax;
14 using Microsoft.CodeAnalysis.Diagnostics;
15 
16 namespace Antmicro.Renode.CustomAnalyzers
17 {
18     [DiagnosticAnalyzer(LanguageNames.CSharp)]
19     public class RenodeClassMembersOrderAnalyzer : DiagnosticAnalyzer
20     {
21         public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
22             ImmutableArray.Create(ClassMembersOrderRule);
23 
Initialize(AnalysisContext context)24         public override void Initialize(AnalysisContext context)
25         {
26             context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
27             context.EnableConcurrentExecution();
28             context.RegisterSyntaxTreeAction(CheckClassMembersOrder);
29         }
30 
CheckClassMembersOrder(SyntaxTreeAnalysisContext context)31         private void CheckClassMembersOrder(SyntaxTreeAnalysisContext context)
32         {
33             var classes = context
34                 .Tree
35                 .GetRoot()
36                 .DescendantNodes()
37                 .Where(node => node.IsKind(SyntaxKind.ClassDeclaration));
38 
39             foreach(var cls in classes)
40             {
41                 var declarations = cls
42                     .ChildNodes()
43                     .OfType<MemberDeclarationSyntax>();
44 
45                 var currentMember = ClassMemberOrder.StaticConstructor;
46 
47                 foreach(var declaration in declarations)
48                 {
49                     if(TryGetClassMemberOrder(declaration, out var nextMember))
50                     {
51                         if(nextMember < currentMember)
52                         {
53                             var diagnostic = Diagnostic.Create(
54                                 ClassMembersOrderRule,
55                                 declaration.GetLocation(),
56                                 new object[] {currentMember, nextMember}
57                             );
58                             context.ReportDiagnostic(diagnostic);
59                         }
60                         currentMember = nextMember;
61                     }
62                 }
63             }
64         }
65 
TryGetClassMemberOrder(MemberDeclarationSyntax node, out ClassMemberOrder classMemberOrder)66         private static bool TryGetClassMemberOrder(MemberDeclarationSyntax node, out ClassMemberOrder classMemberOrder)
67         {
68             var modifiers = node
69                 .Modifiers
70                 .Aggregate("", (acc, val) => $"{acc}_{val}");
71             var id = $"{modifiers}_{node.Kind()}";
72 
73             if(ClassMembersOrderMap.TryGetValue(id, out classMemberOrder))
74             {
75                 return true;
76             }
77             return false;
78         }
79 
80         private static readonly DiagnosticDescriptor ClassMembersOrderRule = new DiagnosticDescriptor(
81             "renode_class_members_order",
82             "Class Members Order",
83             "Wrong Class Members Order: {1} should be placed before {0}",
84             "Design",
85             DiagnosticSeverity.Warning,
86             isEnabledByDefault: false,
87             description: "Checks if class members order is preserved."
88         );
89 
90 
91 #pragma warning disable SA1509
92         // SA1509 - No space before opening braces
93         private static readonly Dictionary<string, ClassMemberOrder> ClassMembersOrderMap = new Dictionary<string, ClassMemberOrder>
94         {
95             {"_static_ConstructorDeclaration",                      ClassMemberOrder.StaticConstructor},
96             {"_public_static_MethodDeclaration",                    ClassMemberOrder.PublicStaticMethod},
97             {"_public_static_PropertyDeclaration",                  ClassMemberOrder.PublicStaticProperty},
98             {"_public_static_explicit_OperatorDeclaration",         ClassMemberOrder.PublicStaticExplicitOperator},
99             {"_public_static_implicit_OperatorDeclaration",         ClassMemberOrder.PublicStaticImplicitOperator},
100             {"_public_static_FieldDeclaration",                     ClassMemberOrder.PublicStaticField},
101             {"_public_ConstructorDeclaration",                      ClassMemberOrder.PublicConstructor},
102             {"_public_MethodDeclaration",                           ClassMemberOrder.PublicMethod},
103             {"_public_abstract_MethodDeclaration",                  ClassMemberOrder.PublicAbstractMethod},
104             {"_public_PropertyDeclaration",                         ClassMemberOrder.PublicProperty},
105             {"_public_EventDeclaration",                            ClassMemberOrder.PublicEvent},
106             {"_public_readonly_FieldDeclaration",                   ClassMemberOrder.PublicReadonlyField},
107             {"_public_ConstDeclaration",                            ClassMemberOrder.PublicConst},
108 
109             {"_protected_static_MethodDeclaration",                 ClassMemberOrder.ProtectedStaticMethod},
110             {"_protected_static_PropertyDeclaration",               ClassMemberOrder.ProtectedStaticProperty},
111             {"_protected_static_explicit_OperatorDeclaration",      ClassMemberOrder.ProtectedStaticExplicitOperator},
112             {"_protected_static_implicit_OperatorDeclaration",      ClassMemberOrder.ProtectedStaticImplicitOperator},
113             {"_protected_static_FieldDeclaration",                  ClassMemberOrder.ProtectedStaticField},
114             {"_protected_ConstructorDeclaration",                   ClassMemberOrder.ProtectedConstructor},
115             {"_protected_MethodDeclaration",                        ClassMemberOrder.ProtectedMethod},
116             {"_protected_PropertyDeclaration",                      ClassMemberOrder.ProtectedProperty},
117             {"_protected_EventDeclaration",                         ClassMemberOrder.ProtectedEvent},
118             {"_protected_ConstDeclaration",                         ClassMemberOrder.ProtectedConst},
119 
120             {"_private_static_MethodDeclaration",                   ClassMemberOrder.PrivateStaticMethod},
121             {"_private_static_FieldDeclaration",                    ClassMemberOrder.PrivateStaticField},
122             {"_private_ConstructorDeclaration",                     ClassMemberOrder.PrivateConstructor},
123             {"_private_MethodDeclaration",                          ClassMemberOrder.PrivateMethod},
124             {"_private_PropertyDeclaration",                        ClassMemberOrder.PrivateProperty},
125             {"_private_FieldDeclaration",                           ClassMemberOrder.PrivateField},
126             {"_private_ConstDeclaration",                           ClassMemberOrder.PrivateConst},
127 
128             {"_public_ClassDeclaration",                            ClassMemberOrder.PublicClass},
129             {"_public_DelegateDeclaration",                         ClassMemberOrder.PublicDelegate},
130             {"_public_EnumDeclaration",                             ClassMemberOrder.PublicEnum},
131 
132             {"_protected_ClassDeclaration",                         ClassMemberOrder.ProtectedClass},
133             {"_protected_DelegateDeclaration",                      ClassMemberOrder.ProtectedDelegate},
134             {"_protected_EnumDeclaration",                          ClassMemberOrder.ProtectedEnum},
135 
136             {"_private_ClassDeclaration",                           ClassMemberOrder.PrivateClass},
137             {"_private_DelegateDeclaration",                        ClassMemberOrder.PrivateDelegate},
138             {"_private_EnumDeclaration",                            ClassMemberOrder.PrivateEnum},
139         };
140 #pragma warning restore SA1509
141 
142         // Order of fileds in this Enum is important.
143         // It specifies allowed order of class members.
144         private enum ClassMemberOrder
145         {
146             StaticConstructor,
147             PublicStaticMethod,
148             PublicStaticProperty,
149             PublicStaticExplicitOperator,
150             PublicStaticImplicitOperator,
151             PublicStaticField,
152             PublicConstructor,
153             PublicMethod,
154             PublicAbstractMethod,
155             PublicProperty,
156             PublicEvent,
157             PublicReadonlyField,
158             PublicConst,
159             ProtectedStaticMethod,
160             ProtectedStaticProperty,
161             ProtectedStaticExplicitOperator,
162             ProtectedStaticImplicitOperator,
163             ProtectedStaticField,
164             ProtectedConstructor,
165             ProtectedMethod,
166             ProtectedProperty,
167             ProtectedEvent,
168             ProtectedConst,
169             PrivateStaticMethod,
170             PrivateStaticField,
171             PrivateConstructor,
172             PrivateMethod,
173             PrivateProperty,
174             PrivateField,
175             PrivateConst,
176             PublicClass,
177             PublicDelegate,
178             PublicEnum,
179             ProtectedClass,
180             ProtectedDelegate,
181             ProtectedEnum,
182             PrivateClass,
183             PrivateDelegate,
184             PrivateEnum,
185         }
186     }
187 }
188