@@ -28,7 +28,6 @@ internal sealed class UsingCodeFixProvider : CodeFixProvider
2828 {
2929 private static readonly List < UsingDirectiveSyntax > EmptyUsingsList = new List < UsingDirectiveSyntax > ( ) ;
3030 private static readonly SyntaxAnnotation UsingCodeFixAnnotation = new SyntaxAnnotation ( nameof ( UsingCodeFixAnnotation ) ) ;
31- private static readonly SyntaxAnnotation FileHeaderStrippedAnnotation = new SyntaxAnnotation ( nameof ( FileHeaderStrippedAnnotation ) ) ;
3231
3332 /// <inheritdoc/>
3433 public override ImmutableArray < string > FixableDiagnosticIds { get ; } =
@@ -73,13 +72,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
7372
7473 private static async Task < Document > GetTransformedDocumentAsync ( Document document , SyntaxNode syntaxRoot , CancellationToken cancellationToken )
7574 {
75+ var fileHeader = GetFileHeader ( syntaxRoot ) ;
7676 var compilationUnit = ( CompilationUnitSyntax ) syntaxRoot ;
7777
7878 var settings = SettingsHelper . GetStyleCopSettings ( document . Project . AnalyzerOptions , cancellationToken ) ;
7979 var semanticModel = await document . GetSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
8080 var indentationOptions = IndentationOptions . FromDocument ( document ) ;
8181
82- var usingsHelper = new UsingsHelper ( settings , semanticModel , document , compilationUnit ) ;
82+ var usingsHelper = new UsingsHelper ( settings , semanticModel , compilationUnit , fileHeader ) ;
8383 var namespaceCount = CountNamespaces ( compilationUnit . Members ) ;
8484
8585 // Only move using declarations inside the namespace when
@@ -88,8 +88,7 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
8888 // - OrderingSettings.UsingDirectivesPlacement is set to InsideNamespace
8989 UsingDirectivesPlacement usingDirectivesPlacement ;
9090
91- OrderingSettings orderingSettings = SettingsHelper . GetStyleCopSettings ( document . Project . AnalyzerOptions , cancellationToken ) . OrderingRules ;
92- switch ( orderingSettings . UsingDirectivesPlacement )
91+ switch ( settings . OrderingRules . UsingDirectivesPlacement )
9392 {
9493 case UsingDirectivesPlacement . InsideNamespace :
9594 if ( compilationUnit . AttributeLists . Any ( )
@@ -153,7 +152,7 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
153152
154153 BuildReplaceMapForConditionalDirectives ( usingsHelper , replaceMap , indentationOptions , usingsHelper . RootSpan ) ;
155154
156- var usingSyntaxRewriter = new UsingSyntaxRewriter ( stripList , replaceMap ) ;
155+ var usingSyntaxRewriter = new UsingSyntaxRewriter ( stripList , replaceMap , fileHeader ) ;
157156 var newSyntaxRoot = usingSyntaxRewriter . Visit ( syntaxRoot ) ;
158157
159158 if ( usingDirectivesPlacement == UsingDirectivesPlacement . InsideNamespace )
@@ -167,39 +166,13 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
167166
168167 // Final cleanup
169168 newSyntaxRoot = StripMultipleBlankLines ( newSyntaxRoot ) ;
170- newSyntaxRoot = ReAddFileHeader ( syntaxRoot , newSyntaxRoot ) ;
169+ newSyntaxRoot = ReAddFileHeader ( newSyntaxRoot , fileHeader ) ;
171170
172171 var newDocument = document . WithSyntaxRoot ( newSyntaxRoot . WithoutFormatting ( ) ) ;
173172
174173 return newDocument ;
175174 }
176175
177- private static SyntaxNode ReAddFileHeader ( SyntaxNode syntaxRoot , SyntaxNode newSyntaxRoot )
178- {
179- // Only re-add the file header if it was stripped.
180- var usingDirectives = newSyntaxRoot . GetAnnotatedNodes ( FileHeaderStrippedAnnotation ) ;
181- if ( ! usingDirectives . Any ( ) )
182- {
183- return newSyntaxRoot ;
184- }
185-
186- var oldFirstToken = syntaxRoot . GetFirstToken ( ) ;
187- if ( ! oldFirstToken . HasLeadingTrivia )
188- {
189- return newSyntaxRoot ;
190- }
191-
192- var fileHeader = UsingsHelper . GetFileHeader ( oldFirstToken . LeadingTrivia ) ;
193- if ( ! fileHeader . Any ( ) )
194- {
195- return newSyntaxRoot ;
196- }
197-
198- var newFirstToken = newSyntaxRoot . GetFirstToken ( ) ;
199- var newLeadingTrivia = newFirstToken . LeadingTrivia . InsertRange ( 0 , fileHeader ) ;
200- return newSyntaxRoot . ReplaceToken ( newFirstToken , newFirstToken . WithLeadingTrivia ( newLeadingTrivia ) ) ;
201- }
202-
203176 private static int CountNamespaces ( SyntaxList < MemberDeclarationSyntax > members )
204177 {
205178 var result = 0 ;
@@ -350,6 +323,73 @@ private static SyntaxNode StripMultipleBlankLines(SyntaxNode syntaxRoot)
350323 return newSyntaxRoot ;
351324 }
352325
326+ private static ImmutableArray < SyntaxTrivia > GetFileHeader ( SyntaxNode syntaxRoot )
327+ {
328+ var onBlankLine = false ;
329+ var hasHeader = false ;
330+ var fileHeaderBuilder = ImmutableArray . CreateBuilder < SyntaxTrivia > ( ) ;
331+
332+ var firstToken = syntaxRoot . GetFirstToken ( includeZeroWidth : true ) ;
333+ var firstTokenLeadingTrivia = firstToken . LeadingTrivia ;
334+
335+ int i ;
336+ for ( i = 0 ; i < firstTokenLeadingTrivia . Count ; i ++ )
337+ {
338+ bool done = false ;
339+ switch ( firstTokenLeadingTrivia [ i ] . Kind ( ) )
340+ {
341+ case SyntaxKind . SingleLineCommentTrivia :
342+ case SyntaxKind . MultiLineCommentTrivia :
343+ fileHeaderBuilder . Add ( firstTokenLeadingTrivia [ i ] ) ;
344+ onBlankLine = false ;
345+ hasHeader = true ;
346+ break ;
347+
348+ case SyntaxKind . WhitespaceTrivia :
349+ fileHeaderBuilder . Add ( firstTokenLeadingTrivia [ i ] ) ;
350+ break ;
351+
352+ case SyntaxKind . EndOfLineTrivia :
353+ fileHeaderBuilder . Add ( firstTokenLeadingTrivia [ i ] ) ;
354+
355+ if ( onBlankLine )
356+ {
357+ done = true ;
358+ }
359+ else
360+ {
361+ onBlankLine = true ;
362+ }
363+
364+ break ;
365+
366+ default :
367+ done = true ;
368+ break ;
369+ }
370+
371+ if ( done )
372+ {
373+ break ;
374+ }
375+ }
376+
377+ return hasHeader ? fileHeaderBuilder . ToImmutableArray ( ) : ImmutableArray . Create < SyntaxTrivia > ( ) ;
378+ }
379+
380+ private static SyntaxNode ReAddFileHeader ( SyntaxNode syntaxRoot , ImmutableArray < SyntaxTrivia > fileHeader )
381+ {
382+ if ( fileHeader . IsEmpty )
383+ {
384+ // Only re-add the file header if it was stripped.
385+ return syntaxRoot ;
386+ }
387+
388+ var firstToken = syntaxRoot . GetFirstToken ( includeZeroWidth : true ) ;
389+ var newLeadingTrivia = firstToken . LeadingTrivia . InsertRange ( 0 , fileHeader ) ;
390+ return syntaxRoot . ReplaceToken ( firstToken , firstToken . WithLeadingTrivia ( newLeadingTrivia ) ) ;
391+ }
392+
353393 private class DirectiveSpan
354394 {
355395 private List < DirectiveSpan > children = new List < DirectiveSpan > ( ) ;
@@ -447,13 +487,16 @@ private class UsingsHelper
447487 {
448488 private readonly StyleCopSettings settings ;
449489 private readonly SemanticModel semanticModel ;
490+ private readonly ImmutableArray < SyntaxTrivia > fileHeader ;
450491 private readonly DirectiveSpan conditionalDirectiveTree ;
451492 private readonly bool separateSystemDirectives ;
452493
453- public UsingsHelper ( StyleCopSettings settings , SemanticModel semanticModel , Document document , CompilationUnitSyntax compilationUnit )
494+ public UsingsHelper ( StyleCopSettings settings , SemanticModel semanticModel , CompilationUnitSyntax compilationUnit , ImmutableArray < SyntaxTrivia > fileHeader )
454495 {
455496 this . settings = settings ;
456497 this . semanticModel = semanticModel ;
498+ this . fileHeader = fileHeader ;
499+
457500 this . conditionalDirectiveTree = DirectiveSpan . BuildConditionalDirectiveTree ( compilationUnit ) ;
458501 this . separateSystemDirectives = settings . OrderingRules . SystemUsingDirectivesFirst ;
459502
@@ -543,55 +586,6 @@ public SyntaxList<UsingDirectiveSyntax> GenerateGroupedUsings(List<UsingDirectiv
543586 return SyntaxFactory . List ( usingList ) ;
544587 }
545588
546- internal static List < SyntaxTrivia > GetFileHeader ( SyntaxTriviaList newLeadingTrivia )
547- {
548- var onBlankLine = false ;
549- var hasHeader = false ;
550- var fileHeader = new List < SyntaxTrivia > ( ) ;
551- for ( var i = 0 ; i < newLeadingTrivia . Count ; i ++ )
552- {
553- bool done = false ;
554- switch ( newLeadingTrivia [ i ] . Kind ( ) )
555- {
556- case SyntaxKind . SingleLineCommentTrivia :
557- case SyntaxKind . MultiLineCommentTrivia :
558- fileHeader . Add ( newLeadingTrivia [ i ] ) ;
559- onBlankLine = false ;
560- hasHeader = true ;
561- break ;
562-
563- case SyntaxKind . WhitespaceTrivia :
564- fileHeader . Add ( newLeadingTrivia [ i ] ) ;
565- break ;
566-
567- case SyntaxKind . EndOfLineTrivia :
568- fileHeader . Add ( newLeadingTrivia [ i ] ) ;
569-
570- if ( onBlankLine )
571- {
572- done = true ;
573- }
574- else
575- {
576- onBlankLine = true ;
577- }
578-
579- break ;
580-
581- default :
582- done = true ;
583- break ;
584- }
585-
586- if ( done )
587- {
588- break ;
589- }
590- }
591-
592- return hasHeader ? fileHeader : new List < SyntaxTrivia > ( ) ;
593- }
594-
595589 private List < UsingDirectiveSyntax > GenerateUsings ( Dictionary < DirectiveSpan , List < UsingDirectiveSyntax > > usingsGroup , DirectiveSpan directiveSpan , string indentation , List < SyntaxTrivia > triviaToMove , bool qualifyNames )
596590 {
597591 List < UsingDirectiveSyntax > result = new List < UsingDirectiveSyntax > ( ) ;
@@ -616,22 +610,13 @@ private List<UsingDirectiveSyntax> GenerateUsings(List<UsingDirectiveSyntax> usi
616610
617611 for ( var i = 0 ; i < usingsList . Count ; i ++ )
618612 {
619- bool strippedFileHeader = false ;
620613 var currentUsing = usingsList [ i ] ;
621614
622- if ( qualifyNames )
623- {
624- currentUsing = this . QualifyUsingDirective ( currentUsing ) ;
625- }
626-
627615 // strip the file header, if the using is the first node in the source file.
628616 List < SyntaxTrivia > leadingTrivia ;
629617 if ( ( i == 0 ) && currentUsing . GetFirstToken ( ) . GetPreviousToken ( ) . IsMissingOrDefault ( ) )
630618 {
631- var trivia = currentUsing . GetLeadingTrivia ( ) ;
632- var fileHeader = GetFileHeader ( trivia ) ;
633- leadingTrivia = trivia . Skip ( fileHeader . Count ) . ToList ( ) ;
634- strippedFileHeader = fileHeader . Count > 0 ;
619+ leadingTrivia = currentUsing . GetLeadingTrivia ( ) . Except ( this . fileHeader ) . ToList ( ) ;
635620 }
636621 else
637622 {
@@ -700,16 +685,11 @@ private List<UsingDirectiveSyntax> GenerateUsings(List<UsingDirectiveSyntax> usi
700685 newTrailingTrivia = newTrailingTrivia . Add ( SyntaxFactory . CarriageReturnLineFeed ) ;
701686 }
702687
703- var processedUsing = currentUsing
688+ var processedUsing = ( qualifyNames ? this . QualifyUsingDirective ( currentUsing ) : currentUsing )
704689 . WithLeadingTrivia ( newLeadingTrivia )
705690 . WithTrailingTrivia ( newTrailingTrivia )
706691 . WithAdditionalAnnotations ( UsingCodeFixAnnotation ) ;
707692
708- if ( strippedFileHeader )
709- {
710- processedUsing = processedUsing . WithAdditionalAnnotations ( FileHeaderStrippedAnnotation ) ;
711- }
712-
713693 result . Add ( processedUsing ) ;
714694 }
715695
@@ -899,14 +879,16 @@ private List<UsingDirectiveSyntax> FilterRelevantUsings(Dictionary<DirectiveSpan
899879
900880 private class UsingSyntaxRewriter : CSharpSyntaxRewriter
901881 {
902- private List < UsingDirectiveSyntax > stripList ;
903- private Dictionary < UsingDirectiveSyntax , UsingDirectiveSyntax > replaceMap ;
882+ private readonly List < UsingDirectiveSyntax > stripList ;
883+ private readonly Dictionary < UsingDirectiveSyntax , UsingDirectiveSyntax > replaceMap ;
884+ private readonly ImmutableArray < SyntaxTrivia > fileHeader ;
904885 private LinkedList < SyntaxToken > tokensToStrip = new LinkedList < SyntaxToken > ( ) ;
905886
906- public UsingSyntaxRewriter ( List < UsingDirectiveSyntax > stripList , Dictionary < UsingDirectiveSyntax , UsingDirectiveSyntax > replaceMap )
887+ public UsingSyntaxRewriter ( List < UsingDirectiveSyntax > stripList , Dictionary < UsingDirectiveSyntax , UsingDirectiveSyntax > replaceMap , ImmutableArray < SyntaxTrivia > fileHeader )
907888 {
908889 this . stripList = stripList ;
909890 this . replaceMap = replaceMap ;
891+ this . fileHeader = fileHeader ;
910892 }
911893
912894 public override SyntaxNode VisitUsingDirective ( UsingDirectiveSyntax node )
@@ -951,6 +933,16 @@ public override SyntaxToken VisitToken(SyntaxToken token)
951933
952934 return base . VisitToken ( token ) ;
953935 }
936+
937+ public override SyntaxTrivia VisitTrivia ( SyntaxTrivia trivia )
938+ {
939+ if ( this . fileHeader . Contains ( trivia ) )
940+ {
941+ return default ( SyntaxTrivia ) ;
942+ }
943+
944+ return base . VisitTrivia ( trivia ) ;
945+ }
954946 }
955947
956948 private class FixAll : DocumentBasedFixAllProvider
0 commit comments