Skip to content

Fix IDE0055: Enforce import directive grouping when groups are contiguous, regardless of sorting#81202

Merged
CyrusNajmabadi merged 19 commits intomainfrom
copilot/fix-import-directives-sorting
Nov 13, 2025
Merged

Fix IDE0055: Enforce import directive grouping when groups are contiguous, regardless of sorting#81202
CyrusNajmabadi merged 19 commits intomainfrom
copilot/fix-import-directives-sorting

Conversation

Copy link
Contributor

Copilot AI commented Nov 13, 2025

Fix IDE0055 to respect import directive groups even when not sorted

Summary

This PR fixes an issue where IDE0055 only enforces dotnet_separate_import_directive_groups when usings are globally alphabetically sorted. The fix changes the logic to check if usings are properly grouped (contiguous) rather than checking if they are sorted.

Changes Made

  1. Modified TokenBasedFormattingRule.cs:

    • Replaced sorting check with contiguity/grouping check
    • Added AreUsingsProperlyGrouped() to check if usings with the same first namespace token are contiguous
    • Added GetGroupIdentifier() using Contract.ThrowIfNull pattern
    • Removed all sorting logic - we only care about grouping (contiguity)
  2. Modified UsingsAndExternAliasesOrganizer.cs:

    • Added sync comment and LanguageParser guarantee comment
  3. Added comprehensive tests in FormattingEngineTests.cs:

    • 10 new tests following existing SeparateGroups test pattern
    • All use WpfTheory with CombinatorialData for boolean parameter testing
    • Tests cover: grouped/ungrouped usings, aliases, statics, and mixed scenarios

Behavior Changes

  • Before: Only enforced grouping when ALL usings were alphabetically sorted
  • After: Enforces grouping when usings are contiguous, regardless of sorting

Key Insight

The critical requirement is grouping (contiguity), not sorting. As long as all usings with the same first namespace token are contiguous, we add separators between groups.

Original prompt

This section details on the original issue you should resolve

<issue_title>Undocumented / incorrect requirement for import directives to be alphabetically sorted to trigger IDE0055</issue_title>
<issue_description>Version Used:

.NET SDK Version: 9.0.201
SDK-Style Project
Microsoft Windows 11 Business 10.0.22631
Microsoft Visual Studio Enterprise 2022 (64-bit) - 17.13.5

Steps to Reproduce:

  1. Have an SDK-Style C# project
  2. Set dotnet_separate_import_directive_groups = true and dotnet_diagnostic.IDE0055.severity = error in .editorconfig
  3. Add several usings in alphabetical order to a .cs file without separating line breaks
  4. Observe an IDE0055 formatting rule violation is triggered
  5. Move one of the directives such that they are no longer all alphabetically sorted
  6. Observe that the IDE0055 formatting rule violation is no longer triggered
# Triggers IDE0055
namespace ktsu.IDE0055Test;

using Azure.Storage.Blobs;
using Azure.Storage.Sas;
using NuGet.Versioning;
using System.Diagnostics;
# Does not trigger IDE0055
namespace ktsu.IDE0055Test;

using Azure.Storage.Blobs;
using Azure.Storage.Sas;
using System.Diagnostics;
using NuGet.Versioning;

There is a comment in the analyzer rule that alludes to this as if it were intended behavior:

private AdjustNewLinesOperation AdjustNewLinesAfterSemicolonToken(

        // if the user is separating using-groups, and we're between two usings, and these
        // usings *should* be separated, then do so (if the usings were already properly
        // sorted).

However the documentation for configuring the rule makes no mention of this requirement:

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/dotnet-formatting-options#dotnet_separate_import_directive_groups

Diagnostic Id: IDE0055: Fix formatting

Expected Behavior:

UsingsAndExternAliasesOrganizer.NeedsGrouping() indicates that two namespaces should belong to the same grouping if the first token of each is different.

The rule should not enforce alphabetical sorting, or the requirement should be documented, and if the sorting is intentional it should properly respect dotnet_sort_system_directives_first instead of allowing either completely sorted or sorted with System namespaces first.

In summary the rule should:

  • Not query for alphabetical sorting at all

or

  • Query for alphabetical sorting in a way that respects the dotnet_sort_system_directives_first configuration, and be documented as such

Actual Behavior:

TokenBasedFormattingRule.AdjustNewLinesAfterSemicolonToken() will only enforce separating newlines between grouped usings if all of the usings are sorted either alphabetically, or alphabetically with the System group first, regardless of the configuration of dotnet_sort_system_directives_first.

private AdjustNewLinesOperation AdjustNewLinesAfterSemicolonToken(

            if (usings.IsSorted(UsingsAndExternAliasesDirectiveComparer.SystemFirstInstance) ||
                usings.IsSorted(UsingsAndExternAliasesDirectiveComparer.NormalInstance))

This check also does not match the documentation for dotnet_sort_system_directives_first in that it states:

Sort System.* using directives alphabetically, and place them before other using directives.

But actually checks for:

Sort System.* using directives alphabetically, and place them before other alphabetically sorted using directives.

</issue_description>

<agent_instructions>@copilot we should respect dotnet_separate_import_directive_groups if the usings are in groups, even if the groups are not sorted. </agent_instructions>

Comments on the Issue (you are @copilot in this section)

@JoeRobich I think having a separate analyzer/codefix for organized imports would make sense.

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix requirement for import directives to trigger IDE0055 Fix IDE0055: Enforce import directive grouping when groups are sorted, not just globally sorted Nov 13, 2025
Copilot AI requested a review from CyrusNajmabadi November 13, 2025 12:14
}
""");

[Fact]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot add [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77831")] to all tests you added

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8ffedad. Added the WorkItem attribute to all three test methods and imported the necessary Roslyn.Test.Utilities namespace.

using Azure.Storage.Sas;
using Azure.Storage.Blobs;
using System.Diagnostics;
using NuGet.Versioning;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot these are properly grouped. there is hte 'Azure' group, the 'System' group, and the 'Nuget' group. So we should add space between.

Note: we should add a test where things are not grouped. Specifically, where there are two 'Azure' usings, one at the start and one at the end. because these are not grouped, we should not add spaces between anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8627866. The test now correctly expects separators since the usings are properly grouped (Azure group, System group, NuGet group). Also added a new test TestSeparateImportDirectiveGroups_WithUngroupedUsingsNoSeparator that tests truly ungrouped usings with Azure at the start and end, which should not trigger separators.

return IsRangeSorted(usings, currentGroupStart, usings.Count - 1, comparer);
}

private static bool IsRangeSorted(SyntaxList<UsingDirectiveSyntax> usings, int start, int end, IComparer<SyntaxNode> comparer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot can you add the suffix 'Inclusive'/'Exclusive' to start/end (depending on what semantics they actually have) to make the logic here clearer. I think you are trying to do 'endInclusive', but it's not clear to me.

Note: consider not pssing in X - 1 into IsRangeSorted (with inclusive) semantics, and instead just pass X in with 'exclusive' semantics. It might make things simpler (though maybe it won't, if it makes the inner for-loop harder. Up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8627866. Added startInclusive and endExclusive suffixes to the parameters. Also switched to exclusive end semantics (passing i instead of i - 1, and usings.Count instead of usings.Count - 1), which simplifies the logic - the inner loop now uses endExclusive - 1 as the upper bound.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
- Fixed TestSeparateImportDirectiveGroups_WithUnsortedGroupsNoSeparator to correctly expect separators since groups are properly formed
- Added new TestSeparateImportDirectiveGroups_WithUngroupedUsingsNoSeparator to test truly ungrouped usings (Azure at start and end)
- Added Inclusive/Exclusive suffixes to IsRangeSorted parameters for clarity
- Changed to use exclusive end semantics to simplify logic

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
@CyrusNajmabadi
Copy link
Contributor

@copilot I think you've misunderstood entirely. The point is to support separating even if usings are not. The only critical thing is that they are grouped. Usings are grouped if their first namespace token matches. So as long as all the sequences having matching token, and no token shows again later, then things are properly grouped and can be separated.

Changed AreUsingsProperlyGrouped to only check if usings are contiguous (grouped), not sorted. The key insight is that we should add separators as long as all usings with the same first namespace token are together, regardless of sorting within groups. This matches the original intent of dotnet_separate_import_directive_groups.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 13, 2025

@copilot I think you've misunderstood entirely. The point is to support separating even if usings are not. The only critical thing is that they are grouped. Usings are grouped if their first names...

Fixed in bbbc26c. Completely rewrote the logic to only check grouping (contiguity), not sorting. Now AreUsingsProperlyGrouped() simply verifies that all usings with the same first namespace token are contiguous - if a token appears, then a different token appears, and then the first token appears again, it returns false. Sorting within groups is no longer checked at all.

Copilot AI changed the title Fix IDE0055: Enforce import directive grouping when groups are sorted, not just globally sorted Fix IDE0055: Enforce import directive grouping when groups are contiguous, regardless of sorting Nov 13, 2025
""");

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77831")]
public async Task TestSeparateImportDirectiveGroups_WithGroupedButNotGloballySortedUsings()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot can you make this a Theory instead of a Fact, and have it validate that we get the same result regardless of whether dotnet_sort_system_directives_first is true or false? Do this for all the tests. They should behave hte same way regardless of the value of this other setting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5861163. Converted all four test methods to theories with [InlineData(true)] and [InlineData(false)] to verify the behavior is consistent regardless of the dotnet_sort_system_directives_first setting. Each test now validates that grouping works the same way whether that option is true or false.

return true;

// Track which group identifiers we've seen
var seenGroups = new HashSet<string>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var seenGroups = new HashSet<string>();
using var _ = PooledHashSet<string>(out var seenGroups);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot

Suggested change
var seenGroups = new HashSet<string>();
using var _ = PooledHashSet<string>(out var seenGroups);

Changed all test methods from [Theory, WorkItem(...)] with [InlineData(true)]/[InlineData(false)] to [Theory, CombinatorialData, WorkItem(...)] which automatically tests all boolean parameter combinations.

Co-authored-by: JoeRobich <611219+JoeRobich@users.noreply.github.com>
""");

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/77831")]
public async Task TestSeparateImportDirectiveGroups_WithGroupedButNotGloballySortedUsings(bool sortSystemDirectivesFirst)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot move all these tests to FormattingEngineTests.cs. They should go after the existing SeparateGroups_XXX tests. Keep the WorkItem and keep them Theory/Combinatorial data. but otherwise update to match the Pattern of the tests in the destination.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ab58b02. Moved all tests to FormattingEngineTests.cs after the existing SeparateGroups tests. Updated them to use the AssertFormatWithView pattern matching the existing tests. Kept WpfTheory, CombinatorialData, and WorkItem attributes as requested.

Added TestSeparateImportDirectiveGroups_WithUngroupedAliasesAndStatics to verify that when aliases and statics are not contiguous (appearing at start and end), no separators are added. This ensures the grouping logic correctly handles mixed directive types.

Co-authored-by: JoeRobich <611219+JoeRobich@users.noreply.github.com>
Copilot AI and others added 2 commits November 13, 2025 18:06
- Changed GetGroupIdentifier to use Contract.ThrowIfNull instead of if-check, matching pattern in UsingsAndExternAliasesOrganizer
- Added comment explaining LanguageParser.ParseUsingDirective guarantees Name is non-null for non-alias directives
- Moved all tests from FormattingAnalyzerTests.cs to FormattingEngineTests.cs
- Updated tests to use simpler AssertFormatWithView pattern consistent with existing SeparateGroups tests
- Kept Theory/CombinatorialData and WorkItem attributes as requested

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
@CyrusNajmabadi CyrusNajmabadi enabled auto-merge (rebase) November 13, 2025 18:21
auto-merge was automatically disabled November 13, 2025 19:32

Rebase failed

@CyrusNajmabadi CyrusNajmabadi merged commit c6e8616 into main Nov 13, 2025
26 checks passed
@CyrusNajmabadi CyrusNajmabadi deleted the copilot/fix-import-directives-sorting branch November 13, 2025 19:47
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Nov 13, 2025
@davidwengier davidwengier modified the milestones: Next, 18.3 Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Undocumented / incorrect requirement for import directives to be alphabetically sorted to trigger IDE0055

4 participants