Skip to content

Commit 92a3ade

Browse files
committed
Merge pull request #3 from vweijsters/fix-1897
Fixed issues with file headers in UsingCodeFixProvider
2 parents 35d8ca9 + d4291ce commit 92a3ade

2 files changed

Lines changed: 74 additions & 12 deletions

File tree

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ namespace StyleCop.Analyzers.OrderingRules
2727
internal sealed class UsingCodeFixProvider : CodeFixProvider
2828
{
2929
private static readonly List<UsingDirectiveSyntax> EmptyUsingsList = new List<UsingDirectiveSyntax>();
30-
private static readonly SyntaxAnnotation UsingCodeFixAnnotation = new SyntaxAnnotation(nameof(UsingCodeFixProvider));
30+
private static readonly SyntaxAnnotation UsingCodeFixAnnotation = new SyntaxAnnotation(nameof(UsingCodeFixAnnotation));
31+
private static readonly SyntaxAnnotation FileHeaderStrippedAnnotation = new SyntaxAnnotation(nameof(FileHeaderStrippedAnnotation));
3132

3233
/// <inheritdoc/>
3334
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
@@ -175,6 +176,13 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
175176

176177
private static SyntaxNode ReAddFileHeader(SyntaxNode syntaxRoot, SyntaxNode newSyntaxRoot)
177178
{
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+
178186
var oldFirstToken = syntaxRoot.GetFirstToken();
179187
if (!oldFirstToken.HasLeadingTrivia)
180188
{
@@ -584,12 +592,6 @@ internal static List<SyntaxTrivia> GetFileHeader(SyntaxTriviaList newLeadingTriv
584592
return hasHeader ? fileHeader : new List<SyntaxTrivia>();
585593
}
586594

587-
private static List<SyntaxTrivia> StripFileHeader(SyntaxTriviaList leadingTrivia)
588-
{
589-
var fileHeader = GetFileHeader(leadingTrivia);
590-
return leadingTrivia.Skip(fileHeader.Count).ToList();
591-
}
592-
593595
private List<UsingDirectiveSyntax> GenerateUsings(Dictionary<DirectiveSpan, List<UsingDirectiveSyntax>> usingsGroup, DirectiveSpan directiveSpan, string indentation, List<SyntaxTrivia> triviaToMove, bool qualifyNames)
594596
{
595597
List<UsingDirectiveSyntax> result = new List<UsingDirectiveSyntax>();
@@ -614,15 +616,29 @@ private List<UsingDirectiveSyntax> GenerateUsings(List<UsingDirectiveSyntax> usi
614616

615617
for (var i = 0; i < usingsList.Count; i++)
616618
{
619+
bool strippedFileHeader = false;
617620
var currentUsing = usingsList[i];
618621

619622
if (qualifyNames)
620623
{
621624
currentUsing = this.QualifyUsingDirective(currentUsing);
622625
}
623626

627+
// strip the file header, if the using is the first node in the source file.
628+
List<SyntaxTrivia> leadingTrivia;
629+
if ((i == 0) && currentUsing.GetFirstToken().GetPreviousToken().IsMissingOrDefault())
630+
{
631+
var trivia = currentUsing.GetLeadingTrivia();
632+
var fileHeader = GetFileHeader(trivia);
633+
leadingTrivia = trivia.Skip(fileHeader.Count).ToList();
634+
strippedFileHeader = fileHeader.Count > 0;
635+
}
636+
else
637+
{
638+
leadingTrivia = currentUsing.GetLeadingTrivia().ToList();
639+
}
640+
624641
// when there is a directive trivia, add it (and any trivia before it) to the triviaToMove collection.
625-
var leadingTrivia = (i == 0) ? StripFileHeader(currentUsing.GetLeadingTrivia()) : currentUsing.GetLeadingTrivia().ToList();
626642
for (var m = leadingTrivia.Count - 1; m >= 0; m--)
627643
{
628644
if (leadingTrivia[m].IsDirective)
@@ -689,6 +705,11 @@ private List<UsingDirectiveSyntax> GenerateUsings(List<UsingDirectiveSyntax> usi
689705
.WithTrailingTrivia(newTrailingTrivia)
690706
.WithAdditionalAnnotations(UsingCodeFixAnnotation);
691707

708+
if (strippedFileHeader)
709+
{
710+
processedUsing = processedUsing.WithAdditionalAnnotations(FileHeaderStrippedAnnotation);
711+
}
712+
692713
// filter duplicate using declarations, preferring to keep the one with an alias
693714
var existingUsing = result.Find(u => string.Equals(u.Name.ToNormalizedString(), processedUsing.Name.ToNormalizedString(), StringComparison.Ordinal));
694715
if (existingUsing != null)

StyleCop.Analyzers/StyleCop.Analyzers.Test/OrderingRules/SA1210UnitTests.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,10 +344,6 @@ namespace Newtonsoft.Json
344344
// Copyright (c) FooCorporation. All rights reserved.
345345
// </copyright>
346346
347-
// <copyright file=""VoiceCommandService.cs"" company=""Foo Corporation"">
348-
// Copyright (c) FooCorporation. All rights reserved.
349-
// </copyright>
350-
351347
namespace Foo.Garage.XYZ
352348
{
353349
using System;
@@ -360,6 +356,7 @@ namespace Newtonsoft.Json
360356
}
361357
";
362358

359+
// The same diagnostic is reported multiple times due to a bug in Roslyn 1.0
363360
DiagnosticResult[] expected =
364361
{
365362
this.CSharpDiagnostic().WithLocation(8, 5),
@@ -372,6 +369,50 @@ namespace Newtonsoft.Json
372369
await this.VerifyCSharpFixAsync(testCode, fixedTestCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
373370
}
374371

372+
/// <summary>
373+
/// Verifies that the first using statement will preserve its leading comment.
374+
/// </summary>
375+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
376+
[Fact]
377+
public async Task TestLeadingCommentForFirstUsingInNamespaceIsPreservedAsync()
378+
{
379+
var testCode = @"namespace TestNamespace
380+
{
381+
// With test comment
382+
using System;
383+
using TestNamespace;
384+
using Newtonsoft.Json;
385+
}
386+
387+
namespace Newtonsoft.Json
388+
{
389+
}
390+
";
391+
392+
var fixedTestCode = @"namespace TestNamespace
393+
{
394+
// With test comment
395+
using System;
396+
using Newtonsoft.Json;
397+
using TestNamespace;
398+
}
399+
400+
namespace Newtonsoft.Json
401+
{
402+
}
403+
";
404+
405+
// The same diagnostic is reported multiple times due to a bug in Roslyn 1.0
406+
DiagnosticResult[] expected =
407+
{
408+
this.CSharpDiagnostic().WithLocation(5, 5),
409+
};
410+
411+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
412+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
413+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
414+
}
415+
375416
/// <inheritdoc/>
376417
protected override IEnumerable<string> GetDisabledDiagnostics()
377418
{

0 commit comments

Comments
 (0)