@@ -25,28 +25,20 @@ namespace StyleCop.Analyzers.LayoutRules
2525 [ Shared ]
2626 internal class SA1500CodeFixProvider : CodeFixProvider
2727 {
28- private static readonly ImmutableArray < string > FixableDiagnostics =
29- ImmutableArray . Create ( SA1500CurlyBracketsForMultiLineStatementsMustNotShareLine . DiagnosticId ) ;
30-
3128 /// <inheritdoc/>
32- public override ImmutableArray < string > FixableDiagnosticIds
33- {
34- get
35- {
36- return FixableDiagnostics ;
37- }
38- }
29+ public override ImmutableArray < string > FixableDiagnosticIds { get ; } =
30+ ImmutableArray . Create ( SA1500CurlyBracketsForMultiLineStatementsMustNotShareLine . DiagnosticId ) ;
3931
4032 /// <inheritdoc/>
4133 public override FixAllProvider GetFixAllProvider ( )
4234 {
43- return CustomFixAllProviders . BatchFixer ;
35+ return FixAll . Instance ;
4436 }
4537
4638 /// <inheritdoc/>
4739 public override Task RegisterCodeFixesAsync ( CodeFixContext context )
4840 {
49- foreach ( Diagnostic diagnostic in context . Diagnostics . Where ( d => FixableDiagnostics . Contains ( d . Id ) ) )
41+ foreach ( Diagnostic diagnostic in context . Diagnostics )
5042 {
5143 context . RegisterCodeFix ( CodeAction . Create ( LayoutResources . SA1500CodeFix , token => GetTransformedDocumentAsync ( context . Document , diagnostic , token ) , equivalenceKey : nameof ( SA1500CodeFixProvider ) ) , diagnostic ) ;
5244 }
@@ -58,79 +50,98 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
5850 {
5951 var syntaxRoot = await document . GetSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
6052
61- var curlyBracketToken = syntaxRoot . FindToken ( diagnostic . Location . SourceSpan . Start ) ;
62- var curlyBracketLine = curlyBracketToken . GetLineSpan ( ) . StartLinePosition . Line ;
63- var curlyBracketReplacementToken = curlyBracketToken ;
53+ var braceToken = syntaxRoot . FindToken ( diagnostic . Location . SourceSpan . Start ) ;
54+ var tokenReplacements = GenerateBraceFixes ( document , ImmutableArray . Create ( braceToken ) ) ;
6455
65- var indentationOptions = IndentationOptions . FromDocument ( document ) ;
66- var indentationSteps = DetermineIndentationSteps ( indentationOptions , curlyBracketToken ) ;
67-
68- var previousToken = curlyBracketToken . GetPreviousToken ( ) ;
69- var nextToken = curlyBracketToken . GetNextToken ( ) ;
56+ var newSyntaxRoot = syntaxRoot . ReplaceTokens ( tokenReplacements . Keys , ( originalToken , rewrittenToken ) => tokenReplacements [ originalToken ] ) ;
57+ return document . WithSyntaxRoot ( newSyntaxRoot ) ;
58+ }
7059
71- var rewriter = new Rewriter ( ) ;
60+ private static Dictionary < SyntaxToken , SyntaxToken > GenerateBraceFixes ( Document document , ImmutableArray < SyntaxToken > braceTokens )
61+ {
62+ var tokenReplacements = new Dictionary < SyntaxToken , SyntaxToken > ( ) ;
7263
73- if ( IsAccessorWithSingleLineBlock ( previousToken , curlyBracketToken ) )
64+ foreach ( var braceToken in braceTokens )
7465 {
75- var newTrailingTrivia = previousToken . TrailingTrivia
76- . WithoutTrailingWhitespace ( )
77- . Add ( SyntaxFactory . Space ) ;
66+ var braceLine = LocationHelpers . GetLineSpan ( braceToken ) . StartLinePosition . Line ;
67+ var braceReplacementToken = braceToken ;
7868
79- rewriter . AddReplacement ( previousToken , previousToken . WithTrailingTrivia ( newTrailingTrivia ) ) ;
69+ var indentationOptions = IndentationOptions . FromDocument ( document ) ;
70+ var indentationSteps = DetermineIndentationSteps ( indentationOptions , braceToken ) ;
8071
81- curlyBracketReplacementToken = curlyBracketReplacementToken . WithLeadingTrivia ( curlyBracketToken . LeadingTrivia . WithoutLeadingWhitespace ( ) ) ;
82- }
83- else
84- {
85- // Check if we need to apply a fix before the curly bracket
86- if ( previousToken . GetLineSpan ( ) . StartLinePosition . Line == curlyBracketLine )
72+ var previousToken = braceToken . GetPreviousToken ( ) ;
73+ var nextToken = braceToken . GetNextToken ( ) ;
74+
75+ if ( IsAccessorWithSingleLineBlock ( previousToken , braceToken ) )
8776 {
88- var sharedTrivia = curlyBracketReplacementToken . LeadingTrivia . WithoutTrailingWhitespace ( ) ;
89- var previousTokenNewTrailingTrivia = previousToken . TrailingTrivia
90- . WithoutTrailingWhitespace ( )
91- . AddRange ( sharedTrivia )
92- . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
77+ if ( ! braceTokens . Contains ( previousToken ) )
78+ {
79+ var newTrailingTrivia = previousToken . TrailingTrivia
80+ . WithoutTrailingWhitespace ( )
81+ . Add ( SyntaxFactory . Space ) ;
9382
94- rewriter . AddReplacement ( previousToken , previousToken . WithTrailingTrivia ( previousTokenNewTrailingTrivia ) ) ;
83+ AddReplacement ( tokenReplacements , previousToken , previousToken . WithTrailingTrivia ( newTrailingTrivia ) ) ;
84+ }
9585
96- curlyBracketReplacementToken = curlyBracketReplacementToken . WithLeadingTrivia ( IndentationHelper . GenerateWhitespaceTrivia ( indentationOptions , indentationSteps ) ) ;
86+ braceReplacementToken = braceReplacementToken . WithLeadingTrivia ( braceToken . LeadingTrivia . WithoutLeadingWhitespace ( ) ) ;
9787 }
98-
99- // Check if we need to apply a fix after the curly bracket
100- // if a closing curly bracket is followed by a semi-colon or closing paren, no fix is needed.
101- if ( ( nextToken . GetLineSpan ( ) . StartLinePosition . Line == curlyBracketLine ) &&
102- ( ! curlyBracketToken . IsKind ( SyntaxKind . CloseBraceToken ) || ! IsValidFollowingToken ( nextToken ) ) )
88+ else
10389 {
104- var sharedTrivia = nextToken . LeadingTrivia . WithoutTrailingWhitespace ( ) ;
105- var newTrailingTrivia = curlyBracketReplacementToken . TrailingTrivia
106- . WithoutTrailingWhitespace ( )
107- . AddRange ( sharedTrivia )
108- . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
109-
110- int newIndentationSteps ;
111- if ( curlyBracketToken . IsKind ( SyntaxKind . OpenBraceToken ) )
90+ // Check if we need to apply a fix before the curly bracket
91+ if ( LocationHelpers . GetLineSpan ( previousToken ) . StartLinePosition . Line == braceLine )
11292 {
113- newIndentationSteps = indentationSteps + 1 ;
93+ if ( ! braceTokens . Contains ( previousToken ) )
94+ {
95+ var sharedTrivia = braceReplacementToken . LeadingTrivia . WithoutTrailingWhitespace ( ) ;
96+ var previousTokenNewTrailingTrivia = previousToken . TrailingTrivia
97+ . WithoutTrailingWhitespace ( )
98+ . AddRange ( sharedTrivia )
99+ . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
100+
101+ AddReplacement ( tokenReplacements , previousToken , previousToken . WithTrailingTrivia ( previousTokenNewTrailingTrivia ) ) ;
102+ }
103+
104+ braceReplacementToken = braceReplacementToken . WithLeadingTrivia ( IndentationHelper . GenerateWhitespaceTrivia ( indentationOptions , indentationSteps ) ) ;
114105 }
115- else if ( nextToken . IsKind ( SyntaxKind . CloseBraceToken ) )
116- {
117- newIndentationSteps = Math . Max ( 0 , indentationSteps - 1 ) ;
118- }
119- else
106+
107+ // Check if we need to apply a fix after the curly bracket
108+ // if a closing curly bracket is followed by a semi-colon or closing paren, no fix is needed.
109+ if ( ( LocationHelpers . GetLineSpan ( nextToken ) . StartLinePosition . Line == braceLine ) &&
110+ ( ! braceToken . IsKind ( SyntaxKind . CloseBraceToken ) || ! IsValidFollowingToken ( nextToken ) ) )
120111 {
121- newIndentationSteps = indentationSteps ;
112+ var sharedTrivia = nextToken . LeadingTrivia . WithoutTrailingWhitespace ( ) ;
113+ var newTrailingTrivia = braceReplacementToken . TrailingTrivia
114+ . WithoutTrailingWhitespace ( )
115+ . AddRange ( sharedTrivia )
116+ . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
117+
118+ if ( ! braceTokens . Contains ( nextToken ) )
119+ {
120+ int newIndentationSteps ;
121+ if ( braceToken . IsKind ( SyntaxKind . OpenBraceToken ) )
122+ {
123+ newIndentationSteps = indentationSteps + 1 ;
124+ }
125+ else if ( nextToken . IsKind ( SyntaxKind . CloseBraceToken ) )
126+ {
127+ newIndentationSteps = Math . Max ( 0 , indentationSteps - 1 ) ;
128+ }
129+ else
130+ {
131+ newIndentationSteps = indentationSteps ;
132+ }
133+
134+ AddReplacement ( tokenReplacements , nextToken , nextToken . WithLeadingTrivia ( IndentationHelper . GenerateWhitespaceTrivia ( indentationOptions , newIndentationSteps ) ) ) ;
135+ }
136+
137+ braceReplacementToken = braceReplacementToken . WithTrailingTrivia ( newTrailingTrivia ) ;
122138 }
123-
124- rewriter . AddReplacement ( nextToken , nextToken . WithLeadingTrivia ( IndentationHelper . GenerateWhitespaceTrivia ( indentationOptions , newIndentationSteps ) ) ) ;
125-
126- curlyBracketReplacementToken = curlyBracketReplacementToken . WithTrailingTrivia ( newTrailingTrivia ) ;
127139 }
128- }
129140
130- rewriter . AddReplacement ( curlyBracketToken , curlyBracketReplacementToken ) ;
141+ AddReplacement ( tokenReplacements , braceToken , braceReplacementToken ) ;
142+ }
131143
132- var newSyntaxRoot = rewriter . Visit ( syntaxRoot ) . WithoutFormatting ( ) ;
133- return document . WithSyntaxRoot ( newSyntaxRoot ) ;
144+ return tokenReplacements ;
134145 }
135146
136147 private static bool IsAccessorWithSingleLineBlock ( SyntaxToken previousToken , SyntaxToken curlyBracketToken )
@@ -170,7 +181,7 @@ private static bool IsAccessorWithSingleLineBlock(SyntaxToken previousToken, Syn
170181 }
171182 }
172183
173- return curlyBracketToken . GetLineSpan ( ) . StartLinePosition . Line == token . GetLineSpan ( ) . StartLinePosition . Line ;
184+ return LocationHelpers . GetLineSpan ( curlyBracketToken ) . StartLinePosition . Line == LocationHelpers . GetLineSpan ( token ) . StartLinePosition . Line ;
174185 }
175186
176187 private static bool IsValidFollowingToken ( SyntaxToken nextToken )
@@ -229,28 +240,39 @@ private static bool ContainsStartOfLine(SyntaxToken token, int startLine)
229240
230241 private static LinePosition GetTokenStartLinePosition ( SyntaxToken token )
231242 {
232- return Location . Create ( token . SyntaxTree , token . FullSpan ) . GetLineSpan ( ) . StartLinePosition ;
243+ return token . SyntaxTree . GetLineSpan ( token . FullSpan ) . StartLinePosition ;
233244 }
234245
235- private class Rewriter : CSharpSyntaxRewriter
246+ private static void AddReplacement ( Dictionary < SyntaxToken , SyntaxToken > tokenReplacements , SyntaxToken originalToken , SyntaxToken replacementToken )
236247 {
237- private readonly Dictionary < SyntaxToken , SyntaxToken > tokenReplacements = new Dictionary < SyntaxToken , SyntaxToken > ( ) ;
248+ tokenReplacements [ originalToken ] = replacementToken ;
249+ }
238250
239- public void AddReplacement ( SyntaxToken originalToken , SyntaxToken replacementToken )
240- {
241- this . tokenReplacements . Add ( originalToken , replacementToken ) ;
242- }
251+ private class FixAll : DocumentBasedFixAllProvider
252+ {
253+ public static FixAllProvider Instance { get ; } =
254+ new FixAll ( ) ;
243255
244- public override SyntaxToken VisitToken ( SyntaxToken token )
245- {
246- SyntaxToken replacementToken ;
256+ protected override string CodeActionTitle =>
257+ LayoutResources . SA1503CodeFix ;
247258
248- if ( this . tokenReplacements . TryGetValue ( token , out replacementToken ) )
259+ protected override async Task < SyntaxNode > FixAllInDocumentAsync ( FixAllContext fixAllContext , Document document )
260+ {
261+ var diagnostics = await fixAllContext . GetDocumentDiagnosticsAsync ( document ) . ConfigureAwait ( false ) ;
262+ if ( diagnostics . IsEmpty )
249263 {
250- return replacementToken ;
264+ return null ;
251265 }
252266
253- return base . VisitToken ( token ) ;
267+ SyntaxNode syntaxRoot = await document . GetSyntaxRootAsync ( ) . ConfigureAwait ( false ) ;
268+
269+ var tokens = diagnostics
270+ . Select ( diagnostic => syntaxRoot . FindToken ( diagnostic . Location . SourceSpan . Start ) )
271+ . OrderBy ( token => token . SpanStart ) ;
272+
273+ var tokenReplacements = GenerateBraceFixes ( document , tokens . ToImmutableArray ( ) ) ;
274+
275+ return syntaxRoot . ReplaceTokens ( tokenReplacements . Keys , ( originalToken , rewrittenToken ) => tokenReplacements [ originalToken ] ) ;
254276 }
255277 }
256278 }
0 commit comments