Skip to content

Commit dff50fe

Browse files
authored
feature: Add XML doc comment support for generated properties (#377)
* Add XML doc comment support for generated properties This update enhances the source generator to extract and include XML documentation comments for generated properties, improving IntelliSense and documentation. It also updates the PropertyInfo model to store XML comments, adjusts code generation to use these comments, and adds a summary for an internal test property. Minor code cleanup and a new .slnx solution file are included. * Delete ReactiveUI.SourceGenerators.sln * Preserve XML documentation in generated commands Added support to extract and include XML documentation comments from method symbols into the generated command properties. This helps retain developer documentation in the generated code for better maintainability and IntelliSense support.
1 parent 0a67b63 commit dff50fe

File tree

7 files changed

+59
-16
lines changed

7 files changed

+59
-16
lines changed

src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#TestNs.TestVM.Properties.g.verified.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace TestNs
1010
public partial class TestVM
1111
{
1212

13-
/// <inheritdoc cref="this.value"/>
13+
/// <inheritdoc cref="value"/>
1414
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
1515
public string Value
1616
{

src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ public TestViewModel()
358358
[ObservableAsProperty(InitialValue = "10")]
359359
public partial int ObservableAsPropertyFromProperty { get; }
360360

361+
/// <summary>
362+
/// Gets or sets the value for internal use within the partial class or assembly.
363+
/// </summary>
361364
[Reactive]
362365
internal partial int InternalPartialPropertyTest { get; set; }
363366

src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/FieldSyntaxExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
// See the LICENSE file in the project root for full license information.
55

66
using System.Globalization;
7+
using System.Linq;
78
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
810
using ReactiveUI.SourceGenerators.Helpers;
911

1012
namespace ReactiveUI.SourceGenerators.Extensions;

src/ReactiveUI.SourceGenerators.Roslyn/Reactive/Models/PropertyInfo.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ internal sealed record PropertyInfo(
2323
string UseRequired,
2424
bool IsProperty,
2525
string PropertyAccessModifier,
26-
EquatableArray<string> AlsoNotify);
26+
EquatableArray<string> AlsoNotify,
27+
string? XmlComment);

src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,19 @@ public sealed partial class ReactiveGenerator
155155

156156
token.ThrowIfCancellationRequested();
157157

158+
var xmlTrivia = propertySymbol.GetDocumentationCommentXml(cancellationToken: token);
159+
160+
// Remove Member attributes from xmlTrivia on the first and last lines
161+
if (xmlTrivia?.Length > 0)
162+
{
163+
var s = xmlTrivia.Split('\n').ToList();
164+
s.RemoveAt(0);
165+
s.Remove(s.Last());
166+
s.Remove(s.Last());
167+
xmlTrivia = string.Concat(s.Select(c => " /// " + c.TrimStart() + "\n"));
168+
xmlTrivia = xmlTrivia.TrimEnd();
169+
}
170+
158171
return new(
159172
new(
160173
targetInfo,
@@ -169,19 +182,20 @@ public sealed partial class ReactiveGenerator
169182
useRequired,
170183
true,
171184
propertyAccessModifier!,
172-
alsoNotify),
185+
alsoNotify,
186+
xmlTrivia),
173187
builder.ToImmutable());
174188
}
175189
#endif
176190

177-
/// <summary>
178-
/// Gets the observable method information.
179-
/// </summary>
180-
/// <param name="context">The context.</param>
181-
/// <param name="token">The token.</param>
182-
/// <returns>
183-
/// The value.
184-
/// </returns>
191+
/// <summary>
192+
/// Gets the observable method information.
193+
/// </summary>
194+
/// <param name="context">The context.</param>
195+
/// <param name="token">The token.</param>
196+
/// <returns>
197+
/// The value.
198+
/// </returns>
185199
private static Result<PropertyInfo?>? GetVariableInfo(in GeneratorAttributeSyntaxContext context, CancellationToken token)
186200
{
187201
using var builder = ImmutableArrayBuilder<DiagnosticInfo>.Rent();
@@ -300,7 +314,8 @@ public sealed partial class ReactiveGenerator
300314
useRequired,
301315
false,
302316
"public",
303-
alsoNotify),
317+
alsoNotify,
318+
string.Empty),
304319
builder.ToImmutable());
305320
}
306321

@@ -393,12 +408,18 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo)
393408
""";
394409
}
395410

411+
var docSource = string.Empty;
396412
if (propertyInfo.IsProperty)
397413
{
414+
docSource = !string.IsNullOrWhiteSpace(propertyInfo.XmlComment)
415+
? propertyInfo.XmlComment
416+
: $$""" /// <inheritdoc cref="{{propertyInfo.PropertyName}}"/>""";
417+
398418
propertyAttributes = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage);
399419
}
400420
else
401421
{
422+
docSource = $$""" /// <inheritdoc cref="{{getFieldName}}"/>""";
402423
propertyAttributes = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(propertyInfo.ForwardedAttributes));
403424
}
404425

@@ -416,7 +437,7 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo)
416437
return
417438
$$"""
418439
{{fieldSyntax}}
419-
/// <inheritdoc cref="{{setFieldName}}"/>
440+
{{docSource}}
420441
[global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")]
421442
{{propertyAttributes}}
422443
{{accessModifier}}{{propertyInfo.Inheritance}} {{propertyInfo.UseRequired}}{{partialModifier}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}}
@@ -434,7 +455,7 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo)
434455
return
435456
$$"""
436457
{{fieldSyntax}}
437-
/// <inheritdoc cref="{{setFieldName}}"/>
458+
{{docSource}}
438459
[global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")]
439460
{{propertyAttributes}}
440461
{{accessModifier}}{{propertyInfo.Inheritance}} {{propertyInfo.UseRequired}}{{partialModifier}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}}

src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/Models/CommandInfo.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ internal record CommandInfo(
1919
CanExecuteTypeInfo? CanExecuteTypeInfo,
2020
string? OutputScheduler,
2121
EquatableArray<string> ForwardedPropertyAttributes,
22-
string AccessModifier)
22+
string AccessModifier,
23+
string? XmlComment)
2324
{
2425
private const string UnitTypeName = "global::System.Reactive.Unit";
2526

src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.Execute.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ public partial class ReactiveCommandGenerator
123123

124124
token.ThrowIfCancellationRequested();
125125

126+
var xmlTrivia = methodSymbol.GetDocumentationCommentXml(cancellationToken: token);
127+
128+
// Remove Member attributes from xmlTrivia on the first and last lines
129+
if (xmlTrivia?.Length > 0)
130+
{
131+
var s = xmlTrivia.Split('\n').ToList();
132+
s.RemoveAt(0);
133+
s.Remove(s.Last());
134+
s.Remove(s.Last());
135+
xmlTrivia = string.Concat(s.Select(c => " /// " + c.TrimStart() + "\n"));
136+
xmlTrivia = xmlTrivia.TrimEnd();
137+
}
138+
126139
return new(
127140
targetInfo,
128141
symbol.Name,
@@ -135,7 +148,8 @@ public partial class ReactiveCommandGenerator
135148
canExecuteTypeInfo,
136149
outputScheduler,
137150
forwardedPropertyAttributes,
138-
accessModifier);
151+
accessModifier,
152+
xmlTrivia);
139153
}
140154

141155
private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, CommandInfo[] commands)
@@ -217,6 +231,7 @@ private static string GetCommandSyntax(CommandInfo commandExtensionInfo)
217231
$$"""
218232
private {{RxCmd}}<{{inputType}}, {{outputType}}>? {{fieldName}};
219233
234+
{{commandExtensionInfo.XmlComment}}
220235
[global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")]
221236
{{forwardedPropertyAttributesString}}
222237
{{commandExtensionInfo.AccessModifier}} {{RxCmd}}<{{inputType}}, {{outputType}}> {{commandName}} { get => {{initializer}} }

0 commit comments

Comments
 (0)