Skip to content

Commit 49986c4

Browse files
committed
Fix handling of trivia before first using directive
Fixes #2363
1 parent 04deed8 commit 49986c4

3 files changed

Lines changed: 142 additions & 4 deletions

File tree

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,14 @@ 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+
var previousIsEndOfLine = false;
177177
for (var m = leadingTrivia.Count - 1; m >= 0; m--)
178178
{
179179
if (leadingTrivia[m].IsDirective)
180180
{
181-
triviaToMove.InsertRange(0, leadingTrivia.Take(m + 1));
181+
// When a directive is followed by a blank line, keep the blank line with the directive.
182+
int takeCount = previousIsEndOfLine ? m + 2 : m + 1;
183+
triviaToMove.InsertRange(0, leadingTrivia.Take(takeCount));
182184
break;
183185
}
184186

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/SA1200UnitTests.cs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,142 @@ namespace TestNamespace
128128
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
129129
}
130130

131+
[Fact]
132+
[WorkItem(2363, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2363")]
133+
public async Task TestInvalidUsingStatementsWithFileHeaderTriviaAsync()
134+
{
135+
var testCode = @"// Some comment
136+
using System;
137+
using System.Threading;
138+
139+
namespace TestNamespace
140+
{
141+
}
142+
";
143+
144+
var fixedTestCode = @"// Some comment
145+
namespace TestNamespace
146+
{
147+
using System;
148+
using System.Threading;
149+
}
150+
";
151+
152+
DiagnosticResult[] expectedResults =
153+
{
154+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorInside).WithLocation(2, 1),
155+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorInside).WithLocation(3, 1),
156+
};
157+
158+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
159+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
160+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
161+
}
162+
163+
[Fact]
164+
[WorkItem(2363, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2363")]
165+
public async Task TestInvalidUsingStatementsWithSeparatedFileHeaderTriviaAsync()
166+
{
167+
var testCode = @"// Some comment
168+
169+
using System;
170+
using System.Threading;
171+
172+
namespace TestNamespace
173+
{
174+
}
175+
";
176+
177+
var fixedTestCode = @"// Some comment
178+
179+
namespace TestNamespace
180+
{
181+
using System;
182+
using System.Threading;
183+
}
184+
";
185+
186+
DiagnosticResult[] expectedResults =
187+
{
188+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorInside).WithLocation(3, 1),
189+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorInside).WithLocation(4, 1),
190+
};
191+
192+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
193+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
194+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
195+
}
196+
197+
[Fact]
198+
[WorkItem(2363, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2363")]
199+
public async Task TestInvalidUsingStatementsWithTriviaAsync()
200+
{
201+
var testCode = @"
202+
// Some comment
203+
using System;
204+
using System.Threading;
205+
206+
namespace TestNamespace
207+
{
208+
}
209+
";
210+
211+
var fixedTestCode = @"
212+
namespace TestNamespace
213+
{
214+
// Some comment
215+
using System;
216+
using System.Threading;
217+
}
218+
";
219+
220+
DiagnosticResult[] expectedResults =
221+
{
222+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorInside).WithLocation(3, 1),
223+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorInside).WithLocation(4, 1),
224+
};
225+
226+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
227+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
228+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
229+
}
230+
231+
[Fact]
232+
[WorkItem(2363, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2363")]
233+
public async Task TestInvalidUsingStatementsWithHeaderAndTriviaAsync()
234+
{
235+
var testCode = @"// Copyright notice here
236+
237+
// Some comment
238+
using System;
239+
using System.Threading;
240+
241+
namespace TestNamespace
242+
{
243+
}
244+
";
245+
246+
var fixedTestCode = @"// Copyright notice here
247+
248+
namespace TestNamespace
249+
{
250+
// Some comment
251+
using System;
252+
using System.Threading;
253+
}
254+
";
255+
256+
DiagnosticResult[] expectedResults =
257+
{
258+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorInside).WithLocation(4, 1),
259+
this.CSharpDiagnostic(SA1200UsingDirectivesMustBePlacedCorrectly.DescriptorInside).WithLocation(5, 1),
260+
};
261+
262+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedResults, CancellationToken.None).ConfigureAwait(false);
263+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
264+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
265+
}
266+
131267
/// <inheritdoc/>
132268
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
133269
{

0 commit comments

Comments
 (0)