Skip to content
This repository was archived by the owner on Jul 12, 2022. It is now read-only.

Commit 4aca602

Browse files
committed
Merge pull request #143 from jaredpar/issue-83
Fix double new lines with extern directives
2 parents 3b9388d + ced83ad commit 4aca602

File tree

3 files changed

+120
-50
lines changed

3 files changed

+120
-50
lines changed

src/Microsoft.DotNet.CodeFormatting.Tests/Rules/NewLineAboveRuleTests.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ public void UsingWithOnlyDirectiveAbove()
5858
var text = @"#pragma warning disable 1591
5959
using System;";
6060

61-
var expected = @"
62-
#pragma warning disable 1591
61+
var expected = @"#pragma warning disable 1591
62+
6363
using System;";
6464

6565
Verify(text, expected);
@@ -158,8 +158,8 @@ public void TestNewLineBeforeFirstUsing03()
158158
";
159159
var expected = @"
160160
// copyright comment
161-
162161
#pragma warning disable 1591
162+
163163
using System;
164164
";
165165
Verify(text, expected);
@@ -228,6 +228,7 @@ namespace N { }
228228
using System;
229229
230230
#pragma warning disable 1591
231+
231232
namespace N { }
232233
";
233234
Verify(text, expected);
@@ -245,5 +246,23 @@ namespace N { }
245246
";
246247
Verify(text, text);
247248
}
249+
250+
[Fact]
251+
public void Issue83()
252+
{
253+
var text = @"
254+
extern alias PDB;
255+
using System;
256+
namespace N { }";
257+
258+
var expected = @"
259+
extern alias PDB;
260+
261+
using System;
262+
263+
namespace N { }";
264+
265+
Verify(text, expected);
266+
}
248267
}
249268
}

src/Microsoft.DotNet.CodeFormatting/Rules/NewLineAboveRule.cs

Lines changed: 83 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections;
66
using System.Collections.Generic;
77
using System.ComponentModel.Composition;
8+
using System.Diagnostics;
89
using System.Linq;
910
using System.Threading;
1011
using 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
}

src/Microsoft.DotNet.CodeFormatting/SyntaxUtil.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,20 @@ internal static bool IsAnyEndOfLine(this SyntaxTrivia trivia)
9191
{
9292
return trivia.IsKind(SyntaxKind.EndOfLineTrivia) || trivia.IsDirective;
9393
}
94+
95+
/// <summary>
96+
/// Find the node directly before this in the parent. Returns null in the case it
97+
/// cannot be found.
98+
/// </summary>
99+
internal static SyntaxNode FindPreviousNodeInParent(this SyntaxNode node)
100+
{
101+
var parent = node.Parent;
102+
if (parent == null)
103+
{
104+
return null;
105+
}
106+
107+
return parent.ChildNodes().Where(x => x.FullSpan.End == node.FullSpan.Start).FirstOrDefault();
108+
}
94109
}
95110
}

0 commit comments

Comments
 (0)