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;
9 using System.Linq;
10 using System.Collections.Immutable;
11 using System.Text.RegularExpressions;
12 using Microsoft.CodeAnalysis;
13 using Microsoft.CodeAnalysis.CSharp;
14 using Microsoft.CodeAnalysis.Diagnostics;
15 
16 namespace Antmicro.Renode.CustomAnalyzers
17 {
18     [DiagnosticAnalyzer(LanguageNames.CSharp)]
19     public class RenodeCopyrightAnalyzer : DiagnosticAnalyzer
20     {
21         public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
22             ImmutableArray.Create(CopyrightDateRule, CopyrightFormatRule);
23 
Initialize(AnalysisContext context)24         public override void Initialize(AnalysisContext context)
25         {
26             context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
27             context.EnableConcurrentExecution();
28             context.RegisterSyntaxTreeAction(CheckCopyrights);
29         }
30 
CheckCopyrights(SyntaxTreeAnalysisContext context)31         private void CheckCopyrights(SyntaxTreeAnalysisContext context)
32         {
33             var comments = context
34                 .Tree
35                 .GetRoot()
36                 .DescendantTrivia()
37                 .Where(node => node.IsKind(SyntaxKind.SingleLineCommentTrivia))
38                 .ToArray();
39 
40             var commentIdx = 0;
41             for(var templateIdx = 0; templateIdx < CopyrightTemplate.Length; ++templateIdx)
42             {
43                 // We don't have any more comments to check
44                 if(commentIdx >= comments.Length)
45                 {
46                     context.ReportDiagnostic(Diagnostic.Create(CopyrightFormatRule, location: null));
47                     return;
48                 }
49 
50                 var comment = comments[commentIdx];
51                 var content = comment.ToString();
52                 var commentLine = comment
53                     .GetLocation()
54                     .GetLineSpan()
55                     .StartLinePosition
56                     .Line;
57 
58                 // copyright line might be correct, but it's on wrong line
59                 if(commentLine != commentIdx)
60                 {
61                     context.ReportDiagnostic(Diagnostic.Create(CopyrightFormatRule, location: null));
62                 }
63 
64                 switch(templateIdx)
65                 {
66                 case AntmicroCopyrightLine:
67                     CheckAntmicroCopyrights(context, comment);
68                     break;
69                 case AdditionalCopyrightLine:
70                     if(CheckAdditionalCopyrights(comment))
71                     {
72                         // We found additional copyright, but we adjust templateIdx
73                         // because we don't know how many there will be.
74                         templateIdx--;
75                     }
76                     else
77                     {
78                         // We end up pointing to the line after additional copyrights.
79                         // Let's adjust pointer to not skip any line.
80                         commentIdx--;
81                     }
82                     break;
83                 default:
84                     if(content != CopyrightTemplate[templateIdx])
85                     {
86                         context.ReportDiagnostic(Diagnostic.Create(CopyrightFormatRule, comment.GetLocation()));
87                     }
88                     break;
89                 }
90                 commentIdx++;
91             }
92         }
93 
CheckAntmicroCopyrights(SyntaxTreeAnalysisContext context, SyntaxTrivia commentNode)94         private void CheckAntmicroCopyrights(SyntaxTreeAnalysisContext context, SyntaxTrivia commentNode)
95         {
96             var content = commentNode.ToString();
97             var regex = new Regex(CopyrightTemplate[AntmicroCopyrightLine]);
98             var match = regex.Match(content);
99             if(match.Success)
100             {
101                 var copyrightYear = match.Groups[1].Value;
102                 var currentYear = DateTime.Today.Year.ToString();
103                 if(copyrightYear != currentYear)
104                 {
105                     context.ReportDiagnostic(Diagnostic.Create(CopyrightDateRule, commentNode.GetLocation()));
106                 }
107             }
108             else
109             {
110                 context.ReportDiagnostic(Diagnostic.Create(CopyrightFormatRule, commentNode.GetLocation()));
111             }
112         }
113 
CheckAdditionalCopyrights(SyntaxTrivia commentNode)114         private bool CheckAdditionalCopyrights(SyntaxTrivia commentNode)
115         {
116             var content = commentNode.ToString();
117             var regex = new Regex(CopyrightTemplate[AdditionalCopyrightLine]);
118             var match = regex.Match(content);
119             return match.Success;
120         }
121 
122         private static readonly DiagnosticDescriptor CopyrightDateRule = new DiagnosticDescriptor(
123             "renode_copyright",
124             "Copyright Date",
125             "Copyright is not up to date",
126             "License",
127             DiagnosticSeverity.Warning,
128             isEnabledByDefault: false,
129             description: "Ensure that copyrights are up to date."
130         );
131 
132         private static readonly DiagnosticDescriptor CopyrightFormatRule = new DiagnosticDescriptor(
133             "renode_copyright",
134             "Copyright Format",
135             "Wrong copyright format",
136             "License",
137             DiagnosticSeverity.Warning,
138             isEnabledByDefault: false,
139             description: "Ensure that copyrights are in correct format."
140         );
141 
142         private static readonly string[] CopyrightTemplate = {
143             "//",
144             @"// Copyright \(c\) 2010-(\d+) Antmicro",
145             @"// Copyright \(c\) .*",
146             "//",
147             "// This file is licensed under the MIT License.",
148             "// Full license text is available in 'licenses/MIT.txt'.",
149             "//"
150         };
151 
152         private const int AntmicroCopyrightLine = 1;
153         private const int AdditionalCopyrightLine = 2;
154     }
155 }
156