33
44namespace StyleCop . Analyzers . SpacingRules
55{
6+ using System . Collections . Generic ;
67 using System . Collections . Immutable ;
78 using System . Composition ;
89 using System . Linq ;
10+ using System . Threading ;
911 using System . Threading . Tasks ;
1012 using Helpers ;
1113 using Microsoft . CodeAnalysis ;
1214 using Microsoft . CodeAnalysis . CodeActions ;
1315 using Microsoft . CodeAnalysis . CodeFixes ;
1416 using Microsoft . CodeAnalysis . CSharp ;
17+ using Microsoft . CodeAnalysis . Text ;
1518
1619 /// <summary>
1720 /// Implements a code fix for <see cref="SA1004DocumentationLinesMustBeginWithSingleSpace"/>.
@@ -23,11 +26,9 @@ namespace StyleCop.Analyzers.SpacingRules
2326 [ Shared ]
2427 public class SA1004CodeFixProvider : CodeFixProvider
2528 {
26- private static readonly ImmutableArray < string > FixableDiagnostics =
27- ImmutableArray . Create ( SA1004DocumentationLinesMustBeginWithSingleSpace . DiagnosticId ) ;
28-
2929 /// <inheritdoc/>
30- public override ImmutableArray < string > FixableDiagnosticIds => FixableDiagnostics ;
30+ public override ImmutableArray < string > FixableDiagnosticIds { get ; }
31+ = ImmutableArray . Create ( SA1004DocumentationLinesMustBeginWithSingleSpace . DiagnosticId ) ;
3132
3233 /// <inheritdoc/>
3334 public override FixAllProvider GetFixAllProvider ( )
@@ -36,38 +37,77 @@ public override FixAllProvider GetFixAllProvider()
3637 }
3738
3839 /// <inheritdoc/>
39- public override async Task RegisterCodeFixesAsync ( CodeFixContext context )
40+ public override Task RegisterCodeFixesAsync ( CodeFixContext context )
4041 {
41- var root = await context . Document . GetSyntaxRootAsync ( context . CancellationToken ) . ConfigureAwait ( false ) ;
42-
43- foreach ( var diagnostic in context . Diagnostics . Where ( d => FixableDiagnostics . Contains ( d . Id ) ) )
42+ foreach ( var diagnostic in context . Diagnostics )
4443 {
4544 if ( ! diagnostic . Id . Equals ( SA1004DocumentationLinesMustBeginWithSingleSpace . DiagnosticId ) )
4645 {
4746 continue ;
4847 }
4948
50- context . RegisterCodeFix ( CodeAction . Create ( SpacingResources . SA1004CodeFix , token => GetTransformedDocumentAsync ( context . Document , root , diagnostic ) , equivalenceKey : nameof ( SA1004CodeFixProvider ) ) , diagnostic ) ;
49+ context . RegisterCodeFix (
50+ CodeAction . Create (
51+ SpacingResources . SA1004CodeFix ,
52+ cancellationToken => GetTransformedDocumentAsync ( context . Document , diagnostic , cancellationToken ) ,
53+ equivalenceKey : nameof ( SA1004CodeFixProvider ) ) ,
54+ diagnostic ) ;
5155 }
56+
57+ return SpecializedTasks . CompletedTask ;
58+ }
59+
60+ private static async Task < Document > GetTransformedDocumentAsync ( Document document , Diagnostic diagnostic , CancellationToken cancellationToken )
61+ {
62+ var root = await document . GetSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
63+ var text = await document . GetTextAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
64+ return document . WithText ( text . WithChanges ( GetTextChange ( root , text , diagnostic ) ) ) ;
5265 }
5366
54- private static Task < Document > GetTransformedDocumentAsync ( Document document , SyntaxNode root , Diagnostic diagnostic )
67+ private static TextChange GetTextChange ( SyntaxNode root , SourceText sourceText , Diagnostic diagnostic )
5568 {
5669 var token = root . FindToken ( diagnostic . Location . SourceSpan . Start , findInsideTrivia : true ) ;
57- SyntaxToken updatedToken ;
5870 switch ( token . Kind ( ) )
5971 {
6072 case SyntaxKind . XmlTextLiteralToken :
61- updatedToken = XmlSyntaxFactory . TextLiteral ( " " + token . Text . TrimStart ( ' ' ) ) . WithTriviaFrom ( token ) ;
62- break ;
73+ int spaceCount = token . ValueText . Length - token . ValueText . TrimStart ( ' ' ) . Length ;
74+ return new TextChange ( new TextSpan ( token . SpanStart , spaceCount ) , " " ) ;
6375
6476 default :
65- updatedToken = token . WithLeadingTrivia ( token . LeadingTrivia . Add ( SyntaxFactory . Space ) ) ;
66- break ;
77+ return new TextChange ( new TextSpan ( token . SpanStart , 0 ) , " " ) ;
6778 }
79+ }
80+
81+ private class FixAll : DocumentBasedFixAllProvider
82+ {
83+ public static FixAllProvider Instance { get ; } =
84+ new FixAll ( ) ;
85+
86+ protected override string CodeActionTitle =>
87+ SpacingResources . SA1004CodeFix ;
6888
69- Document updatedDocument = document . WithSyntaxRoot ( root . ReplaceToken ( token , updatedToken ) ) ;
70- return Task . FromResult ( updatedDocument ) ;
89+ protected override async Task < SyntaxNode > FixAllInDocumentAsync ( FixAllContext fixAllContext , Document document )
90+ {
91+ var diagnostics = await fixAllContext . GetDocumentDiagnosticsAsync ( document ) . ConfigureAwait ( false ) ;
92+ if ( diagnostics . IsEmpty )
93+ {
94+ return null ;
95+ }
96+
97+ var root = await document . GetSyntaxRootAsync ( fixAllContext . CancellationToken ) . ConfigureAwait ( false ) ;
98+ var text = await document . GetTextAsync ( fixAllContext . CancellationToken ) . ConfigureAwait ( false ) ;
99+
100+ List < TextChange > changes = new List < TextChange > ( ) ;
101+ foreach ( var diagnostic in diagnostics )
102+ {
103+ changes . Add ( GetTextChange ( root , text , diagnostic ) ) ;
104+ }
105+
106+ changes . Sort ( ( left , right ) => left . Span . Start . CompareTo ( right . Span . Start ) ) ;
107+
108+ var tree = await document . GetSyntaxTreeAsync ( fixAllContext . CancellationToken ) . ConfigureAwait ( false ) ;
109+ return await tree . WithChangedText ( text . WithChanges ( changes ) ) . GetRootAsync ( ) . ConfigureAwait ( false ) ;
110+ }
71111 }
72112 }
73113}
0 commit comments