Skip to content

Commit 2e8149f

Browse files
committed
Added support for <summary> XML docs for generated commands
1 parent 5a08c7a commit 2e8149f

File tree

3 files changed

+51
-8
lines changed

3 files changed

+51
-8
lines changed

Microsoft.Toolkit.Mvvm.SourceGenerators/Input/ICommandGenerator.SyntaxReceiver.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ private sealed class SyntaxReceiver : ISyntaxContextReceiver
2222
/// <summary>
2323
/// The list of info gathered during exploration.
2424
/// </summary>
25-
private readonly List<IMethodSymbol> gatheredInfo = new();
25+
private readonly List<Item> gatheredInfo = new();
2626

2727
/// <summary>
2828
/// Gets the collection of gathered info to process.
2929
/// </summary>
30-
public IReadOnlyCollection<IMethodSymbol> GatheredInfo => this.gatheredInfo;
30+
public IReadOnlyCollection<Item> GatheredInfo => this.gatheredInfo;
3131

3232
/// <inheritdoc/>
3333
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
@@ -37,9 +37,16 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
3737
context.SemanticModel.Compilation.GetTypeByMetadataName(typeof(ICommandAttribute).FullName) is INamedTypeSymbol iCommandSymbol &&
3838
methodSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, iCommandSymbol)))
3939
{
40-
this.gatheredInfo.Add(methodSymbol);
40+
this.gatheredInfo.Add(new Item(methodDeclaration.GetLeadingTrivia(), methodSymbol));
4141
}
4242
}
43+
44+
/// <summary>
45+
/// A model for a group of item representing a discovered type to process.
46+
/// </summary>
47+
/// <param name="LeadingTrivia">The leading trivia for the field declaration.</param>
48+
/// <param name="MethodSymbol">The <see cref="IMethodSymbol"/> instance for the target method.</param>
49+
public sealed record Item(SyntaxTriviaList LeadingTrivia, IMethodSymbol MethodSymbol);
4350
}
4451
}
4552
}

Microsoft.Toolkit.Mvvm.SourceGenerators/Input/ICommandGenerator.cs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Diagnostics.Contracts;
99
using System.Linq;
1010
using System.Text;
11+
using System.Text.RegularExpressions;
1112
using Microsoft.CodeAnalysis;
1213
using Microsoft.CodeAnalysis.CSharp;
1314
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -43,7 +44,7 @@ public void Execute(GeneratorExecutionContext context)
4344
return;
4445
}
4546

46-
foreach (var items in syntaxReceiver.GatheredInfo.GroupBy<IMethodSymbol, INamedTypeSymbol>(static item => item.ContainingType, SymbolEqualityComparer.Default))
47+
foreach (var items in syntaxReceiver.GatheredInfo.GroupBy<SyntaxReceiver.Item, INamedTypeSymbol>(static item => item.MethodSymbol.ContainingType, SymbolEqualityComparer.Default))
4748
{
4849
if (items.Key.DeclaringSyntaxReferences.Length > 0 &&
4950
items.Key.DeclaringSyntaxReferences.First().GetSyntax() is ClassDeclarationSyntax classDeclaration)
@@ -66,12 +67,12 @@ public void Execute(GeneratorExecutionContext context)
6667
/// <param name="context">The input <see cref="GeneratorExecutionContext"/> instance to use.</param>
6768
/// <param name="classDeclaration">The <see cref="ClassDeclarationSyntax"/> node to process.</param>
6869
/// <param name="classDeclarationSymbol">The <see cref="INamedTypeSymbol"/> for <paramref name="classDeclaration"/>.</param>
69-
/// <param name="methodSymbols">The sequence of <see cref="IMethodSymbol"/> instances to process.</param>
70+
/// <param name="items">The sequence of <see cref="IMethodSymbol"/> instances to process.</param>
7071
private static void OnExecute(
7172
GeneratorExecutionContext context,
7273
ClassDeclarationSyntax classDeclaration,
7374
INamedTypeSymbol classDeclarationSymbol,
74-
IEnumerable<IMethodSymbol> methodSymbols)
75+
IEnumerable<SyntaxReceiver.Item> items)
7576
{
7677
// Create the class declaration for the user type. This will produce a tree as follows:
7778
//
@@ -82,7 +83,7 @@ private static void OnExecute(
8283
var classDeclarationSyntax =
8384
ClassDeclaration(classDeclarationSymbol.Name)
8485
.WithModifiers(classDeclaration.Modifiers)
85-
.AddMembers(methodSymbols.Select(item => CreateCommandMembers(context, default, item)).SelectMany(static g => g).ToArray());
86+
.AddMembers(items.Select(item => CreateCommandMembers(context, item.LeadingTrivia, item.MethodSymbol)).SelectMany(static g => g).ToArray());
8687

8788
TypeDeclarationSyntax typeDeclarationSyntax = classDeclarationSyntax;
8889

@@ -158,6 +159,27 @@ private static IEnumerable<MemberDeclarationSyntax> CreateCommandMembers(Generat
158159
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ICommandGenerator).FullName))),
159160
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ICommandGenerator).Assembly.GetName().Version.ToString())))))));
160161

162+
SyntaxTriviaList summaryTrivia = SyntaxTriviaList.Empty;
163+
164+
// Parse the <summary> docs, if present
165+
foreach (SyntaxTrivia trivia in leadingTrivia)
166+
{
167+
if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia) ||
168+
trivia.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia))
169+
{
170+
string text = trivia.ToString();
171+
172+
Match match = Regex.Match(text, @"<summary>.*?<\/summary>", RegexOptions.Singleline);
173+
174+
if (match.Success)
175+
{
176+
summaryTrivia = TriviaList(Comment($"/// {match.Value}"));
177+
178+
break;
179+
}
180+
}
181+
}
182+
161183
// Construct the generated property as follows (the explicit delegate cast is needed to avoid overload resolution conflicts):
162184
//
163185
// <METHOD_SUMMARY>
@@ -175,7 +197,8 @@ private static IEnumerable<MemberDeclarationSyntax> CreateCommandMembers(Generat
175197
Attribute(IdentifierName("global::System.CodeDom.Compiler.GeneratedCode"))
176198
.AddArgumentListArguments(
177199
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ICommandGenerator).FullName))),
178-
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ICommandGenerator).Assembly.GetName().Version.ToString())))))),
200+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ICommandGenerator).Assembly.GetName().Version.ToString()))))))
201+
.WithOpenBracketToken(Token(summaryTrivia, SyntaxKind.OpenBracketToken, TriviaList())),
179202
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))),
180203
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))))
181204
.WithExpressionBody(

UnitTests/UnitTests.NetCore/Mvvm/Test_ICommandAttribute.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,25 @@ public sealed partial class MyViewModel
5151
{
5252
public int Counter { get; private set; }
5353

54+
/// <summary>This is a single line summary.</summary>
5455
[ICommand]
5556
private void IncrementCounter()
5657
{
5758
Counter++;
5859
}
5960

61+
/// <summary>
62+
/// This is a multiline summary
63+
/// </summary>
6064
[ICommand]
6165
private void IncrementCounterWithValue(int count)
6266
{
6367
Counter += count;
6468
}
6569

70+
/// <summary>This is single line with also other stuff below</summary>
71+
/// <returns>Foo bar baz</returns>
72+
/// <returns>A task</returns>
6673
[ICommand]
6774
private async Task DelayAndIncrementCounterAsync()
6875
{
@@ -71,6 +78,11 @@ private async Task DelayAndIncrementCounterAsync()
7178
Counter += 1;
7279
}
7380

81+
/// <summary>
82+
/// This is multi line with also other stuff below
83+
/// </summary>
84+
/// <returns>Foo bar baz</returns>
85+
/// <returns>A task</returns>
7486
[ICommand]
7587
private async Task DelayAndIncrementCounterWithTokenAsync(CancellationToken token)
7688
{
@@ -79,6 +91,7 @@ private async Task DelayAndIncrementCounterWithTokenAsync(CancellationToken toke
7991
Counter += 1;
8092
}
8193

94+
// This should not be ported over
8295
[ICommand]
8396
private async Task DelayAndIncrementCounterWithValueAsync(int count)
8497
{

0 commit comments

Comments
 (0)