1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Linq ;
4+ using System . Text ;
5+ using System . Threading . Tasks ;
6+ using Interfaces ;
7+ using Microsoft . CodeAnalysis ;
8+ using Microsoft . CodeAnalysis . CSharp ;
9+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
10+ using Octokit ;
11+ using Storage . Remote . GitHub ;
12+
13+ namespace Platform . Bot . Triggers
14+ {
15+ using TContext = Issue ;
16+
17+ /// <summary>
18+ /// <para>
19+ /// Represents the generate XML comments stubs trigger.
20+ /// </para>
21+ /// <para></para>
22+ /// </summary>
23+ /// <seealso cref="ITrigger{TContext}"/>
24+ internal class GenerateXmlCommentsStubsTrigger : ITrigger < TContext >
25+ {
26+ private readonly GitHubStorage _storage ;
27+
28+ /// <summary>
29+ /// <para>
30+ /// Initializes a new <see cref="GenerateXmlCommentsStubsTrigger"/> instance.
31+ /// </para>
32+ /// <para></para>
33+ /// </summary>
34+ /// <param name="storage">
35+ /// <para>A GitHub storage.</para>
36+ /// <para></para>
37+ /// </param>
38+ public GenerateXmlCommentsStubsTrigger ( GitHubStorage storage )
39+ {
40+ _storage = storage ;
41+ }
42+
43+ /// <summary>
44+ /// <para>
45+ /// Determines whether this instance condition.
46+ /// </para>
47+ /// <para></para>
48+ /// </summary>
49+ /// <param name="context">
50+ /// <para>The context.</para>
51+ /// <para></para>
52+ /// </param>
53+ /// <returns>
54+ /// <para>The bool</para>
55+ /// <para></para>
56+ /// </returns>
57+ public async Task < bool > Condition ( TContext context )
58+ {
59+ var title = context . Title . ToLower ( ) ;
60+ var body = context . Body ? . ToLower ( ) ?? "" ;
61+
62+ return title . Contains ( "xml comment" ) || title . Contains ( "xml doc" ) ||
63+ body . Contains ( "xml comment" ) || body . Contains ( "xml doc" ) ||
64+ title . Contains ( "generate comments" ) || body . Contains ( "generate comments" ) ;
65+ }
66+
67+ /// <summary>
68+ /// <para>
69+ /// Actions the context.
70+ /// </para>
71+ /// <para></para>
72+ /// </summary>
73+ /// <param name="context">
74+ /// <para>The context.</para>
75+ /// <para></para>
76+ /// </param>
77+ public async Task Action ( TContext context )
78+ {
79+ try
80+ {
81+ var repository = context . Repository ;
82+ var branchName = $ "generate-xml-comments-{ DateTime . UtcNow : yyyyMMdd-HHmmss} ";
83+
84+ // Get all C# files in the repository
85+ var csharpFiles = await GetCSharpFilesFromRepository ( repository ) ;
86+
87+ var modifiedFiles = new List < ( string path , string content ) > ( ) ;
88+
89+ foreach ( var file in csharpFiles )
90+ {
91+ var modifiedContent = await GenerateXmlCommentsForFile ( file . content , file . path ) ;
92+ if ( modifiedContent != file . content )
93+ {
94+ modifiedFiles . Add ( ( file . path , modifiedContent ) ) ;
95+ }
96+ }
97+
98+ if ( modifiedFiles . Any ( ) )
99+ {
100+ // Create a new branch using Octokit directly
101+ await CreateBranch ( repository , branchName , repository . DefaultBranch ) ;
102+
103+ // Update files with XML comments
104+ foreach ( var ( path , content ) in modifiedFiles )
105+ {
106+ await _storage . CreateOrUpdateFile ( content , repository , branchName , path ,
107+ "Generate XML comment stubs for public members" ) ;
108+ }
109+
110+ // Create a pull request using Octokit directly
111+ var pullRequest = await CreatePullRequest ( repository ,
112+ title : "Generate XML comment stubs for public members" ,
113+ head : branchName ,
114+ baseRef : repository . DefaultBranch ,
115+ body : $ "This PR automatically generates XML comment stubs for all public members.\n \n Generated from issue #{ context . Number } ") ;
116+
117+ // Comment on the original issue
118+ await _storage . CreateIssueComment ( repository . Id , context . Number ,
119+ $ "XML comment stubs have been generated! Please review the pull request: #{ pullRequest . Number } ") ;
120+ }
121+ else
122+ {
123+ await _storage . CreateIssueComment ( repository . Id , context . Number ,
124+ "No C# files found or all public members already have XML comments." ) ;
125+ }
126+
127+ _storage . CloseIssue ( context ) ;
128+ }
129+ catch ( Exception ex )
130+ {
131+ await _storage . CreateIssueComment ( context . Repository . Id , context . Number ,
132+ $ "Error generating XML comments: { ex . Message } ") ;
133+ }
134+ }
135+
136+ private async Task < List < ( string path , string content ) > > GetCSharpFilesFromRepository ( Repository repository )
137+ {
138+ var files = new List < ( string path , string content ) > ( ) ;
139+ var contents = await _storage . Client . Repository . Content . GetAllContents ( repository . Id , "" ) ;
140+
141+ await CollectCSharpFiles ( repository , contents , "" , files ) ;
142+ return files ;
143+ }
144+
145+ private async Task CollectCSharpFiles ( Repository repository , IReadOnlyList < RepositoryContent > contents ,
146+ string currentPath , List < ( string path , string content ) > files )
147+ {
148+ foreach ( var item in contents )
149+ {
150+ var fullPath = string . IsNullOrEmpty ( currentPath ) ? item . Name : $ "{ currentPath } /{ item . Name } ";
151+
152+ if ( item . Type == ContentType . File && item . Name . EndsWith ( ".cs" ) )
153+ {
154+ var fileContents = await _storage . Client . Repository . Content . GetAllContents ( repository . Id , fullPath ) ;
155+ var fileContent = fileContents . First ( ) . Content ;
156+ files . Add ( ( fullPath , fileContent ) ) ;
157+ }
158+ else if ( item . Type == ContentType . Dir )
159+ {
160+ var subContents = await _storage . Client . Repository . Content . GetAllContents ( repository . Id , fullPath ) ;
161+ await CollectCSharpFiles ( repository , subContents , fullPath , files ) ;
162+ }
163+ }
164+ }
165+
166+ private async Task CreateBranch ( Repository repository , string branchName , string baseBranchName )
167+ {
168+ var baseBranch = await _storage . Client . Repository . Branch . Get ( repository . Id , baseBranchName ) ;
169+ var newReference = new NewReference ( $ "refs/heads/{ branchName } ", baseBranch . Commit . Sha ) ;
170+ await _storage . Client . Git . Reference . Create ( repository . Id , newReference ) ;
171+ }
172+
173+ private async Task < PullRequest > CreatePullRequest ( Repository repository , string title , string head , string baseRef , string body )
174+ {
175+ var newPullRequest = new NewPullRequest ( title , head , baseRef )
176+ {
177+ Body = body
178+ } ;
179+ return await _storage . Client . PullRequest . Create ( repository . Id , newPullRequest ) ;
180+ }
181+
182+ private async Task < string > GenerateXmlCommentsForFile ( string fileContent , string filePath )
183+ {
184+ try
185+ {
186+ var tree = CSharpSyntaxTree . ParseText ( fileContent ) ;
187+ var root = await tree . GetRootAsync ( ) ;
188+ var rewriter = new XmlCommentsRewriter ( ) ;
189+ var newRoot = rewriter . Visit ( root ) ;
190+
191+ return newRoot . ToFullString ( ) ;
192+ }
193+ catch
194+ {
195+ // If parsing fails, return original content
196+ return fileContent ;
197+ }
198+ }
199+ }
200+
201+ internal class XmlCommentsRewriter : CSharpSyntaxRewriter
202+ {
203+ public override SyntaxNode VisitClassDeclaration ( ClassDeclarationSyntax node )
204+ {
205+ if ( IsPublic ( node . Modifiers ) && ! HasXmlComment ( node ) )
206+ {
207+ var xmlComment = CreateXmlComment ( $ "Represents the { node . Identifier . ValueText . ToLower ( ) } .") ;
208+ node = node . WithLeadingTrivia ( node . GetLeadingTrivia ( ) . Insert ( 0 , xmlComment ) ) ;
209+ }
210+ return base . VisitClassDeclaration ( node ) ;
211+ }
212+
213+ public override SyntaxNode VisitMethodDeclaration ( MethodDeclarationSyntax node )
214+ {
215+ if ( IsPublic ( node . Modifiers ) && ! HasXmlComment ( node ) )
216+ {
217+ var xmlComment = GenerateMethodXmlComment ( node ) ;
218+ node = node . WithLeadingTrivia ( node . GetLeadingTrivia ( ) . Insert ( 0 , xmlComment ) ) ;
219+ }
220+ return base . VisitMethodDeclaration ( node ) ;
221+ }
222+
223+ public override SyntaxNode VisitPropertyDeclaration ( PropertyDeclarationSyntax node )
224+ {
225+ if ( IsPublic ( node . Modifiers ) && ! HasXmlComment ( node ) )
226+ {
227+ var xmlComment = CreateXmlComment ( $ "Gets or sets the { node . Identifier . ValueText . ToLower ( ) } .") ;
228+ node = node . WithLeadingTrivia ( node . GetLeadingTrivia ( ) . Insert ( 0 , xmlComment ) ) ;
229+ }
230+ return base . VisitPropertyDeclaration ( node ) ;
231+ }
232+
233+ public override SyntaxNode VisitConstructorDeclaration ( ConstructorDeclarationSyntax node )
234+ {
235+ if ( IsPublic ( node . Modifiers ) && ! HasXmlComment ( node ) )
236+ {
237+ var xmlComment = GenerateConstructorXmlComment ( node ) ;
238+ node = node . WithLeadingTrivia ( node . GetLeadingTrivia ( ) . Insert ( 0 , xmlComment ) ) ;
239+ }
240+ return base . VisitConstructorDeclaration ( node ) ;
241+ }
242+
243+ private static bool IsPublic ( SyntaxTokenList modifiers )
244+ {
245+ return modifiers . Any ( m => m . IsKind ( SyntaxKind . PublicKeyword ) ) ;
246+ }
247+
248+ private static bool HasXmlComment ( SyntaxNode node )
249+ {
250+ var leadingTrivia = node . GetLeadingTrivia ( ) ;
251+ return leadingTrivia . Any ( t => t . IsKind ( SyntaxKind . SingleLineDocumentationCommentTrivia ) ||
252+ t . IsKind ( SyntaxKind . MultiLineDocumentationCommentTrivia ) ) ;
253+ }
254+
255+ private static SyntaxTrivia CreateXmlComment ( string summary )
256+ {
257+ var commentText = $@ " /// <summary>
258+ /// <para>
259+ /// { summary }
260+ /// </para>
261+ /// <para></para>
262+ /// </summary>" ;
263+
264+ return SyntaxFactory . Comment ( commentText + Environment . NewLine ) ;
265+ }
266+
267+ private static SyntaxTrivia GenerateMethodXmlComment ( MethodDeclarationSyntax method )
268+ {
269+ var sb = new StringBuilder ( ) ;
270+ sb . AppendLine ( " /// <summary>" ) ;
271+ sb . AppendLine ( " /// <para>" ) ;
272+ sb . AppendLine ( $ " /// { GetMethodDescription ( method ) } ") ;
273+ sb . AppendLine ( " /// </para>" ) ;
274+ sb . AppendLine ( " /// <para></para>" ) ;
275+ sb . AppendLine ( " /// </summary>" ) ;
276+
277+ foreach ( var parameter in method . ParameterList . Parameters )
278+ {
279+ sb . AppendLine ( $ " /// <param name=\" { parameter . Identifier } \" >") ;
280+ sb . AppendLine ( " /// <para>The parameter.</para>" ) ;
281+ sb . AppendLine ( " /// <para></para>" ) ;
282+ sb . AppendLine ( " /// </param>" ) ;
283+ }
284+
285+ if ( ! method . ReturnType . ToString ( ) . Equals ( "void" , StringComparison . OrdinalIgnoreCase ) )
286+ {
287+ sb . AppendLine ( " /// <returns>" ) ;
288+ sb . AppendLine ( $ " /// <para>The { method . ReturnType . ToString ( ) . ToLower ( ) } </para>") ;
289+ sb . AppendLine ( " /// <para></para>" ) ;
290+ sb . AppendLine ( " /// </returns>" ) ;
291+ }
292+
293+ return SyntaxFactory . Comment ( sb . ToString ( ) ) ;
294+ }
295+
296+ private static SyntaxTrivia GenerateConstructorXmlComment ( ConstructorDeclarationSyntax constructor )
297+ {
298+ var sb = new StringBuilder ( ) ;
299+ sb . AppendLine ( " /// <summary>" ) ;
300+ sb . AppendLine ( " /// <para>" ) ;
301+ sb . AppendLine ( $ " /// Initializes a new <see cref=\" { constructor . Identifier } \" /> instance.") ;
302+ sb . AppendLine ( " /// </para>" ) ;
303+ sb . AppendLine ( " /// <para></para>" ) ;
304+ sb . AppendLine ( " /// </summary>" ) ;
305+
306+ foreach ( var parameter in constructor . ParameterList . Parameters )
307+ {
308+ sb . AppendLine ( $ " /// <param name=\" { parameter . Identifier } \" >") ;
309+ sb . AppendLine ( " /// <para>The parameter.</para>" ) ;
310+ sb . AppendLine ( " /// <para></para>" ) ;
311+ sb . AppendLine ( " /// </param>" ) ;
312+ }
313+
314+ return SyntaxFactory . Comment ( sb . ToString ( ) ) ;
315+ }
316+
317+ private static string GetMethodDescription ( MethodDeclarationSyntax method )
318+ {
319+ var methodName = method . Identifier . ValueText ;
320+
321+ if ( methodName . StartsWith ( "Get" ) )
322+ return $ "Gets the { methodName . Substring ( 3 ) . ToLower ( ) } .";
323+ if ( methodName . StartsWith ( "Set" ) )
324+ return $ "Sets the { methodName . Substring ( 3 ) . ToLower ( ) } .";
325+ if ( methodName . StartsWith ( "Create" ) )
326+ return $ "Creates the { methodName . Substring ( 6 ) . ToLower ( ) } .";
327+ if ( methodName . StartsWith ( "Update" ) )
328+ return $ "Updates the { methodName . Substring ( 6 ) . ToLower ( ) } .";
329+ if ( methodName . StartsWith ( "Delete" ) )
330+ return $ "Deletes the { methodName . Substring ( 6 ) . ToLower ( ) } .";
331+
332+ return $ "Performs the { methodName . ToLower ( ) } operation.";
333+ }
334+ }
335+ }
0 commit comments