diff --git a/Shell/Release.psm1 b/Shell/CreateRelease.psm1 similarity index 97% rename from Shell/Release.psm1 rename to Shell/CreateRelease.psm1 index a635d3b36..15ea76dc7 100644 --- a/Shell/Release.psm1 +++ b/Shell/CreateRelease.psm1 @@ -1,4 +1,4 @@ -function CSH-Release { +function CSH-CreateRelease { param ( [Parameter(Mandatory=$true)] [string]$versionNumber diff --git a/Shell/DeleteReviewBranches.psm1 b/Shell/DeleteReviewBranches.psm1 new file mode 100644 index 000000000..0103053f3 --- /dev/null +++ b/Shell/DeleteReviewBranches.psm1 @@ -0,0 +1,12 @@ +function CSH-DeleteReviewBranches { + + # TODO probably make this configurable + $pathToTestingRepo = "C:/Projects/csharpier-repos" + Set-Location $pathToTestingRepo + + git checkout main + git branch | Where-Object { $_ -notmatch "main" } | ForEach-Object { git branch -D $_ } + git branch -r | Where-Object { $_ -notmatch "origin/main" } | ForEach-Object { git push origin --delete $_.Replace("origin/", "").Trim() } +} + +Export-ModuleMember -Function CSH-* diff --git a/Shell/Init.ps1 b/Shell/Init.ps1 index 94fb340c3..7d13ae04d 100644 --- a/Shell/Init.ps1 +++ b/Shell/Init.ps1 @@ -1,5 +1,5 @@ foreach ($file in Get-ChildItem $PSScriptRoot -Filter "*.psm1") { - Import-Module $file.FullName -DisableNameChecking + Import-Module $file.FullName -DisableNameChecking -Force } Write-Host -ForegroundColor DarkMagenta "Welcome to CSharpier shell" diff --git a/Shell/ReviewBranch.psm1 b/Shell/ReviewBranch.psm1 index 131afdd9a..399c23ae0 100644 --- a/Shell/ReviewBranch.psm1 +++ b/Shell/ReviewBranch.psm1 @@ -10,7 +10,7 @@ function CSH-ReviewBranch { $csharpierDllPath = Join-Path $repositoryRoot "Src/CSharpier.Cli/bin/release/net7.0/dotnet-csharpier.dll" $location = Get-Location - + Set-Location $repositoryRoot if (!$pathToTestingRepo) { @@ -32,22 +32,25 @@ function CSH-ReviewBranch { if ($branch -eq "main") { Write-Output "You must be on the branch you want to test. You are currently on main" - exit 1 + return } $preBranch = "pre-" + $branch $postBranch = "post-" + $branch - + if ($folder -ne $null) { $preBranch += "-" + $folder $postBranch += "-" + $folder } - - Set-Location $pathToTestingRepo - & git reset --hard - git checkout $postBranch - $postBranchOutput = (git status) | Out-String + Set-Location $pathToTestingRepo + & git reset --hard *> $null + & git pull + try { + & git checkout $postBranch 2>&1 + } + catch { } + $postBranchOutput = (git status 2>&1) | Out-String $firstRun = -not $postBranchOutput.Contains("On branch $postBranch") $fastParam = "" @@ -55,13 +58,15 @@ function CSH-ReviewBranch { $fastParam = "--fast" } - if ($firstRun) - { + if ($firstRun) { Set-Location $repositoryRoot - $checkoutMainOutput = (git checkout main) | Out-String - if (-not $checkoutMainOutput.Contains("Your branch is up to date with ")) { - return - } +# try { + & git checkout main #2>&1 | Out-String +# } +# catch { +# Write-Host "Could not checkout main on csharpier, working directory is probably not clean" +# return +# } CSH-BuildProject diff --git a/Src/CSharpier.FakeGenerators/SyntaxNodeComparerGenerator.cs b/Src/CSharpier.FakeGenerators/SyntaxNodeComparerGenerator.cs index ed508bd55..d428dfbab 100644 --- a/Src/CSharpier.FakeGenerators/SyntaxNodeComparerGenerator.cs +++ b/Src/CSharpier.FakeGenerators/SyntaxNodeComparerGenerator.cs @@ -4,6 +4,8 @@ namespace CSharpier.FakeGenerators; +using Microsoft.CodeAnalysis.CSharp.Syntax; + public class SyntaxNodeComparerGenerator { // this would probably be easier to understand as a scriban template but is a lot of effort @@ -174,14 +176,27 @@ private static void GenerateMethod(StringBuilder sourceBuilder, Type type) ) ) { - var compare = propertyType == typeof(SyntaxTokenList) ? "Compare" : "null"; - if (propertyName == "Modifiers") + if ( + propertyType.IsGenericType + && propertyType.GenericTypeArguments[0] == typeof(UsingDirectiveSyntax) + ) { - propertyName += ".OrderBy(o => o.Text).ToList()"; + sourceBuilder.AppendLine( + $" result = this.CompareUsingDirectives(originalNode.{propertyName}, formattedNode.{propertyName}, originalNode, formattedNode);" + ); } - sourceBuilder.AppendLine( - $" result = this.CompareLists(originalNode.{propertyName}, formattedNode.{propertyName}, {compare}, o => o.Span, originalNode.Span, formattedNode.Span);" - ); + else + { + var compare = propertyType == typeof(SyntaxTokenList) ? "Compare" : "null"; + if (propertyName == "Modifiers") + { + propertyName += ".OrderBy(o => o.Text).ToList()"; + } + sourceBuilder.AppendLine( + $" result = this.CompareLists(originalNode.{propertyName}, formattedNode.{propertyName}, {compare}, o => o.Span, originalNode.Span, formattedNode.Span);" + ); + } + sourceBuilder.AppendLine($" if (result.IsInvalid) return result;"); } else if ( diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/Directives_CompilationUnit.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/Directives_CompilationUnit.test deleted file mode 100644 index 378678c5a..000000000 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/Directives_CompilationUnit.test +++ /dev/null @@ -1,19 +0,0 @@ -#if NO_EXTRA_LINES -extern alias Foo1; -#else -extern alias Foo2; -#endif - -#if NO_EXTRA_LINES -using System.Net.Http; -#else -using System.Web.Routing; -#endif - -using System.Web.Http; - -#if KEEP_LINE_ABOVE -using System.Web.Http; -#endif - -namespace Namespace { } diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives.test index c4d4e635c..d52e6a398 100644 --- a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives.test +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives.test @@ -1,29 +1,13 @@ // leading -using First; // trailing - +using A; // trailing // leading with space using // another trailing -Second; - -using -// static leading -static // static trailing -Third; - -using M = System.Math; -using Point = (int x, int y); - -using static System.Math; - -global using System; - -using First; -using Second; +B; namespace Namespace { - using Third; using One.Two.Three; + using Third; public class ClassName { } } 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..ed4e3e503 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicIfDirective.expected.test @@ -0,0 +1,4 @@ +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..c1dbd44db --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_BasicIfDirective.test @@ -0,0 +1,4 @@ +#if DEBUG +using Insite.Bad; +#endif +using System; 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..752b78bc9 --- /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; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_Basics.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_Basics.test new file mode 100644 index 000000000..b69a74a87 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_Basics.test @@ -0,0 +1,7 @@ +global using Global; +using System; +using global::Zebra; +using Custom; +using static Expression; +using Point = (int x, int y); +using Index = Microsoft.Framework.Index; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase1.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase1.test new file mode 100644 index 000000000..492c1c88a --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase1.test @@ -0,0 +1,9 @@ +#if BUILD_MSI_TASKS +using System; +using Microsoft.Build.Framework; + +namespace RepoTasks; + +class ClassName { } + +#endif diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase2.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase2.expected.test new file mode 100644 index 000000000..dd004adfc --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase2.expected.test @@ -0,0 +1,6 @@ +using System.IO; +#if DEBUG +using System; +#else +using Microsoft; +#endif diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase2.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase2.test new file mode 100644 index 000000000..267ff491d --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase2.test @@ -0,0 +1,6 @@ +#if DEBUG +using System; +#else +using Microsoft; +#endif +using System.IO; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase3.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase3.expected.test new file mode 100644 index 000000000..eb9e783fa --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase3.expected.test @@ -0,0 +1,6 @@ +using System.IO; +#if !DEBUG +using System; +#else +using Microsoft; +#endif diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase3.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase3.test new file mode 100644 index 000000000..f79019003 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase3.test @@ -0,0 +1,6 @@ +#if !DEBUG +using System; +#else +using Microsoft; +#endif +using System.IO; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase4.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase4.test new file mode 100644 index 000000000..dc41fe9c5 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase4.test @@ -0,0 +1,8 @@ +#if DEBUG +using A; +#else +using B; +#endif +#if !DEBUG +using C; +#endif diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase5.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase5.expected.test new file mode 100644 index 000000000..ee20b766f --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase5.expected.test @@ -0,0 +1,10 @@ +using C; +#if DEBUG +using A; +#else +using B; +#endif + +#if !DEBUG +using C; +#endif diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase5.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase5.test new file mode 100644 index 000000000..86ee53c56 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase5.test @@ -0,0 +1,9 @@ +#if DEBUG +using A; +#else +using B; +#endif +using C; +#if !DEBUG +using C; +#endif diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase6.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase6.expected.test new file mode 100644 index 000000000..425600c91 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase6.expected.test @@ -0,0 +1,6 @@ +#if DEBUG +#define SYMBOL +#endif + +using System; +using System.Collections; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase6.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase6.test new file mode 100644 index 000000000..2fb47e06c --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase6.test @@ -0,0 +1,6 @@ +#if DEBUG +#define SYMBOL +#endif + +using System.Collections; +using System; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase7.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase7.expected.test new file mode 100644 index 000000000..af32b58ea --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase7.expected.test @@ -0,0 +1,7 @@ +#if DEBUG +extern alias MonoSecurity; +#else +using Mono.Security.Cryptography; +#endif + +using System; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase7.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase7.test new file mode 100644 index 000000000..af32b58ea --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase7.test @@ -0,0 +1,7 @@ +#if DEBUG +extern alias MonoSecurity; +#else +using Mono.Security.Cryptography; +#endif + +using System; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase9.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase9.expected.test new file mode 100644 index 000000000..efc8a80f4 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase9.expected.test @@ -0,0 +1,6 @@ +#if XMLCHARTYPE_GEN_RESOURCE +#undef XMLCHARTYPE_USE_RESOURCE +#endif + +using System.Diagnostics; +using System.Threading; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase9.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase9.test new file mode 100644 index 000000000..153d1d80d --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_EdgeCase9.test @@ -0,0 +1,6 @@ +#if XMLCHARTYPE_GEN_RESOURCE +#undef XMLCHARTYPE_USE_RESOURCE +#endif + +using System.Threading; +using System.Diagnostics; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_FileComments.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_FileComments.expected.test new file mode 100644 index 000000000..33869b703 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_FileComments.expected.test @@ -0,0 +1,4 @@ +// Licensed to something + +using Apple; +using Microsoft; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_FileComments.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_FileComments.test new file mode 100644 index 000000000..f10d0337f --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_FileComments.test @@ -0,0 +1,4 @@ +// Licensed to something + +using Microsoft; +using Apple; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepDefineAtTop.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepDefineAtTop.expected.test new file mode 100644 index 000000000..3b5ffec07 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepDefineAtTop.expected.test @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#define USE_STRUCT +using System; +using System.Runtime.CompilerServices; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepDefineAtTop.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepDefineAtTop.test new file mode 100644 index 000000000..ec8a8109c --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepDefineAtTop.test @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#define USE_STRUCT +using System.Runtime.CompilerServices; +using System; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepUndefAtTop.expected.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepUndefAtTop.expected.test new file mode 100644 index 000000000..68036812a --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepUndefAtTop.expected.test @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#undef USE_STRUCT +using System; +using System.Runtime.CompilerServices; diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepUndefAtTop.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepUndefAtTop.test new file mode 100644 index 000000000..7e396d771 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_KeepUndefAtTop.test @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#undef USE_STRUCT +using System.Runtime.CompilerServices; +using System; + diff --git a/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsAlias.test b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsAlias.test new file mode 100644 index 000000000..67736536d --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsAlias.test @@ -0,0 +1,3 @@ +using A = Z; +using M = A; +using Z = M; 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..c9805adb3 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsSystemToTop.expected.test @@ -0,0 +1,4 @@ +using System; +using System.Web; +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..c1389e511 --- /dev/null +++ b/Src/CSharpier.Tests/FormattingTests/TestFiles/cs/UsingDirectives_SortsSystemToTop.test @@ -0,0 +1,4 @@ +using ZWord; +using AWord; +using System.Web; +using System; diff --git a/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs b/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs index 642889d1e..602a07e0d 100644 --- a/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs +++ b/Src/CSharpier.Tests/SyntaxNodeComparerTests.cs @@ -11,9 +11,9 @@ public class SyntaxNodeComparerTests public void Class_Not_Equal_Namespace() { var left = "class ClassName { }"; - var right = @"namespace Namespace { }"; + var right = "namespace Namespace { }"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); ResultShouldBe( result, @@ -33,7 +33,7 @@ public void Class_Not_Equal_Class_Different_Whitespace() @"class ClassName { }"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); result.Should().BeEmpty(); } @@ -60,7 +60,7 @@ public ConstructorWithBase(string value) } "; - var result = AreEqual(left, right); + var result = CompareSource(left, right); ResultShouldBe( result, @@ -104,7 +104,7 @@ public Resources() { } } "; - var result = AreEqual(left, right); + var result = CompareSource(left, right); ResultShouldBe( result, @@ -167,7 +167,7 @@ public DropdownAttribute(bool ignoreIfNotPresent) } "; - var result = AreEqual(left, right); + var result = CompareSource(left, right); result.Should().BeEmpty(); } @@ -187,7 +187,7 @@ public void MissingSemiColon() Integer, String, }"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); ResultShouldBe( result, @@ -211,7 +211,7 @@ public void Extra_SyntaxTrivia_Should_Work() var left = " public class ClassName { }"; var right = "public class ClassName { }"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); result.Should().BeEmpty(); } @@ -223,7 +223,7 @@ public void Mismatched_Syntax_Trivia_Should_Print_Error() public class ClassName { }"; var right = "public class ClassName { }"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); ResultShouldBe( result, @"----------------------------- Original: Around Line 0 ----------------------------- @@ -255,7 +255,7 @@ public class ClassName { }"; // 7 public class ClassName { }"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); ResultShouldBe( result, @"----------------------------- Original: Around Line 5 ----------------------------- @@ -286,7 +286,7 @@ public void Mismatched_Line_Endings_In_Verbatim_String_Should_Not_Print_Error(st + start + "\"EndThisLineWith\nEndThisLineWith\n\";\n}"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); result.Should().BeEmpty(); } @@ -308,7 +308,7 @@ public void Mismatched_Disabled_Text_Should_Not_Print_Error() #endif } "; - var result = AreEqual(left, right); + var result = CompareSource(left, right); result.Should().BeEmpty(); } @@ -341,7 +341,7 @@ public void Comments_Should_Ignore_Indent_Width() private string field; }"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); result.Should().BeEmpty(); } @@ -374,7 +374,7 @@ void MethodName() #endif "; - var result = AreEqual(left, right); + var result = CompareSource(left, right); result.Should().BeEmpty(); } @@ -386,7 +386,117 @@ public void Unsorted_Modifiers_Pass_Validation() var right = @"public static class { }"; - var result = AreEqual(left, right); + var result = CompareSource(left, right); + + result.Should().BeEmpty(); + } + + [Test] + public void Sorted_Usings_Pass_Validation() + { + var left = + @"using Monday; +using Zebra; +using Apple; +using Banana; +using Yellow;"; + + var right = + @"using Apple; +using Banana; +using Monday; +using Yellow; +using Zebra; +"; + + var result = CompareSource(left, right); + + result.Should().BeEmpty(); + } + + [Test] + public void Extra_Usings_Fails_Validation() + { + var left = + @"using Zebra; +using Apple; +"; + + var right = + @"using Apple; +using Monday; +using Zebra; +"; + + var result = CompareSource(left, right); + + ResultShouldBe( + result, + @"----------------------------- Original: Around Line 0 ----------------------------- +using Zebra; +using Apple; +----------------------------- Formatted: Around Line 0 ----------------------------- +using Apple; +using Monday; +using Zebra; +" + ); + } + + [Test] + public void Sorted_Usings_With_Header_Pass_Validation() + { + var left = + @"// some copyright + +using Zebra; +using Apple; +"; + + var right = + @"// some copyright1 + +using Apple; +using Zebra; +"; + + var result = CompareSource(left, right); + + result.Should().BeEmpty(); + } + + [Test] + [Ignore("no clue how to solve this")] + public void Usings_With_Directives_Pass_Validation() + { + // The problem is that the #endif leading trivia to the ClassDeclaration + // which then fails the compare + // that class could be an interface, enum, top level statement, etc + // so there doesn't seem to be any good way to handle this + // it will only fail the compare the first time that it sorts, so doesn't seem worth fixing + var left = + @"#if DEBUG +using System; +#else +using Microsoft; +#endif +using System.IO; + +private class Class { } +"; + + var right = + @"using System.IO; +#if DEBUG +using System; +#else +using Microsoft; +#endif + +private class Class { } +"; + + var result = CompareSource(left, right); result.Should().BeEmpty(); } @@ -401,7 +511,7 @@ private static void ResultShouldBe(string result, string be) result.Should().Be(be); } - private static string AreEqual(string left, string right) + private static string CompareSource(string left, string right) { var result = new SyntaxNodeComparer( left, diff --git a/Src/CSharpier/SyntaxNodeComparer.cs b/Src/CSharpier/SyntaxNodeComparer.cs index 1527379ed..f0a51f87c 100644 --- a/Src/CSharpier/SyntaxNodeComparer.cs +++ b/Src/CSharpier/SyntaxNodeComparer.cs @@ -313,6 +313,42 @@ private CompareResult Compare(SyntaxTriviaList originalList, SyntaxTriviaList fo return Equal; } + + private CompareResult CompareUsingDirectives( + SyntaxList original, + SyntaxList formatted, + SyntaxNode originalParent, + SyntaxNode formattedParent + ) + { + if (original.Count > 0 && original.First().GetLeadingTrivia().Any()) + { + return Equal; + } + + if (original.Count != formatted.Count) + { + return NotEqual(originalParent, formattedParent); + } + + var sortedOriginal = original.OrderBy(o => o.ToFullString().Trim()).ToList(); + var sortedFormatted = formatted.OrderBy(o => o.ToFullString().Trim()).ToList(); + + for (var x = 0; x < original.Count; x++) + { + var result = this.Compare( + (sortedOriginal[x], originalParent), + (sortedFormatted[x], formattedParent) + ); + + if (result.IsInvalid) + { + return result; + } + } + + return Equal; + } } internal struct CompareResult diff --git a/Src/CSharpier/SyntaxNodeComparer.generated.cs b/Src/CSharpier/SyntaxNodeComparer.generated.cs index cf93f2908..522dcb733 100644 --- a/Src/CSharpier/SyntaxNodeComparer.generated.cs +++ b/Src/CSharpier/SyntaxNodeComparer.generated.cs @@ -1073,7 +1073,7 @@ private CompareResult CompareCompilationUnitSyntax(CompilationUnitSyntax origina if (originalNode.IsMissing != formattedNode.IsMissing) return NotEqual(originalNode, formattedNode); result = this.CompareLists(originalNode.Members, formattedNode.Members, null, o => o.Span, originalNode.Span, formattedNode.Span); if (result.IsInvalid) return result; - result = this.CompareLists(originalNode.Usings, formattedNode.Usings, null, o => o.Span, originalNode.Span, formattedNode.Span); + result = this.CompareUsingDirectives(originalNode.Usings, formattedNode.Usings, originalNode, formattedNode); if (result.IsInvalid) return result; return Equal; } @@ -1711,7 +1711,7 @@ private CompareResult CompareFileScopedNamespaceDeclarationSyntax(FileScopedName if (result.IsInvalid) return result; result = this.Compare(originalNode.SemicolonToken, formattedNode.SemicolonToken, originalNode, formattedNode); if (result.IsInvalid) return result; - result = this.CompareLists(originalNode.Usings, formattedNode.Usings, null, o => o.Span, originalNode.Span, formattedNode.Span); + result = this.CompareUsingDirectives(originalNode.Usings, formattedNode.Usings, originalNode, formattedNode); if (result.IsInvalid) return result; return Equal; } @@ -2592,7 +2592,7 @@ private CompareResult CompareNamespaceDeclarationSyntax(NamespaceDeclarationSynt if (result.IsInvalid) return result; result = this.Compare(originalNode.SemicolonToken, formattedNode.SemicolonToken, originalNode, formattedNode); if (result.IsInvalid) return result; - result = this.CompareLists(originalNode.Usings, formattedNode.Usings, null, o => o.Span, originalNode.Span, formattedNode.Span); + result = this.CompareUsingDirectives(originalNode.Usings, formattedNode.Usings, originalNode, formattedNode); if (result.IsInvalid) return result; return Equal; } 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/SyntaxNodePrinters/UsingDirective.cs b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/UsingDirective.cs index 5314f2396..a0e4e9005 100644 --- a/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/UsingDirective.cs +++ b/Src/CSharpier/SyntaxPrinter/SyntaxNodePrinters/UsingDirective.cs @@ -10,9 +10,9 @@ public static Doc Print( { return Doc.Concat( printExtraLines ? ExtraNewLines.Print(node) : Doc.Null, - Token.PrintWithSuffix(node.GlobalKeyword, " ", context), - Token.PrintWithSuffix(node.UsingKeyword, " ", context), - Token.PrintWithSuffix(node.StaticKeyword, " ", context), + Token.PrintWithSuffix(node.GlobalKeyword, " ", context, skipLeadingTrivia: true), + Token.PrintWithSuffix(node.UsingKeyword, " ", context, skipLeadingTrivia: true), + Token.PrintWithSuffix(node.StaticKeyword, " ", context, skipLeadingTrivia: true), node.Alias == null ? Doc.Null : NameEquals.Print(node.Alias, context), Node.Print(node.NamespaceOrType, context), Token.Print(node.SemicolonToken, context) diff --git a/Src/CSharpier/SyntaxPrinter/Token.cs b/Src/CSharpier/SyntaxPrinter/Token.cs index 1d0e6c650..deb224ea2 100644 --- a/Src/CSharpier/SyntaxPrinter/Token.cs +++ b/Src/CSharpier/SyntaxPrinter/Token.cs @@ -17,10 +17,11 @@ public static Doc Print(SyntaxToken syntaxToken, FormattingContext context) public static Doc PrintWithSuffix( SyntaxToken syntaxToken, Doc suffixDoc, - FormattingContext context + FormattingContext context, + bool skipLeadingTrivia = false ) { - return PrintSyntaxToken(syntaxToken, context, suffixDoc); + return PrintSyntaxToken(syntaxToken, context, suffixDoc, skipLeadingTrivia); } private static Doc PrintSyntaxToken( @@ -251,7 +252,7 @@ void AddLeadingComment(CommentType commentType) } } - while (skipLastHardline && docs.Any() && docs.Last() is HardLine) + while (skipLastHardline && docs.Any() && docs.Last() is HardLine or NullDoc) { docs.RemoveAt(docs.Count - 1); } diff --git a/Src/CSharpier/SyntaxPrinter/UsingDirectives.cs b/Src/CSharpier/SyntaxPrinter/UsingDirectives.cs new file mode 100644 index 000000000..1d0c4d4a1 --- /dev/null +++ b/Src/CSharpier/SyntaxPrinter/UsingDirectives.cs @@ -0,0 +1,259 @@ +namespace CSharpier.SyntaxPrinter; + +internal static class UsingDirectives +{ + private static readonly DefaultOrder Comparer = new(); + + public static Doc PrintWithSorting( + SyntaxList usings, + FormattingContext context, + bool printExtraLines + ) + { + if (usings.Count == 0) + { + return Doc.Null; + } + + var docs = new List(); + var usingList = usings.ToList(); + + var initialComments = new List(); + var triviaWithinIf = new List(); + var foundIfDirective = false; + var keepUsingsUntilEndIf = false; + foreach (var leadingTrivia in usings[0].GetLeadingTrivia()) + { + if ( + leadingTrivia.RawSyntaxKind() == SyntaxKind.DisabledTextTrivia + && leadingTrivia.ToFullString().TrimStart().StartsWith("extern alias") + ) + { + initialComments = usings[0].GetLeadingTrivia().ToList(); + triviaWithinIf.Clear(); + keepUsingsUntilEndIf = true; + break; + } + if ( + leadingTrivia.RawSyntaxKind() == SyntaxKind.DefineDirectiveTrivia + || leadingTrivia.RawSyntaxKind() == SyntaxKind.UndefDirectiveTrivia + ) + { + initialComments = usings.First().GetLeadingTrivia().ToList(); + triviaWithinIf.Clear(); + break; + } + if (leadingTrivia.RawSyntaxKind() == SyntaxKind.IfDirectiveTrivia) + { + foundIfDirective = true; + } + + if (foundIfDirective) + { + triviaWithinIf.Add(leadingTrivia); + } + else + { + initialComments.Add(leadingTrivia); + } + } + + docs.Add(Token.PrintLeadingTrivia(new SyntaxTriviaList(initialComments), context)); + if (keepUsingsUntilEndIf) + { + while (usingList.Any()) + { + var firstUsing = usingList.First(); + + usingList.RemoveAt(0); + if (firstUsing != usings[0]) + { + docs.Add(Token.PrintLeadingTrivia(firstUsing.GetLeadingTrivia(), context)); + } + docs.Add(UsingDirective.Print(firstUsing, context)); + + if ( + firstUsing + .GetLeadingTrivia() + .Any(o => o.RawSyntaxKind() == SyntaxKind.EndIfDirectiveTrivia) + ) + { + break; + } + } + } + + var isFirst = true; + foreach ( + var groupOfUsingData in GroupUsings( + usingList, + new SyntaxTriviaList(triviaWithinIf), + context + ) + ) + { + foreach (var usingData in groupOfUsingData) + { + if (!isFirst) + { + docs.Add(Doc.HardLine); + } + + if (usingData.LeadingTrivia != Doc.Null) + { + docs.Add(usingData.LeadingTrivia); + } + if (usingData.Using is not null) + { + docs.Add(UsingDirective.Print(usingData.Using, context, printExtraLines)); + } + + isFirst = false; + } + } + + return Doc.Concat(docs); + } + + private static IEnumerable> GroupUsings( + List usings, + SyntaxTriviaList triviaOnFirstUsing, + FormattingContext context + ) + { + var globalUsings = new List(); + var systemUsings = new List(); + var aliasNameUsings = new List(); + var regularUsings = new List(); + var staticUsings = new List(); + var aliasUsings = new List(); + var directiveGroup = new List(); + var ifCount = 0; + var isFirst = true; + + foreach (var usingDirective in usings) + { + var openIf = ifCount > 0; + foreach (var directive in usingDirective.GetLeadingTrivia().Where(o => o.IsDirective)) + { + if (directive.RawSyntaxKind() is SyntaxKind.IfDirectiveTrivia) + { + ifCount++; + } + else if (directive.RawSyntaxKind() is SyntaxKind.EndIfDirectiveTrivia) + { + ifCount--; + } + } + + if (ifCount > 0) + { + directiveGroup.Add( + new UsingData + { + Using = usingDirective, + LeadingTrivia = PrintLeadingTrivia(usingDirective) + } + ); + } + else + { + if (openIf) + { + directiveGroup.Add( + new UsingData { LeadingTrivia = PrintLeadingTrivia(usingDirective) } + ); + } + + var usingData = new UsingData + { + Using = usingDirective, + LeadingTrivia = !openIf ? PrintLeadingTrivia(usingDirective) : Doc.Null + }; + + if (usingDirective.GlobalKeyword.RawSyntaxKind() != SyntaxKind.None) + { + globalUsings.Add(usingData); + } + else if (usingDirective.StaticKeyword.RawSyntaxKind() != SyntaxKind.None) + { + staticUsings.Add(usingData); + } + else if (usingDirective.Alias is not null) + { + aliasUsings.Add(usingData); + } + else if (usingDirective.Name is AliasQualifiedNameSyntax) + { + aliasNameUsings.Add(usingData); + } + else if (usingDirective.Name is not null && IsSystemName(usingDirective.Name)) + { + systemUsings.Add(usingData); + } + else + { + regularUsings.Add(usingData); + } + } + } + + yield return globalUsings.OrderBy(o => o.Using, Comparer).ToList(); + yield return systemUsings.OrderBy(o => o.Using, Comparer).ToList(); + yield return aliasNameUsings.OrderBy(o => o.Using, Comparer).ToList(); + yield return regularUsings.OrderBy(o => o.Using, Comparer).ToList(); + yield return directiveGroup; + yield return staticUsings.OrderBy(o => o.Using, Comparer).ToList(); + yield return aliasUsings.OrderBy(o => o.Using, Comparer).ToList(); + yield break; + + Doc PrintLeadingTrivia(UsingDirectiveSyntax value) + { + var result = isFirst + ? Token.PrintLeadingTrivia(triviaOnFirstUsing, context) + : Token.PrintLeadingTrivia(value.GetLeadingTrivia(), context); + + isFirst = false; + return result; + } + } + + private class UsingData + { + public Doc LeadingTrivia { get; init; } = Doc.Null; + public UsingDirectiveSyntax? Using { get; init; } + } + + private static bool IsSystemName(NameSyntax value) + { + while (value is QualifiedNameSyntax qualifiedNameSyntax) + { + value = qualifiedNameSyntax.Left; + } + + return value is IdentifierNameSyntax { Identifier.Text: "System" }; + } + + 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; + } + + if (x.Alias is not null && y.Alias is not null) + { + return x.Alias.ToFullString().CompareTo(y.Alias.ToFullString()); + } + + return x.Name.ToFullString().CompareTo(y.Name.ToFullString()); + } + } +}