diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicIfDirective.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicIfDirective.expected.test new file mode 100644 index 000000000..e5f573d6d --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicIfDirective.expected.test @@ -0,0 +1,5 @@ +using System; + +#if DEBUG +using Insite.Bad; +#endif diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicIfDirective.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicIfDirective.test new file mode 100644 index 000000000..df5f9a193 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicIfDirective.test @@ -0,0 +1,4 @@ +#if DEBUG +using Insite.Bad; +#endif +using System; \ No newline at end of file diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicSort.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicSort.expected.test new file mode 100644 index 000000000..42caaf782 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicSort.expected.test @@ -0,0 +1,5 @@ +using AWord; +using BWord; +using MWord; +using YWord; +using ZWord; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicSort.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicSort.test new file mode 100644 index 000000000..0e3e608c7 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicSort.test @@ -0,0 +1,5 @@ +using MWord; +using ZWord; +using AWord; +using BWord; +using YWord; \ No newline at end of file diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsSystemToTop.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsSystemToTop.expected.test new file mode 100644 index 000000000..f2122728b --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsSystemToTop.expected.test @@ -0,0 +1,6 @@ +using System; + +using System.Web; // keeping the space above this is odd, but there isn't a good way to know which spaces to remove besides the first one + +using AWord; +using ZWord; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsSystemToTop.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsSystemToTop.test new file mode 100644 index 000000000..f5c6fa852 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsSystemToTop.test @@ -0,0 +1,6 @@ +using ZWord; + +using AWord; // the blank line above here tests that we don't add two blank lines between system and non-system + +using System.Web; // keeping the blank line above this in the expected is odd, but there isn't a good way to know which spaces to remove besides the first one +using System; diff --git a/Src/CSharpier/SyntaxPrinter/NamespaceLikePrinter.cs b/Src/CSharpier/SyntaxPrinter/NamespaceLikePrinter.cs index aeb783a5e..043bf5969 100644 --- a/Src/CSharpier/SyntaxPrinter/NamespaceLikePrinter.cs +++ b/Src/CSharpier/SyntaxPrinter/NamespaceLikePrinter.cs @@ -44,19 +44,7 @@ FormattingContext context docs.Add(Doc.HardLine); } - docs.Add( - Doc.Join( - Doc.HardLine, - usings.Select( - (o, i) => - UsingDirective.Print( - o, - context, - printExtraLines: i != 0 || externs.Count != 0 - ) - ) - ) - ); + docs.Add(UsingDirectives.PrintWithSorting(usings, context, externs.Count != 0)); } var isCompilationUnitWithAttributes = false; diff --git a/Src/CSharpier/SyntaxPrinter/UsingDirectives.cs b/Src/CSharpier/SyntaxPrinter/UsingDirectives.cs new file mode 100644 index 000000000..2f3918b4e --- /dev/null +++ b/Src/CSharpier/SyntaxPrinter/UsingDirectives.cs @@ -0,0 +1,104 @@ +namespace CSharpier.SyntaxPrinter; + +internal static class UsingDirectives +{ + private static readonly DefaultOrder Comparer = new(); + + // TODO what does the analyzer do with some of these sorts? + // TODO what about validation? + // TODO what about #ifs? + // TODO what about globals? + // TODO what about statics? + // TODO what about alias? + // TODO get rid of lines and keep them in blocks? - this does https://google.github.io/styleguide/javaguide.html#s3.3-import-statements + // TODO what about alias any type with c# 12? + + public static Doc PrintWithSorting( + SyntaxList usings, + FormattingContext context, + bool printExtraLines + ) + { + var sortedUsings = usings.OrderBy(o => o, Comparer).ToArray(); + var docs = new List(); + + var lastWasSystem = false; + for (var i = 0; i < sortedUsings.Length; i++) + { + var noExtraLines = false; + var thisIsSystem = sortedUsings[i].Name is NameSyntax name && IsSystemName(name); + if (i != 0) + { + docs.Add(Doc.HardLine); + if (!thisIsSystem && lastWasSystem) + { + noExtraLines = true; + docs.Add(Doc.HardLine); + } + + lastWasSystem = thisIsSystem; + } + docs.Add( + UsingDirective.Print( + sortedUsings[i], + context, + printExtraLines: (i != 0 || printExtraLines) && !noExtraLines + ) + ); + } + + return Doc.Concat(docs); + } + + private static bool IsSystemName(NameSyntax value) + { + while (true) + { + if (value is not QualifiedNameSyntax qualifiedNameSyntax) + { + return value is IdentifierNameSyntax { Identifier.Text: "System" }; + } + value = qualifiedNameSyntax.Left; + } + } + + private class DefaultOrder : IComparer + { + public int Compare(UsingDirectiveSyntax? x, UsingDirectiveSyntax? y) + { + if (x?.Name is null) + { + return -1; + } + + if (y?.Name is null) + { + return 1; + } + + var xIsSystem = IsSystemName(x.Name); + var yIsSystem = IsSystemName(y.Name); + + int Return(int value) + { + DebugLogger.Log( + $"{x.ToFullString().Trim()} {xIsSystem} vs {y.ToFullString().Trim()} {yIsSystem} = {value}" + ); + + return value; + } + + if (xIsSystem && !yIsSystem) + { + return Return(-1); + } + + if (!xIsSystem && yIsSystem) + { + return Return(1); + } + + return Return(x.Name.ToFullString().CompareTo(y.Name.ToFullString())); + } + } +}