belav / csharpier

CSharpier is an opinionated code formatter for c#.
https://csharpier.com
MIT License
1.43k stars 99 forks source link

Consider adding empty lines between groups of using statements, or respect existing empty lines. #988

Open belav opened 1 year ago

belav commented 1 year ago

The initial version of sorting usings (#661) did not retain existing empty lines.

If we try to retain existing empty lines then

  1. What happens when usings are moved?
  2. Can the user easily correct any misplaced empty lines?

If we add empty lines automatically

  1. 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;

parched commented 1 year ago

I'd be happy if it worked like this:

  1. Record which pairs of imports have a space between them.
  2. Sort as is currently done.
  3. For any of the pairs that still exist in the sorted output, reinsert the space between them.
parched commented 1 year ago

Or, perhaps,

  1. Group by first part of the namespace
  2. Put System first
  3. Put the group that matches the namespace used in the file last
  4. Sort the rest alphabetically.
Jackenmen commented 1 year ago

Personally, I'm a fan of the following import groups separated by one empty line each:

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