Skip to content

Commit f2d2cc3

Browse files
authored
Merge pull request #2435 from sharwell/fix-using-trivia
Fix handling of trivia before first using directive
2 parents ca540f2 + bde958b commit f2d2cc3

5 files changed

Lines changed: 392 additions & 5 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.UsingsSorter.cs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,20 +173,24 @@ private List<UsingDirectiveSyntax> GenerateUsings(List<UsingDirectiveSyntax> usi
173173

174174
// when there is a directive trivia, add it (and any trivia before it) to the triviaToMove collection.
175175
// when there are leading blank lines for the first entry, add them to the triviaToMove collection.
176-
var previousIsEndOfLine = true;
176+
int triviaToMoveCount = triviaToMove.Count;
177+
var previousIsEndOfLine = false;
177178
for (var m = leadingTrivia.Count - 1; m >= 0; m--)
178179
{
179180
if (leadingTrivia[m].IsDirective)
180181
{
181-
triviaToMove.InsertRange(0, leadingTrivia.Take(m + 1));
182+
// When a directive is followed by a blank line, keep the blank line with the directive.
183+
int takeCount = previousIsEndOfLine ? m + 2 : m + 1;
184+
triviaToMove.InsertRange(0, leadingTrivia.Take(takeCount));
182185
break;
183186
}
184187

185188
if ((i == 0) && leadingTrivia[m].IsKind(SyntaxKind.EndOfLineTrivia))
186189
{
187190
if (previousIsEndOfLine)
188191
{
189-
triviaToMove.Insert(0, leadingTrivia[m]);
192+
triviaToMove.InsertRange(0, leadingTrivia.Take(m + 2));
193+
break;
190194
}
191195

192196
previousIsEndOfLine = true;
@@ -200,6 +204,56 @@ private List<UsingDirectiveSyntax> GenerateUsings(List<UsingDirectiveSyntax> usi
200204
// preserve leading trivia (excluding directive trivia), indenting each line as appropriate
201205
var newLeadingTrivia = leadingTrivia.Except(triviaToMove).ToList();
202206

207+
// indent the triviaToMove if necessary so it behaves correctly later
208+
bool atStartOfLine = triviaToMoveCount == 0 || triviaToMove.Last().HasBuiltinEndLine();
209+
for (int m = triviaToMoveCount; m < triviaToMove.Count; m++)
210+
{
211+
bool currentAtStartOfLine = atStartOfLine;
212+
atStartOfLine = triviaToMove[m].HasBuiltinEndLine();
213+
if (!currentAtStartOfLine)
214+
{
215+
continue;
216+
}
217+
218+
if (triviaToMove[m].IsKind(SyntaxKind.EndOfLineTrivia))
219+
{
220+
// This is a blank line; indenting it would only add trailing whitespace
221+
continue;
222+
}
223+
224+
if (triviaToMove[m].IsDirective)
225+
{
226+
// Only #region and #endregion directives get indented
227+
if (!triviaToMove[m].IsKind(SyntaxKind.RegionDirectiveTrivia) && !triviaToMove[m].IsKind(SyntaxKind.EndRegionDirectiveTrivia))
228+
{
229+
// This is a preprocessor line that doesn't need to be indented
230+
continue;
231+
}
232+
}
233+
234+
if (triviaToMove[m].IsKind(SyntaxKind.DisabledTextTrivia))
235+
{
236+
// This is text in a '#if false' block; just ignore it
237+
continue;
238+
}
239+
240+
if (string.IsNullOrEmpty(indentation))
241+
{
242+
if (triviaToMove[m].IsKind(SyntaxKind.WhitespaceTrivia))
243+
{
244+
// Remove the trivia and analyze the current position again
245+
triviaToMove.RemoveAt(m);
246+
m--;
247+
atStartOfLine = true;
248+
}
249+
}
250+
else
251+
{
252+
triviaToMove.Insert(m, SyntaxFactory.Whitespace(indentation));
253+
m++;
254+
}
255+
}
256+
203257
// strip any leading whitespace on each line (and also blank lines)
204258
var k = 0;
205259
var startOfLine = true;

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/OrderingRules/UsingCodeFixProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ private static SyntaxNode StripMultipleBlankLines(SyntaxNode syntaxRoot)
352352

353353
private static ImmutableArray<SyntaxTrivia> GetFileHeader(SyntaxNode syntaxRoot)
354354
{
355-
var onBlankLine = false;
355+
var onBlankLine = true;
356356
var hasHeader = false;
357357
var fileHeaderBuilder = ImmutableArray.CreateBuilder<SyntaxTrivia>();
358358

@@ -369,14 +369,14 @@ private static ImmutableArray<SyntaxTrivia> GetFileHeader(SyntaxNode syntaxRoot)
369369
case SyntaxKind.MultiLineCommentTrivia:
370370
fileHeaderBuilder.Add(firstTokenLeadingTrivia[i]);
371371
onBlankLine = false;
372-
hasHeader = true;
373372
break;
374373

375374
case SyntaxKind.WhitespaceTrivia:
376375
fileHeaderBuilder.Add(firstTokenLeadingTrivia[i]);
377376
break;
378377

379378
case SyntaxKind.EndOfLineTrivia:
379+
hasHeader = true;
380380
fileHeaderBuilder.Add(firstTokenLeadingTrivia[i]);
381381

382382
if (onBlankLine)

StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1200OutsideNamespaceUnitTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,14 @@ public async Task TestInvalidSimplifiedUsingStatementsInExtensionNamespaceAsync(
129129
{
130130
using System.Threading;
131131
using Reflection;
132+
using Assembly = Reflection.Assembly;
133+
using List = Collections.Generic.IList<int>;
132134
}
133135
";
134136
var fixedTestCode = @"using System.Reflection;
135137
using System.Threading;
138+
using Assembly = System.Reflection.Assembly;
139+
using List = System.Collections.Generic.IList<int>;
136140
137141
namespace System.MyExtension
138142
{
@@ -143,6 +147,8 @@ namespace System.MyExtension
143147
{
144148
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorOutside).WithLocation(3, 5),
145149
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorOutside).WithLocation(4, 5),
150+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorOutside).WithLocation(5, 5),
151+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorOutside).WithLocation(6, 5),
146152
};
147153

148154
await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
@@ -264,6 +270,46 @@ namespace TestNamespace
264270
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
265271
}
266272

273+
[Fact]
274+
public async Task TestFileHeaderIsProperlyPreservedWhenMovingUsingStatementsWithCommentsAsync()
275+
{
276+
var testCode = @"// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
277+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
278+
279+
namespace TestNamespace
280+
{
281+
// Separated Comment
282+
283+
using System.Collections;
284+
// Comment
285+
using System;
286+
}
287+
";
288+
var fixedTestCode = @"// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
289+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
290+
291+
// Separated Comment
292+
293+
// Comment
294+
using System;
295+
using System.Collections;
296+
297+
namespace TestNamespace
298+
{
299+
}
300+
";
301+
302+
DiagnosticResult[] expectedResults =
303+
{
304+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorOutside).WithLocation(8, 5),
305+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorOutside).WithLocation(10, 5),
306+
};
307+
308+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
309+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
310+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
311+
}
312+
267313
/// <inheritdoc/>
268314
protected override string GetSettings()
269315
{

0 commit comments

Comments
 (0)