@@ -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,95 @@ 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
77+ var newTrailingTrivia = previousToken . TrailingTrivia
9078 . WithoutTrailingWhitespace ( )
91- . AddRange ( sharedTrivia )
92- . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
79+ . Add ( SyntaxFactory . Space ) ;
9380
94- rewriter . AddReplacement ( previousToken , previousToken . WithTrailingTrivia ( previousTokenNewTrailingTrivia ) ) ;
81+ AddReplacement ( tokenReplacements , previousToken , previousToken . WithTrailingTrivia ( newTrailingTrivia ) ) ;
9582
96- curlyBracketReplacementToken = curlyBracketReplacementToken . WithLeadingTrivia ( IndentationHelper . GenerateWhitespaceTrivia ( indentationOptions , indentationSteps ) ) ;
83+ braceReplacementToken = braceReplacementToken . WithLeadingTrivia ( braceToken . LeadingTrivia . WithoutLeadingWhitespace ( ) ) ;
9784 }
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 ) ) )
85+ else
10386 {
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 ) )
87+ // Check if we need to apply a fix before the curly bracket
88+ if ( LocationHelpers . GetLineSpan ( previousToken ) . StartLinePosition . Line == braceLine )
11289 {
113- newIndentationSteps = indentationSteps + 1 ;
90+ if ( ! braceTokens . Contains ( previousToken ) )
91+ {
92+ var sharedTrivia = braceReplacementToken . LeadingTrivia . WithoutTrailingWhitespace ( ) ;
93+ var previousTokenNewTrailingTrivia = previousToken . TrailingTrivia
94+ . WithoutTrailingWhitespace ( )
95+ . AddRange ( sharedTrivia )
96+ . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
97+
98+ AddReplacement ( tokenReplacements , previousToken , previousToken . WithTrailingTrivia ( previousTokenNewTrailingTrivia ) ) ;
99+ }
100+
101+ braceReplacementToken = braceReplacementToken . WithLeadingTrivia ( IndentationHelper . GenerateWhitespaceTrivia ( indentationOptions , indentationSteps ) ) ;
114102 }
115- else if ( nextToken . IsKind ( SyntaxKind . CloseBraceToken ) )
116- {
117- newIndentationSteps = Math . Max ( 0 , indentationSteps - 1 ) ;
118- }
119- else
103+
104+ // Check if we need to apply a fix after the curly bracket
105+ // if a closing curly bracket is followed by a semi-colon or closing paren, no fix is needed.
106+ if ( ( LocationHelpers . GetLineSpan ( nextToken ) . StartLinePosition . Line == braceLine ) &&
107+ ( ! braceToken . IsKind ( SyntaxKind . CloseBraceToken ) || ! IsValidFollowingToken ( nextToken ) ) )
120108 {
121- newIndentationSteps = indentationSteps ;
109+ var sharedTrivia = nextToken . LeadingTrivia . WithoutTrailingWhitespace ( ) ;
110+ var newTrailingTrivia = braceReplacementToken . TrailingTrivia
111+ . WithoutTrailingWhitespace ( )
112+ . AddRange ( sharedTrivia )
113+ . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
114+
115+ if ( ! braceTokens . Contains ( nextToken ) )
116+ {
117+ int newIndentationSteps ;
118+ if ( braceToken . IsKind ( SyntaxKind . OpenBraceToken ) )
119+ {
120+ newIndentationSteps = indentationSteps + 1 ;
121+ }
122+ else if ( nextToken . IsKind ( SyntaxKind . CloseBraceToken ) )
123+ {
124+ newIndentationSteps = Math . Max ( 0 , indentationSteps - 1 ) ;
125+ }
126+ else
127+ {
128+ newIndentationSteps = indentationSteps ;
129+ }
130+
131+ AddReplacement ( tokenReplacements , nextToken , nextToken . WithLeadingTrivia ( IndentationHelper . GenerateWhitespaceTrivia ( indentationOptions , newIndentationSteps ) ) ) ;
132+ }
133+
134+ braceReplacementToken = braceReplacementToken . WithTrailingTrivia ( newTrailingTrivia ) ;
122135 }
123-
124- rewriter . AddReplacement ( nextToken , nextToken . WithLeadingTrivia ( IndentationHelper . GenerateWhitespaceTrivia ( indentationOptions , newIndentationSteps ) ) ) ;
125-
126- curlyBracketReplacementToken = curlyBracketReplacementToken . WithTrailingTrivia ( newTrailingTrivia ) ;
127136 }
128- }
129137
130- rewriter . AddReplacement ( curlyBracketToken , curlyBracketReplacementToken ) ;
138+ AddReplacement ( tokenReplacements , braceToken , braceReplacementToken ) ;
139+ }
131140
132- var newSyntaxRoot = rewriter . Visit ( syntaxRoot ) . WithoutFormatting ( ) ;
133- return document . WithSyntaxRoot ( newSyntaxRoot ) ;
141+ return tokenReplacements ;
134142 }
135143
136144 private static bool IsAccessorWithSingleLineBlock ( SyntaxToken previousToken , SyntaxToken curlyBracketToken )
@@ -170,7 +178,7 @@ private static bool IsAccessorWithSingleLineBlock(SyntaxToken previousToken, Syn
170178 }
171179 }
172180
173- return curlyBracketToken . GetLineSpan ( ) . StartLinePosition . Line == token . GetLineSpan ( ) . StartLinePosition . Line ;
181+ return LocationHelpers . GetLineSpan ( curlyBracketToken ) . StartLinePosition . Line == LocationHelpers . GetLineSpan ( token ) . StartLinePosition . Line ;
174182 }
175183
176184 private static bool IsValidFollowingToken ( SyntaxToken nextToken )
@@ -229,28 +237,40 @@ private static bool ContainsStartOfLine(SyntaxToken token, int startLine)
229237
230238 private static LinePosition GetTokenStartLinePosition ( SyntaxToken token )
231239 {
232- return Location . Create ( token . SyntaxTree , token . FullSpan ) . GetLineSpan ( ) . StartLinePosition ;
240+ return token . SyntaxTree . GetLineSpan ( token . FullSpan ) . StartLinePosition ;
233241 }
234242
235- private class Rewriter : CSharpSyntaxRewriter
243+ private static void AddReplacement ( Dictionary < SyntaxToken , SyntaxToken > tokenReplacements , SyntaxToken originalToken , SyntaxToken replacementToken )
236244 {
237- private readonly Dictionary < SyntaxToken , SyntaxToken > tokenReplacements = new Dictionary < SyntaxToken , SyntaxToken > ( ) ;
245+ tokenReplacements [ originalToken ] = replacementToken ;
246+ }
238247
239- public void AddReplacement ( SyntaxToken originalToken , SyntaxToken replacementToken )
240- {
241- this . tokenReplacements . Add ( originalToken , replacementToken ) ;
242- }
248+ private class FixAll : DocumentBasedFixAllProvider
249+ {
250+ public static FixAllProvider Instance { get ; } =
251+ new FixAll ( ) ;
243252
244- public override SyntaxToken VisitToken ( SyntaxToken token )
245- {
246- SyntaxToken replacementToken ;
253+ protected override string CodeActionTitle =>
254+ LayoutResources . SA1500CodeFix ;
247255
248- if ( this . tokenReplacements . TryGetValue ( token , out replacementToken ) )
256+ protected override async Task < SyntaxNode > FixAllInDocumentAsync ( FixAllContext fixAllContext , Document document )
257+ {
258+ var diagnostics = await fixAllContext . GetDocumentDiagnosticsAsync ( document ) . ConfigureAwait ( false ) ;
259+ if ( diagnostics . IsEmpty )
249260 {
250- return replacementToken ;
261+ return null ;
251262 }
252263
253- return base . VisitToken ( token ) ;
264+ SyntaxNode syntaxRoot = await document . GetSyntaxRootAsync ( ) . ConfigureAwait ( false ) ;
265+
266+ var tokens = diagnostics
267+ . Select ( diagnostic => syntaxRoot . FindToken ( diagnostic . Location . SourceSpan . Start ) )
268+ . OrderBy ( token => token . SpanStart )
269+ . ToImmutableArray ( ) ;
270+
271+ var tokenReplacements = GenerateBraceFixes ( document , tokens ) ;
272+
273+ return syntaxRoot . ReplaceTokens ( tokenReplacements . Keys , ( originalToken , rewrittenToken ) => tokenReplacements [ originalToken ] ) ;
254274 }
255275 }
256276 }
0 commit comments