Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,79 @@
return compilationUnitWithSeparatorLine.ReplaceNode(firstMember, firstMember.WithPrependedLeadingTrivia(orphanedTrivia));
}

/// <summary>
/// Moves preprocessor directives (like #if/#endif) that surround using directives along with the usings.
/// If the first using has an #if directive, finds the corresponding #endif and attaches it to the last using.
/// </summary>
private static (ImmutableArray<UsingDirectiveSyntax> adjustedUsings, BaseNamespaceDeclarationSyntax adjustedContainer) MovePreprocessorDirectivesWithUsings(
ImmutableArray<UsingDirectiveSyntax> usings, BaseNamespaceDeclarationSyntax container)
{
if (usings.IsEmpty)
return (usings, container);

// Check if the first using has an #if directive in its leading trivia
var firstUsing = usings[0];
var leadingTrivia = firstUsing.GetLeadingTrivia();
var hasIfDirective = leadingTrivia.Any(t => t.IsKind(SyntaxKind.IfDirectiveTrivia));

if (!hasIfDirective)
return (usings, container);

// Find the #endif directive. It should be in the leading trivia of the first member after the usings.
var members = container.Members;
if (members.Count == 0)
return (usings, container);

var firstMember = members[0];
var firstMemberLeadingTrivia = firstMember.GetLeadingTrivia();

// Find the first #endif directive in the member's leading trivia
var endIfIndex = -1;
for (int i = 0; i < firstMemberLeadingTrivia.Count; i++)

Check failure on line 282 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L282

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(282,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)

Check failure on line 282 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L282

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(282,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)

Check failure on line 282 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L282

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(282,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)

Check failure on line 282 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L282

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(282,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)

Check failure on line 282 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L282

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(282,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)
{
if (firstMemberLeadingTrivia[i].IsKind(SyntaxKind.EndIfDirectiveTrivia))
{
endIfIndex = i;
break;
}
}

if (endIfIndex == -1)
return (usings, container);

// Extract the #endif and any trivia up to and including the first newline after it
var endIfTrivia = firstMemberLeadingTrivia[endIfIndex];
var triviaToMove = new List<SyntaxTrivia> { endIfTrivia };

// Include the newline after the #endif
if (endIfIndex + 1 < firstMemberLeadingTrivia.Count &&
firstMemberLeadingTrivia[endIfIndex + 1].IsKind(SyntaxKind.EndOfLineTrivia))
{
triviaToMove.Add(firstMemberLeadingTrivia[endIfIndex + 1]);
}

// Remove the #endif and its trailing newline from the first member
var newFirstMemberLeadingTrivia = firstMemberLeadingTrivia.ToList();
for (int i = triviaToMove.Count - 1; i >= 0; i--)

Check failure on line 307 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L307

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(307,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)

Check failure on line 307 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L307

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(307,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)

Check failure on line 307 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L307

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(307,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)

Check failure on line 307 in src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs#L307

src/Analyzers/CSharp/CodeFixes/MisplacedUsingDirectives/MisplacedUsingDirectivesCodeFixProvider.cs(307,14): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)
{
newFirstMemberLeadingTrivia.RemoveAt(endIfIndex);
}

var newFirstMember = firstMember.WithLeadingTrivia(newFirstMemberLeadingTrivia);
var newContainer = container.ReplaceNode(firstMember, newFirstMember);

// Attach the #endif to the last using's trailing trivia
var lastUsing = usings[^1];
var lastUsingTrailingTrivia = lastUsing.GetTrailingTrivia();
var newLastUsingTrailingTrivia = lastUsingTrailingTrivia.AddRange(triviaToMove);
var newLastUsing = lastUsing.WithTrailingTrivia(newLastUsingTrailingTrivia);

// Replace the last using in the array
var adjustedUsings = usings.SetItem(usings.Length - 1, newLastUsing);

return (adjustedUsings, newContainer);
}

private static (BaseNamespaceDeclarationSyntax namespaceWithoutUsings, ImmutableArray<UsingDirectiveSyntax> usingsFromNamespace) RemoveUsingsFromNamespace(
BaseNamespaceDeclarationSyntax usingContainer, bool ignoringAliases)
{
Expand All @@ -261,19 +334,27 @@

// Get the using directives from the namespaces.
var usingsFromNamespaces = namespaceDeclarationMap.Values.SelectMany(result => result.usingsFromNamespace);
var usings = ignoringAliases

// Get usings from the current container level only
var localUsings = ignoringAliases
? usingContainer.Usings.Where(u => u.Alias is null)
: usingContainer.Usings;
var allUsings = usings.Concat(usingsFromNamespaces).ToImmutableArray();

// Replace the namespace declarations in the compilation with the ones without using directives.
var namespaceDeclarationWithReplacedNamespaces = usingContainer.ReplaceNodes(
namespaceDeclarations, (node, _) => namespaceDeclarationMap[node].namespaceWithoutUsings);

// Handle preprocessor directives that surround the usings at this container level
var (adjustedLocalUsings, adjustedContainer) = MovePreprocessorDirectivesWithUsings(
localUsings.ToImmutableArray(), namespaceDeclarationWithReplacedNamespaces);

// Combine adjusted local usings with usings from nested namespaces
var allUsings = adjustedLocalUsings.Concat(usingsFromNamespaces).ToImmutableArray();

// Remove usings and fix leading trivia for namespace declaration.
var namespaceDeclarationWithoutUsings = namespaceDeclarationWithReplacedNamespaces
var namespaceDeclarationWithoutUsings = adjustedContainer
.WithUsings(ignoringAliases
? List(namespaceDeclarationWithReplacedNamespaces.Usings.Where(u => u.Alias != null))
? List(adjustedContainer.Usings.Where(u => u.Alias != null))
: default);

var namespaceDeclarationWithoutBlankLine = namespaceDeclarationWithoutUsings.Usings.Count == 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1073,4 +1073,121 @@ namespace TestNamespace
""", InsideNamespaceOption, placeSystemNamespaceFirst: true);

#endregion

#region Preprocessor Directives

/// <summary>
/// Verifies that preprocessor directives surrounding using statements are moved correctly.
/// This tests the scenario from https://github.com/dotnet/roslyn/issues/31249
/// </summary>
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/31249")]
public Task WhenOutsidePreferred_UsingsWithPreprocessorDirectives_DirectivesMovedCorrectly()
=> TestInRegularAndScriptAsync("""
using System;

namespace MyNamespace
{
#if !FOO
[|using System.Runtime.CompilerServices;|]
#endif

class Program
{
static void Main() { }
}
}
""", """
using System;
#if !FOO
{|Warning:using System.Runtime.CompilerServices;|}
#endif

namespace MyNamespace
{
class Program
{
static void Main() { }
}
}
""", OutsideNamespaceOption, placeSystemNamespaceFirst: true);

/// <summary>
/// Verifies that when a namespace is wrapped in preprocessor directives, moving usings outside
/// keeps them inside the same preprocessor block. This tests the scenario from https://github.com/dotnet/roslyn/issues/31249
/// </summary>
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/31249")]
public Task WhenOutsidePreferred_NamespaceInsidePreprocessorBlock_UsingsStayInBlock()
=> TestInRegularAndScriptAsync("""
#if NET6_0

namespace ConsoleApp
{
[|using System;|]

internal class Class1
{
public void M()
{
Console.WriteLine("");
}
}
}

#endif
""", """
#if NET6_0

{|Warning:using System;|}

namespace ConsoleApp
{
internal class Class1
{
public void M()
{
Console.WriteLine("");
}
}
}

#endif
""", OutsideNamespaceOption, placeSystemNamespaceFirst: true);

/// <summary>
/// Verifies that multiple using statements wrapped in preprocessor directives are handled correctly.
/// </summary>
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/31249")]
public Task WhenOutsidePreferred_MultipleUsingsWithPreprocessorDirectives_DirectivesMovedCorrectly()
=> TestInRegularAndScriptAsync("""
using System;

namespace MyNamespace
{
#if DEBUG
[|using System.Diagnostics;
using System.Runtime.CompilerServices;|]
#endif

class Program
{
static void Main() { }
}
}
""", """
using System;
#if DEBUG
{|Warning:using System.Diagnostics;|}
{|Warning:using System.Runtime.CompilerServices;|}
#endif

namespace MyNamespace
{
class Program
{
static void Main() { }
}
}
""", OutsideNamespaceOption, placeSystemNamespaceFirst: true);

#endregion
}
Loading