@@ -27,6 +27,7 @@ namespace StyleCop.Analyzers.OrderingRules
2727 internal sealed class UsingCodeFixProvider : CodeFixProvider
2828 {
2929 private static readonly List < UsingDirectiveSyntax > EmptyUsingsList = new List < UsingDirectiveSyntax > ( ) ;
30+ private static readonly SyntaxAnnotation UsingCodeFixAnnotation = new SyntaxAnnotation ( nameof ( UsingCodeFixProvider ) ) ;
3031
3132 /// <inheritdoc/>
3233 public override ImmutableArray < string > FixableDiagnosticIds { get ; } =
@@ -90,12 +91,16 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
9091 {
9192 case UsingDirectivesPlacement . InsideNamespace :
9293 if ( compilationUnit . AttributeLists . Any ( )
93- || compilationUnit . Members . Count != 1
94- || namespaceCount != 1 )
94+ || compilationUnit . Members . Count > 1
95+ || namespaceCount > 1 )
9596 {
9697 // Override the user's setting with a more conservative one
9798 usingDirectivesPlacement = UsingDirectivesPlacement . Preserve ;
9899 }
100+ else if ( namespaceCount == 0 )
101+ {
102+ usingDirectivesPlacement = UsingDirectivesPlacement . OutsideNamespace ;
103+ }
99104 else
100105 {
101106 usingDirectivesPlacement = UsingDirectivesPlacement . InsideNamespace ;
@@ -158,6 +163,8 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
158163 newSyntaxRoot = AddUsingsToCompilationRoot ( newSyntaxRoot , usingsHelper , usingsIndentation , replaceMap . Any ( ) ) ;
159164 }
160165
166+ // Final cleanup
167+ newSyntaxRoot = StripMultipleBlankLines ( newSyntaxRoot ) ;
161168 newSyntaxRoot = ReAddFileHeader ( syntaxRoot , newSyntaxRoot ) ;
162169
163170 var newDocument = document . WithSyntaxRoot ( newSyntaxRoot . WithoutFormatting ( ) ) ;
@@ -173,7 +180,7 @@ private static SyntaxNode ReAddFileHeader(SyntaxNode syntaxRoot, SyntaxNode newS
173180 return newSyntaxRoot ;
174181 }
175182
176- var fileHeader = UsingsHelper . GetFileHeader ( oldFirstToken . LeadingTrivia . ToList ( ) ) ;
183+ var fileHeader = UsingsHelper . GetFileHeader ( oldFirstToken . LeadingTrivia ) ;
177184 if ( ! fileHeader . Any ( ) )
178185 {
179186 return newSyntaxRoot ;
@@ -270,9 +277,10 @@ private static SyntaxNode AddUsingsToNamespace(SyntaxNode newSyntaxRoot, UsingsH
270277
271278 var groupedUsings = usingsHelper . GenerateGroupedUsings ( usingsHelper . RootSpan , usingsIndentation , withTrailingBlankLine , qualifyNames : false ) ;
272279 groupedUsings = groupedUsings . AddRange ( rootNamespace . Usings ) ;
273- var newRootNamespace = rootNamespace . WithUsings ( groupedUsings ) ;
274280
281+ var newRootNamespace = rootNamespace . WithUsings ( groupedUsings ) ;
275282 newSyntaxRoot = newSyntaxRoot . ReplaceNode ( rootNamespace , newRootNamespace ) ;
283+
276284 return newSyntaxRoot ;
277285 }
278286
@@ -284,6 +292,52 @@ private static SyntaxNode AddUsingsToCompilationRoot(SyntaxNode newSyntaxRoot, U
284292 var groupedUsings = usingsHelper . GenerateGroupedUsings ( usingsHelper . RootSpan , usingsIndentation , withTrailingBlankLine , qualifyNames : true ) ;
285293 groupedUsings = groupedUsings . AddRange ( newCompilationUnit . Usings ) ;
286294 newSyntaxRoot = newCompilationUnit . WithUsings ( groupedUsings ) ;
295+
296+ return newSyntaxRoot ;
297+ }
298+
299+ private static SyntaxNode StripMultipleBlankLines ( SyntaxNode syntaxRoot )
300+ {
301+ var replaceMap = new Dictionary < SyntaxToken , SyntaxToken > ( ) ;
302+
303+ var usingDirectives = syntaxRoot . GetAnnotatedNodes ( UsingCodeFixAnnotation ) . Cast < UsingDirectiveSyntax > ( ) ;
304+
305+ foreach ( var usingDirective in usingDirectives )
306+ {
307+ var nextToken = usingDirective . SemicolonToken . GetNextToken ( true ) ;
308+
309+ // start at -1 to compensate for the always present end-of-line.
310+ var count = - 1 ;
311+
312+ // count the blanks lines at the end of the using statement.
313+ foreach ( var trivia in usingDirective . SemicolonToken . TrailingTrivia . Reverse ( ) )
314+ {
315+ if ( ! trivia . IsKind ( SyntaxKind . EndOfLineTrivia ) )
316+ {
317+ break ;
318+ }
319+
320+ count ++ ;
321+ }
322+
323+ // count the blank lines at the start of the next token
324+ foreach ( var trivia in nextToken . LeadingTrivia )
325+ {
326+ if ( ! trivia . IsKind ( SyntaxKind . EndOfLineTrivia ) )
327+ {
328+ break ;
329+ }
330+
331+ count ++ ;
332+ }
333+
334+ if ( count > 1 )
335+ {
336+ replaceMap [ nextToken ] = nextToken . WithLeadingTrivia ( nextToken . LeadingTrivia . Skip ( count - 1 ) ) ;
337+ }
338+ }
339+
340+ var newSyntaxRoot = syntaxRoot . ReplaceTokens ( replaceMap . Keys , ( original , rewritten ) => replaceMap [ original ] ) ;
287341 return newSyntaxRoot ;
288342 }
289343
@@ -478,7 +532,7 @@ public SyntaxList<UsingDirectiveSyntax> GenerateGroupedUsings(List<UsingDirectiv
478532 return SyntaxFactory . List ( usingList ) ;
479533 }
480534
481- internal static List < SyntaxTrivia > GetFileHeader ( List < SyntaxTrivia > newLeadingTrivia )
535+ internal static List < SyntaxTrivia > GetFileHeader ( SyntaxTriviaList newLeadingTrivia )
482536 {
483537 var onBlankLine = false ;
484538 var hasHeader = false ;
@@ -527,10 +581,10 @@ internal static List<SyntaxTrivia> GetFileHeader(List<SyntaxTrivia> newLeadingTr
527581 return hasHeader ? fileHeader : new List < SyntaxTrivia > ( ) ;
528582 }
529583
530- private static List < SyntaxTrivia > StripFileHeader ( List < SyntaxTrivia > newLeadingTrivia )
584+ private static List < SyntaxTrivia > StripFileHeader ( SyntaxTriviaList leadingTrivia )
531585 {
532- var fileHeader = GetFileHeader ( newLeadingTrivia ) ;
533- return newLeadingTrivia . Skip ( fileHeader . Count ) . ToList ( ) ;
586+ var fileHeader = GetFileHeader ( leadingTrivia ) ;
587+ return leadingTrivia . Skip ( fileHeader . Count ) . ToList ( ) ;
534588 }
535589
536590 private List < UsingDirectiveSyntax > GenerateUsings ( Dictionary < DirectiveSpan , List < UsingDirectiveSyntax > > usingsGroup , DirectiveSpan directiveSpan , string indentation , List < SyntaxTrivia > triviaToMove , bool qualifyNames )
@@ -564,19 +618,20 @@ private List<UsingDirectiveSyntax> GenerateUsings(List<UsingDirectiveSyntax> usi
564618 currentUsing = this . QualifyUsingDirective ( currentUsing ) ;
565619 }
566620
567- triviaToMove . AddRange ( currentUsing . GetLeadingTrivia ( ) . Where ( tr => tr . IsDirective || tr . IsKind ( SyntaxKind . DisabledTextTrivia ) ) ) ;
568-
569- // preserve leading trivia (excluding directive trivia), indenting each line as appropriate
570- var newLeadingTrivia = currentUsing
571- . GetLeadingTrivia ( )
572- . Where ( tr => ! tr . IsDirective && ! tr . IsKind ( SyntaxKind . DisabledTextTrivia ) )
573- . ToList ( ) ;
574-
575- if ( i == 0 )
621+ // when there is a directive trivia, add it (and any trivia before it) to the triviaToMove collection.
622+ var leadingTrivia = ( i == 0 ) ? StripFileHeader ( currentUsing . GetLeadingTrivia ( ) ) : currentUsing . GetLeadingTrivia ( ) . ToList ( ) ;
623+ for ( var m = leadingTrivia . Count - 1 ; m >= 0 ; m -- )
576624 {
577- newLeadingTrivia = StripFileHeader ( newLeadingTrivia ) ;
625+ if ( leadingTrivia [ m ] . IsDirective )
626+ {
627+ triviaToMove . AddRange ( leadingTrivia . Take ( m + 1 ) ) ;
628+ break ;
629+ }
578630 }
579631
632+ // preserve leading trivia (excluding directive trivia), indenting each line as appropriate
633+ var newLeadingTrivia = leadingTrivia . Except ( triviaToMove ) . ToList ( ) ;
634+
580635 // strip any leading whitespace on each line (and also all blank lines)
581636 var k = 0 ;
582637 var startOfLine = true ;
@@ -626,7 +681,10 @@ private List<UsingDirectiveSyntax> GenerateUsings(List<UsingDirectiveSyntax> usi
626681 newTrailingTrivia = newTrailingTrivia . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
627682 }
628683
629- var processedUsing = currentUsing . WithLeadingTrivia ( newLeadingTrivia ) . WithTrailingTrivia ( newTrailingTrivia ) ;
684+ var processedUsing = currentUsing
685+ . WithLeadingTrivia ( newLeadingTrivia )
686+ . WithTrailingTrivia ( newTrailingTrivia )
687+ . WithAdditionalAnnotations ( UsingCodeFixAnnotation ) ;
630688
631689 // filter duplicate using declarations, preferring to keep the one with an alias
632690 var existingUsing = result . Find ( u => string . Equals ( u . Name . ToUnaliasedString ( ) , processedUsing . Name . ToUnaliasedString ( ) , StringComparison . Ordinal ) ) ;
0 commit comments