55using System . Collections ;
66using System . Collections . Generic ;
77using System . ComponentModel . Composition ;
8+ using System . Diagnostics ;
89using System . Linq ;
910using System . Threading ;
1011using System . Threading . Tasks ;
@@ -80,8 +81,8 @@ private SyntaxNode ProcessCore<TNode>(SyntaxNode syntaxRoot, TNode node) where T
8081 return syntaxRoot ;
8182 }
8283
83- SyntaxTriviaList newTriviaList ;
84- if ( ! TryGetNewLeadingTrivia ( node , out newTriviaList ) )
84+ var newTriviaList = GetNewLeadingTrivia ( node ) ;
85+ if ( newTriviaList == node . GetLeadingTrivia ( ) )
8586 {
8687 return syntaxRoot ;
8788 }
@@ -93,69 +94,104 @@ private SyntaxNode ProcessCore<TNode>(SyntaxNode syntaxRoot, TNode node) where T
9394 /// Get the new leading trivia list that will add the double blank line that we are looking
9495 /// for.
9596 /// </summary>
96- private bool TryGetNewLeadingTrivia ( SyntaxNode node , out SyntaxTriviaList newTriviaList )
97+ private SyntaxTriviaList GetNewLeadingTrivia ( SyntaxNode node )
9798 {
98- var newLineTrivia = SyntaxUtil . GetBestNewLineTrivia ( node ) ;
9999 var list = node . GetLeadingTrivia ( ) ;
100- var index = list . Count - 1 ;
100+ var searchIndex = 0 ;
101+ var newLineTrivia = SyntaxUtil . GetBestNewLineTrivia ( node ) ;
102+ var prev = node . FindPreviousNodeInParent ( ) ;
101103
102- MoveBackwardsPastWhitespace ( list , ref index ) ;
103- if ( index < 0 || ! list [ index ] . IsAnyEndOfLine ( ) )
104+ if ( prev == null )
104105 {
105- // There is no newline before the using at all. Add a double newline to
106- // get the blank we are looking for
107- newTriviaList = list . InsertRange ( index + 1 , new [ ] { newLineTrivia , newLineTrivia } ) ;
108- return true ;
106+ if ( node . Span . Start == 0 )
107+ {
108+ // First item in the file. Do nothing for this case.
109+ return list ;
110+ }
111+ }
112+ else
113+ {
114+ // If there is no new line in the trailing trivia of the previous node then need to add
115+ // one to put this node on the next line.
116+ if ( prev . GetTrailingTrivia ( ) . Count == 0 || ! prev . GetTrailingTrivia ( ) . Last ( ) . IsAnyEndOfLine ( ) )
117+ {
118+ list = list . Insert ( 0 , newLineTrivia ) ;
119+ searchIndex = 1 ;
120+ }
109121 }
110122
111- var wasDirective = list [ index ] . IsDirective ;
112- index -- ;
113-
114- // Move past any directives that are above the token. The newline needs to
115- // be above them.
116- while ( index >= 0 && list [ index ] . IsDirective )
123+ // Ensure there are blank above #pragma directives here. This is an attempt to maintain compatibility
124+ // with the original design of this rule which had special spacing rules for #pragma. No reason
125+ // was given for the special casing, only tests.
126+ if ( searchIndex < list . Count && list [ 0 ] . IsKind ( SyntaxKind . PragmaWarningDirectiveTrivia ) && list [ 0 ] . FullSpan . Start != 0 )
117127 {
118- index -- ;
128+ list = list . Insert ( searchIndex , newLineTrivia ) ;
129+ searchIndex ++ ;
119130 }
120131
121- if ( wasDirective )
132+ EnsureHasBlankLineAtEnd ( ref list , searchIndex , newLineTrivia ) ;
133+
134+ return list ;
135+ }
136+
137+ /// <summary>
138+ /// Ensure the trivia list has a blank line at the end. Both the second to last
139+ /// and final line may contain spaces.
140+ ///
141+ /// Note: This function assumes the trivia token before <param name="startIndex" />
142+ /// is an end of line trivia.
143+ /// </summary>
144+ private static void EnsureHasBlankLineAtEnd ( ref SyntaxTriviaList list , int startIndex , SyntaxTrivia newLineTrivia )
145+ {
146+ const int StateNone = 0 ;
147+ const int StateEol = 1 ;
148+ const int StateBlankLine = 2 ;
149+
150+ var state = StateEol ;
151+ var index = startIndex ;
152+ var eolIndex = startIndex - 1 ;
153+
154+ while ( index < list . Count )
122155 {
123- // There was a directive above the using and index now points directly before
124- // that. This token must be a new line.
125- if ( index < 0 || ! list [ index ] . IsKind ( SyntaxKind . EndOfLineTrivia ) )
156+ var current = list [ index ] ;
157+ if ( current . IsKind ( SyntaxKind . WhitespaceTrivia ) )
126158 {
127- newTriviaList = list . Insert ( index + 1 , newLineTrivia ) ;
128- return true ;
159+ index ++ ;
160+ continue ;
129161 }
130162
131- index -- ;
132- }
133-
134- // In the logical line above the using. Need to see <blank><eol> in order for the
135- // using to be correct
136- var insertIndex = index + 1 ;
137- MoveBackwardsPastWhitespace ( list , ref index ) ;
138- if ( index < 0 || ! list [ index ] . IsAnyEndOfLine ( ) )
139- {
140- // If this is the first item in the file then there is no need for a double
141- // blank line.
142- if ( index >= 0 || node . FullSpan . Start != 0 )
163+ var isStateAnyEol = ( state == StateEol || state == StateBlankLine ) ;
164+ if ( isStateAnyEol && current . IsKind ( SyntaxKind . EndOfLineTrivia ) )
143165 {
144- newTriviaList = list . Insert ( insertIndex , newLineTrivia ) ;
145- return true ;
166+ state = StateBlankLine ;
167+ }
168+ else if ( current . IsAnyEndOfLine ( ) )
169+ {
170+ eolIndex = index ;
171+ state = StateEol ;
172+ }
173+ else
174+ {
175+ state = StateNone ;
146176 }
147- }
148177
149- // The using is well formed so there is no work to be done.
150- newTriviaList = SyntaxTriviaList . Empty ;
151- return false ;
152- }
178+ index ++ ;
179+ }
153180
154- private static void MoveBackwardsPastWhitespace ( SyntaxTriviaList list , ref int index )
155- {
156- while ( index >= 0 && list [ index ] . IsKind ( SyntaxKind . WhitespaceTrivia ) )
181+ switch ( state )
157182 {
158- index -- ;
183+ case StateNone :
184+ list = list . InsertRange ( list . Count , new [ ] { newLineTrivia , newLineTrivia } ) ;
185+ break ;
186+ case StateEol :
187+ list = list . Insert ( eolIndex + 1 , newLineTrivia ) ;
188+ break ;
189+ case StateBlankLine :
190+ // Nothing to do.
191+ break ;
192+ default :
193+ Debug . Assert ( false ) ;
194+ break ;
159195 }
160196 }
161197 }
0 commit comments