|
4 | 4 |
|
5 | 5 | using System.Collections.Immutable;
|
6 | 6 | using System.Composition;
|
7 |
| -using System.Diagnostics; |
8 |
| -using System.Linq; |
9 | 7 | using System.Threading;
|
10 | 8 | using System.Threading.Tasks;
|
11 | 9 | using CommunityToolkit.Mvvm.SourceGenerators;
|
|
15 | 13 | using Microsoft.CodeAnalysis.CodeFixes;
|
16 | 14 | using Microsoft.CodeAnalysis.CSharp;
|
17 | 15 | using Microsoft.CodeAnalysis.CSharp.Syntax;
|
| 16 | +using Microsoft.CodeAnalysis.Text; |
18 | 17 |
|
19 | 18 | namespace CommunityToolkit.Mvvm.Fixers;
|
20 | 19 |
|
21 |
| -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member |
22 |
| -[ExportCodeFixProvider(LanguageNames.CSharp), Shared] |
23 |
| -public class FieldReferenceForObservablePropertyFieldFixer : CodeFixProvider |
| 20 | +/// <summary> |
| 21 | +/// A code fixer that automatically updates references to fields with <c>[ObservableProperty]</c> to reference the generated property instead. |
| 22 | +/// </summary> |
| 23 | +[ExportCodeFixProvider(LanguageNames.CSharp)] |
| 24 | +[Shared] |
| 25 | +public sealed class FieldReferenceForObservablePropertyFieldFixer : CodeFixProvider |
24 | 26 | {
|
| 27 | + /// <inheritdoc/> |
25 | 28 | public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticDescriptors.FieldReferenceForObservablePropertyFieldId);
|
26 | 29 |
|
| 30 | + /// <inheritdoc/> |
27 | 31 | public override FixAllProvider? GetFixAllProvider()
|
28 | 32 | {
|
29 | 33 | return WellKnownFixAllProviders.BatchFixer;
|
30 | 34 | }
|
31 | 35 |
|
| 36 | + /// <inheritdoc/> |
32 | 37 | public override async Task RegisterCodeFixesAsync(CodeFixContext context)
|
33 | 38 | {
|
34 | 39 | Diagnostic diagnostic = context.Diagnostics[0];
|
35 |
| - Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; |
| 40 | + TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; |
36 | 41 |
|
37 |
| - string? propertyName = diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey]; |
38 |
| - string? fieldName = diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey]; |
39 |
| - |
40 |
| - if (propertyName == null || fieldName == null) |
| 42 | + // Retrieve the properties passed by the analyzer |
| 43 | + if (diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.FieldNameKey] is not string fieldName || |
| 44 | + diagnostic.Properties[FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey] is not string propertyName) |
41 | 45 | {
|
42 | 46 | return;
|
43 | 47 | }
|
44 | 48 |
|
45 | 49 | SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
|
46 |
| - Debug.Assert(root != null); |
47 |
| - |
48 |
| - IdentifierNameSyntax fieldReference = root!.FindNode(diagnosticSpan).DescendantNodesAndSelf().OfType<IdentifierNameSyntax>().FirstOrDefault(i => i.ToString() == fieldName); |
49 | 50 |
|
50 |
| - if (fieldReference == null) |
| 51 | + foreach (SyntaxNode syntaxNode in root!.FindNode(diagnosticSpan).DescendantNodesAndSelf()) |
51 | 52 | {
|
52 |
| - return; |
53 |
| - } |
54 |
| - |
55 |
| - context.RegisterCodeFix( |
56 |
| - CodeAction.Create( |
57 |
| - title: "Reference property", |
58 |
| - createChangedDocument: c => UpdateReference(context.Document, fieldReference, propertyName, c), |
59 |
| - equivalenceKey: "Reference property"), |
60 |
| - diagnostic); |
| 53 | + // Find the first descendant node from the source of the diagnostic that is an identifier with the target name |
| 54 | + if (syntaxNode is IdentifierNameSyntax { Identifier.Text: string identifierName } identifierNameSyntax && |
| 55 | + identifierName == fieldName) |
| 56 | + { |
| 57 | + // Register the code fix to update the field reference to use the generated property instead |
| 58 | + context.RegisterCodeFix( |
| 59 | + CodeAction.Create( |
| 60 | + title: "Reference property", |
| 61 | + createChangedDocument: token => UpdateReference(context.Document, identifierNameSyntax, propertyName, token), |
| 62 | + equivalenceKey: "Reference property"), |
| 63 | + diagnostic); |
61 | 64 |
|
| 65 | + return; |
| 66 | + } |
| 67 | + } |
62 | 68 | }
|
63 | 69 |
|
| 70 | + /// <summary> |
| 71 | + /// Applies the code fix to a target identifier and returns an updated document. |
| 72 | + /// </summary> |
| 73 | + /// <param name="document">The original document being fixed.</param> |
| 74 | + /// <param name="fieldReference">The <see cref="IdentifierNameSyntax"/> corresponding to the field reference to update.</param> |
| 75 | + /// <param name="propertyName">The name of the generated property.</param> |
| 76 | + /// <param name="cancellationToken">The cancellation token for the operation.</param> |
| 77 | + /// <returns>An updated document with the applied code fix, and <paramref name="fieldReference"/> being replaced with a property reference.</returns> |
64 | 78 | private static async Task<Document> UpdateReference(Document document, IdentifierNameSyntax fieldReference, string propertyName, CancellationToken cancellationToken)
|
65 | 79 | {
|
66 | 80 | IdentifierNameSyntax propertyReference = SyntaxFactory.IdentifierName(propertyName);
|
67 |
| - SyntaxNode originalRoot = await fieldReference.SyntaxTree.GetRootAsync(cancellationToken); |
| 81 | + SyntaxNode originalRoot = await fieldReference.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); |
68 | 82 | SyntaxTree updatedTree = originalRoot.ReplaceNode(fieldReference, propertyReference).SyntaxTree;
|
69 |
| - return document.WithSyntaxRoot(await updatedTree.GetRootAsync(cancellationToken)); |
| 83 | + |
| 84 | + return document.WithSyntaxRoot(await updatedTree.GetRootAsync(cancellationToken).ConfigureAwait(false)); |
70 | 85 | }
|
71 | 86 | }
|
0 commit comments