diff --git a/CodeCracker.sln b/CodeCracker.sln
index 4a19abe95..a20270f5b 100644
--- a/CodeCracker.sln
+++ b/CodeCracker.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26430.14
+VisualStudioVersion = 15.0.27130.2026
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeCracker.Common", "src\Common\CodeCracker.Common\CodeCracker.Common.csproj", "{753D4757-FCBA-43BA-B1BE-89201ACDA192}"
EndProject
@@ -154,4 +154,7 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EF50C6AB-1421-41AE-8CB3-927AB24A69EA}
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {15B937E4-F880-478B-B387-3438FDD895F7}
+ EndGlobalSection
EndGlobal
diff --git a/runTestsCS.ps1 b/runTestsCS.ps1
index 930bdccb4..b3f56a286 100644
--- a/runTestsCS.ps1
+++ b/runTestsCS.ps1
@@ -1,8 +1,10 @@
param([String]$testClass)
-$testDllDirPath = "$PSScriptRoot\test\CSharp\CodeCracker.Test\bin\Debug\"
+# $testDllDirPath = "$PSScriptRoot\test\CSharp\CodeCracker.Test\bin\Debug\"
+$testDllDirPath = "test\CSharp\CodeCracker.Test\bin\Debug\"
$testDllFileName = "CodeCracker.Test.CSharp.dll"
$testDllFullFileName = "$testDllDirPath$testDllFileName"
-$xunitConsole = "$PSScriptRoot\packages\xunit.runner.console.2.2.0\tools\xunit.console.x86.exe"
+# $xunitConsole = "$PSScriptRoot\packages\xunit.runner.console.2.2.0\tools\xunit.console.x86.exe"
+$xunitConsole = "packages\xunit.runner.console.2.2.0\tools\xunit.console.x86.exe"
if (!(gcm nodemon -ErrorAction Ignore)) {
Write-Host -ForegroundColor DarkRed 'Nodemon not found, install it with npm: `npm i -g nodemon`'
diff --git a/src/CSharp/CodeCracker/CodeCracker.csproj b/src/CSharp/CodeCracker/CodeCracker.csproj
index 337211dff..449e7bdff 100644
--- a/src/CSharp/CodeCracker/CodeCracker.csproj
+++ b/src/CSharp/CodeCracker/CodeCracker.csproj
@@ -71,6 +71,8 @@
+
+
diff --git a/src/CSharp/CodeCracker/Refactoring/SortUsingsAnalyzer.cs b/src/CSharp/CodeCracker/Refactoring/SortUsingsAnalyzer.cs
new file mode 100644
index 000000000..2c0cdd16d
--- /dev/null
+++ b/src/CSharp/CodeCracker/Refactoring/SortUsingsAnalyzer.cs
@@ -0,0 +1,37 @@
+using Microsoft.CodeAnalysis;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace CodeCracker.CSharp.Refactoring
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public class SortUsingsAnalyzer : DiagnosticAnalyzer
+ {
+ internal const string Title = "Sort by length.";
+ internal const string MessageFormat = "Sort Using directives by length";
+ internal const string Category = SupportedCategories.Refactoring;
+
+ internal static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
+ DiagnosticId.SortUsings.ToDiagnosticId(),
+ Title,
+ MessageFormat,
+ Category,
+ DiagnosticSeverity.Hidden,
+ isEnabledByDefault: true,
+ helpLinkUri: HelpLink.ForDiagnostic(DiagnosticId.SortUsings));
+
+ public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
+
+ public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.CompilationUnit);
+
+ private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
+ {
+ if (context.IsGenerated()) return;
+ var root = (CompilationUnitSyntax)context.Node;
+ if (root.Usings.Count > 0)
+ context.ReportDiagnostic(Diagnostic.Create(Rule, root.GetLocation()));
+ }
+ }
+}
diff --git a/src/CSharp/CodeCracker/Refactoring/SortUsingsCodeFixProvider.cs b/src/CSharp/CodeCracker/Refactoring/SortUsingsCodeFixProvider.cs
new file mode 100644
index 000000000..e96dbb46e
--- /dev/null
+++ b/src/CSharp/CodeCracker/Refactoring/SortUsingsCodeFixProvider.cs
@@ -0,0 +1,60 @@
+using System.Linq;
+using System.Threading;
+using System.Composition;
+using Microsoft.CodeAnalysis;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.Formatting;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace CodeCracker.CSharp.Refactoring
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SortUsingsCodeFixProvider)), Shared]
+ public class SortUsingsCodeFixProvider : CodeFixProvider
+ {
+ public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId.SortUsings.ToDiagnosticId());
+
+ public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
+
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var diagnostic = context.Diagnostics.First();
+ context.RegisterCodeFix(CodeAction.Create("Sort by length", ct => SortUsingsAsync(context.Document, diagnostic, ct), nameof(SortUsingsCodeFixProvider)), diagnostic);
+ return Task.FromResult(0);
+ }
+
+ private static async Task SortUsingsAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
+ {
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ var sourceSpan = (CompilationUnitSyntax)root.FindNode(diagnostic.Location.SourceSpan);
+
+ var collector = new UsingCollector();
+ collector.Visit(root);
+
+ var sections = new List();
+ sections.AddRange(collector.Usings
+ .OrderBy(u => u.ToString().Contains(" static "))
+ .ThenBy(u => u.ToString().Length));
+
+ var newUsings = sourceSpan.WithUsings(SyntaxFactory.List(sections)).WithAdditionalAnnotations(Formatter.Annotation);
+ var newRoot = root.ReplaceNode(sourceSpan, newUsings);
+ return document.WithSyntaxRoot(newRoot);
+ }
+ }
+
+ internal sealed class UsingCollector : CSharpSyntaxWalker
+ {
+ private readonly List usingCollection = new List();
+
+ public IEnumerable Usings => usingCollection;
+
+ public override void VisitUsingDirective(UsingDirectiveSyntax node)
+ {
+ this.usingCollection.Add(node);
+ }
+ }
+}
diff --git a/src/Common/CodeCracker.Common/DiagnosticId.cs b/src/Common/CodeCracker.Common/DiagnosticId.cs
index 4dfc7ecb6..3884c00dd 100644
--- a/src/Common/CodeCracker.Common/DiagnosticId.cs
+++ b/src/Common/CodeCracker.Common/DiagnosticId.cs
@@ -85,5 +85,6 @@ public enum DiagnosticId
SwitchCaseWithoutDefault = 120,
ReadOnlyComplexTypes = 121,
ReplaceWithGetterOnlyAutoProperty = 125,
+ SortUsings = 126,
}
}
\ No newline at end of file
diff --git a/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj b/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj
index 4d186cfbe..a883a6bf1 100644
--- a/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj
+++ b/test/CSharp/CodeCracker.Test/CodeCracker.Test.csproj
@@ -132,6 +132,7 @@
+
diff --git a/test/CSharp/CodeCracker.Test/Refactoring/SortUsingsTest.cs b/test/CSharp/CodeCracker.Test/Refactoring/SortUsingsTest.cs
new file mode 100644
index 000000000..0b61016dd
--- /dev/null
+++ b/test/CSharp/CodeCracker.Test/Refactoring/SortUsingsTest.cs
@@ -0,0 +1,65 @@
+using Xunit;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using CodeCracker.CSharp.Refactoring;
+
+namespace CodeCracker.Test.CSharp.Refactoring
+{
+ public class SortUsingsTest : CodeFixVerifier
+ {
+ [Fact]
+ public async Task CreateDiagnosticSortingUsings()
+ {
+ const string usings = @"using System;
+using System.Collections.Generic;
+using System.Linq;
+using static System.Console;
+using System.Text;
+using static System.DateTime;
+using System.Threading.Tasks;";
+
+ const string source = @"static void Main(string[] args)
+{
+}";
+
+ var diagnostic = new DiagnosticResult
+ {
+ Id = DiagnosticId.SortUsings.ToDiagnosticId(),
+ Message = "Sort Using directives by length",
+ Severity = DiagnosticSeverity.Hidden,
+ Locations = new[] { new DiagnosticResultLocation("Test0.cs", 2, 5) }
+ };
+ await VerifyCSharpDiagnosticAsync(source.WrapInCSharpClass("Program", usings), diagnostic);
+ }
+
+ [Fact]
+ public async Task FixSortingUsings()
+ {
+ const string actualUsings = @"using System;
+using System.Collections.Generic;
+using System.Linq;
+using static System.Console;
+using System.Text;
+using static System.DateTime;
+using System.Threading.Tasks;";
+
+ const string expectedUsings = @"using System;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using static System.Console;
+using static System.DateTime;";
+
+ const string code = @"static void Main(string[] args)
+{
+}";
+ const string typeName = "Program";
+
+ var oldSource = code.WrapInCSharpClass(typeName, actualUsings);
+ var newSource = code.WrapInCSharpClass(typeName, expectedUsings);
+ await VerifyCSharpFixAsync(oldSource, newSource);
+ }
+
+ }
+}