csharpier
csharpier copied to clipboard
Consider adding empty lines between groups of using statements, or respect existing empty lines.
The initial version of sorting usings (#661) did not retain existing empty lines.
If we try to retain existing empty lines then
- What happens when usings are moved?
- Can the user easily correct any misplaced empty lines?
If we add empty lines automatically
- Should they only appear between groups that are sorted? Or between the first part of the namespace?
// between sorted groups
using System.IO.Abstractions;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
// by first part of namespace
using System.IO.Abstractions;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
I'd be happy if it worked like this:
- Record which pairs of imports have a space between them.
- Sort as is currently done.
- For any of the pairs that still exist in the sorted output, reinsert the space between them.
Or, perhaps,
- Group by first part of the namespace
- Put System first
- Put the group that matches the namespace used in the file last
- Sort the rest alphabetically.
Personally, I'm a fan of the following import groups separated by one empty line each:
- standard library (
System
imports) - third-party (non-
System
imports) - first-party (imports from the same parent namespace as the one used by the file)
I'm not sure if there's a precedent for this in how existing C# code bases are formatted but this is similar to how import sorting works in Python formatters such as isort or ruff.
To extend this to all the supported types of using
syntaxes (global
/static
modifiers, using
alias) in C#, I propose:
// global usings at the top since they affect every source file
// split in groups in the same way as non-global imports
global using System.Timers;
global using static System.Math;
global using NonSystemA;
global using NonSystemB;
global using MyProject.Utils;
// System imports - sorted within specific `using` syntaxes
using System.Collections;
using System.Timers;
using static System.Console;
using static System.Math;
using NameB = System.Collections.Dictionary; // sort by import name vs alias name - up for debate
using NameA = System.Collections.List;
// Non-System imports, same rules as above
using NonSystemA;
using NonSystemB;
using static NonSystemA.X;
using static NonSystemB.Y;
using NonSystemAlias = NonSystem.SomeClass;
// Same-namespace imports
using MyProject.ViewModel;
using static MyProject.Collections.Generic;
#if DEBUG // finally any usings in #if's
// use the same heuristics as outside (or continue not sorting as is the case with the released version)
#endif
namespace MyProject.View; // or namespace MyProject.View { ... }
// actual code