diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..5dc136a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+# Auto-detect text files so that they can be stored with LF endings
+# in github, and CRLF endings locally in Windows.
+* text=auto
+
+# Avoid potential misdetection of some common file types
+*.cs text
+*.csproj text
+*.Config text
+*.config text
+*.StyleCop text
+*.resx text
+
+# Apparently ReSharper uses LF line endings in its settings files,
+# so tell git that we know this to avoid warnings.
+*.DotSettings text eol=lf
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0881b8d..94e9b58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -97,8 +97,10 @@ publish/
*.pubxml
# NuGet Packages Directory
-## TODO: If you have NuGet Package Restore enabled, uncomment the next line
-#packages/
+packages/
+
+# NuGet packages
+*.nupkg
# Windows Azure Build Output
csx
diff --git a/nuget/OrderUsings.nuspec b/nuget/OrderUsings.nuspec
new file mode 100644
index 0000000..7bc4908
--- /dev/null
+++ b/nuget/OrderUsings.nuspec
@@ -0,0 +1,25 @@
+
+
+
+ OrderUsings
+ Order and space using directives
+ 1.0.0
+ Ian Griffiths
+ Ian Griffiths
+ Enables configurable fine control over the ordering, grouping, and spacing of C# using directives. Instead of being limited to two groups - System*, and everything else - you can define any number of groups in any order to arrange using directives however makes most sense for your project.
+ Control over the ordering, grouping, and spacing of C# using directives.
+ Copyright © 2014 Ian Griffiths
+ • 1.0.0 - First release (supporting only ReSharper 8.1)
+ https://github.com/idg10/order-usings
+ https://github.com/idg10/order-usings/blob/master/LICENSE
+
+
+
+
+
+
+
+
+
diff --git a/src/.nuget/packages.config b/src/.nuget/packages.config
new file mode 100644
index 0000000..7025a72
--- /dev/null
+++ b/src/.nuget/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/OrderAndSpacingDeterminationTestBase.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/OrderAndSpacingDeterminationTestBase.cs
new file mode 100644
index 0000000..5c751f4
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/OrderAndSpacingDeterminationTestBase.cs
@@ -0,0 +1,102 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination
+{
+ using System.Collections.Generic;
+ using System.Linq;
+
+ using NUnit.Framework;
+
+ using OrderUsings.Configuration;
+ using OrderUsings.Processing;
+
+ public abstract class OrderAndSpacingDeterminationTestBase
+ {
+ internal static readonly UsingDirective AliasSystemPathAsPath = new UsingDirective { Namespace = "System.IO.Path", Alias = "Path" };
+ internal static readonly UsingDirective AliasSystemLaterAsEarlier = new UsingDirective { Namespace = "System.Later", Alias = "Earlier" };
+ internal static readonly UsingDirective AliasSystemTextAsSystem = new UsingDirective { Namespace = "System.Text", Alias = "System" };
+
+ internal static readonly UsingDirective ImportSystem = new UsingDirective { Namespace = "System" };
+ internal static readonly UsingDirective ImportSystemCollectionsGeneric = new UsingDirective { Namespace = "System.Collections.Generic" };
+ internal static readonly UsingDirective ImportSystemLinq = new UsingDirective { Namespace = "System.Linq" };
+
+ internal static readonly UsingDirective ImportMicrosoftCSharp = new UsingDirective { Namespace = "Microsoft.CSharp" };
+
+ internal static readonly UsingDirective ImportOther = new UsingDirective { Namespace = "Other" };
+ internal static readonly UsingDirective ImportOtherA = new UsingDirective { Namespace = "Other.A" };
+ internal static readonly UsingDirective ImportOtherB = new UsingDirective { Namespace = "Other.B" };
+
+ internal static readonly UsingDirective ImportMyLocal = new UsingDirective { Namespace = "MyLocal" };
+ internal static readonly UsingDirective ImportMyLocalA = new UsingDirective { Namespace = "MyLocal.A" };
+ internal static readonly UsingDirective ImportMyLocalB = new UsingDirective { Namespace = "MyLocal.B" };
+
+ internal static readonly UsingDirective ImportRuhroh = new UsingDirective { Namespace = "Ruhroh" };
+
+
+ internal OrderUsingsConfiguration Configuration { get; private set; }
+
+ [SetUp]
+ public void BaseInitialize()
+ {
+ Configuration = new OrderUsingsConfiguration
+ {
+ GroupsAndSpaces = GetRules()
+ };
+ }
+
+ internal abstract List GetRules();
+
+ internal void Verify(UsingDirective[] directivesIn, params UsingDirective[][] expectedGroups)
+ {
+ foreach (var permutation in AllOrderings(directivesIn))
+ {
+ var results = OrderAndSpacingGenerator.DetermineOrderAndSpacing(
+ permutation, Configuration);
+
+ Assert.AreEqual(expectedGroups.Length, results.Count);
+ for (int i = 0; i < expectedGroups.Length; ++i)
+ {
+ Assert.AreEqual(results[i].Count, expectedGroups[i].Length, "Item count in group " + i);
+ for (int j = 0; j < expectedGroups[i].Length; ++j)
+ {
+ UsingDirective expectedUsing = expectedGroups[i][j];
+ UsingDirective actualUsing = results[i][j];
+ string message = string.Format(
+ "Expected {0} at {1},{2}, found {3}", expectedUsing, i, j, actualUsing);
+ Assert.AreSame(expectedUsing, actualUsing, message);
+ }
+ }
+ }
+ }
+
+ // This is the same for all configurations. We only want to run the test
+ // once per config, so we don't make this a [Test] in this base class - that
+ // would run it once per derived class. Instead, just one derived classes
+ // per config will defer to this.
+ protected void VerifyEmptyHandling()
+ {
+ Verify(new UsingDirective[0], new UsingDirective[0][]);
+ }
+
+ private static IEnumerable> AllOrderings(IEnumerable items)
+ {
+ bool returnedAtLeastOne = false;
+ int index = 0;
+// ReSharper disable PossibleMultipleEnumeration
+ foreach (T item in items)
+ {
+ returnedAtLeastOne = true;
+ int thisIndex = index;
+ foreach (var remainders in AllOrderings(items.Where((x, i) => i != thisIndex)))
+ {
+ yield return new[] { item }.Concat(remainders);
+ }
+ index += 1;
+ }
+ // ReSharper restore PossibleMultipleEnumeration
+
+ if (!returnedAtLeastOne)
+ {
+ yield return Enumerable.Empty();
+ }
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/SinglePatternMatchesAllTestBase.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/SinglePatternMatchesAllTestBase.cs
new file mode 100644
index 0000000..da6cbf6
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/SinglePatternMatchesAllTestBase.cs
@@ -0,0 +1,24 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.SinglePatternMatchesAll
+{
+ using System.Collections.Generic;
+
+ using OrderUsings.Configuration;
+
+ public abstract class SinglePatternMatchesAllTestBase : OrderAndSpacingDeterminationTestBase
+ {
+ internal override List GetRules()
+ {
+ return new List
+ {
+ ConfigurationRule.ForGroupRule(new GroupRule
+ {
+ Type = MatchType.ImportOrAlias,
+ NamespacePattern = "*",
+ AliasPattern = "*",
+ Priority = 1,
+ OrderAliasesBy = OrderAliasBy.Alias
+ })
+ };
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenAliasesOrderedByAlias.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenAliasesOrderedByAlias.cs
new file mode 100644
index 0000000..83e3179
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenAliasesOrderedByAlias.cs
@@ -0,0 +1,30 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.SinglePatternMatchesAll
+{
+ using NUnit.Framework;
+
+ public class WhenAliasesOrderedByAlias : SinglePatternMatchesAllTestBase
+ {
+ [Test]
+ public void ProducesSingleGroupOrderedByAliasWhenAliasesAndNamespaceOtherwise()
+ {
+ Verify(
+ new[]
+ {
+ AliasSystemPathAsPath,
+ ImportSystem,
+ ImportRuhroh,
+ AliasSystemLaterAsEarlier
+ },
+ new[]
+ {
+ new[]
+ {
+ AliasSystemLaterAsEarlier,
+ AliasSystemPathAsPath,
+ ImportRuhroh,
+ ImportSystem
+ }
+ });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenAliasesOrderedByNamespace.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenAliasesOrderedByNamespace.cs
new file mode 100644
index 0000000..9df0758
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenAliasesOrderedByNamespace.cs
@@ -0,0 +1,49 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.SinglePatternMatchesAll
+{
+ using System.Collections.Generic;
+
+ using NUnit.Framework;
+
+ using OrderUsings.Configuration;
+
+ public class WhenAliasesOrderedByNamespace : SinglePatternMatchesAllTestBase
+ {
+ internal override List GetRules()
+ {
+ var r = base.GetRules();
+ r[0].Rule.OrderAliasesBy = OrderAliasBy.Namespace;
+ return r;
+ }
+
+ [Test]
+ public void ProducesSingleGroupOrderedByNamespace()
+ {
+ Verify(
+ new[]
+ {
+ AliasSystemPathAsPath,
+ ImportSystem,
+ ImportRuhroh,
+ AliasSystemLaterAsEarlier
+ },
+ new[]
+ {
+ new[]
+ {
+ ImportRuhroh,
+ ImportSystem,
+ AliasSystemPathAsPath,
+ AliasSystemLaterAsEarlier
+ }
+ });
+ }
+
+ // This class introduces a change in config, so we should verify that
+ // empty input handling still works.
+ [Test]
+ public void EmptyUsingsProducesNoGroups()
+ {
+ VerifyEmptyHandling();
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenImportAndAliasShareName.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenImportAndAliasShareName.cs
new file mode 100644
index 0000000..e0a1314
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenImportAndAliasShareName.cs
@@ -0,0 +1,33 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.SinglePatternMatchesAll
+{
+ using NUnit.Framework;
+
+ public class WhenImportAndAliasShareName : SinglePatternMatchesAllTestBase
+ {
+ [Test]
+ public void GroupItemsShouldPutNonAliasFirst()
+ {
+ // Bizarre though it seems, this:
+ // using System;
+ // using System = System.Text;
+ // is legal. If a group orders using alias directives by Alias (which is the default)
+ // we need to pick one to go first. We put the non-alias one first (i.e., the order
+ // shown above), since this seems most consistent with the behaviour of ordering
+ // usings lexographically within a group.
+ Verify(
+ new[]
+ {
+ AliasSystemTextAsSystem,
+ ImportSystem
+ },
+ new[]
+ {
+ new[]
+ {
+ ImportSystem,
+ AliasSystemTextAsSystem
+ }
+ });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenNoUsings.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenNoUsings.cs
new file mode 100644
index 0000000..5293236
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenNoUsings.cs
@@ -0,0 +1,13 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.SinglePatternMatchesAll
+{
+ using NUnit.Framework;
+
+ public class WhenNoUsings : SinglePatternMatchesAllTestBase
+ {
+ [Test]
+ public void ProducesNoGroups()
+ {
+ VerifyEmptyHandling();
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenOneImport.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenOneImport.cs
new file mode 100644
index 0000000..a99b386
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenOneImport.cs
@@ -0,0 +1,13 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.SinglePatternMatchesAll
+{
+ using NUnit.Framework;
+
+ public class WhenOneImport : SinglePatternMatchesAllTestBase
+ {
+ [Test]
+ public void ProducesOneGroup()
+ {
+ Verify(new[] { ImportSystem }, new[] { new[] { ImportSystem } });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenThreeImports.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenThreeImports.cs
new file mode 100644
index 0000000..646d4de
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/SinglePatternMatchesAll/WhenThreeImports.cs
@@ -0,0 +1,28 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.SinglePatternMatchesAll
+{
+ using NUnit.Framework;
+
+ public class WhenThreeImports : SinglePatternMatchesAllTestBase
+ {
+ [Test]
+ public void ProducesOneGroupInAlphabeticalOrder()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportSystemCollectionsGeneric,
+ ImportSystemLinq
+ },
+ new[]
+ {
+ new[]
+ {
+ ImportSystem,
+ ImportSystemCollectionsGeneric,
+ ImportSystemLinq
+ }
+ });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/TwoSpecificAdjacentPatternsWithoutSpaceThenFallbackBase.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/TwoSpecificAdjacentPatternsWithoutSpaceThenFallbackBase.cs
new file mode 100644
index 0000000..e3de473
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/TwoSpecificAdjacentPatternsWithoutSpaceThenFallbackBase.cs
@@ -0,0 +1,39 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificAdjacentPatternsWithoutSpaceThenFallback
+{
+ using System.Collections.Generic;
+
+ using OrderUsings.Configuration;
+ using OrderUsings.Tests.OrderAndSpacingDetermination;
+
+ public abstract class TwoSpecificAdjacentPatternsWithoutSpaceThenFallbackBase : OrderAndSpacingDeterminationTestBase
+ {
+ internal override List GetRules()
+ {
+ return new List
+ {
+ ConfigurationRule.ForGroupRule(new GroupRule
+ {
+ Type = MatchType.Import,
+ NamespacePattern = "System*",
+ Priority = 1
+ }),
+ ConfigurationRule.ForGroupRule(new GroupRule
+ {
+ Type = MatchType.Import,
+ NamespacePattern = "Microsoft*",
+ Priority = 1
+ }),
+
+ ConfigurationRule.ForSpace(),
+
+ ConfigurationRule.ForGroupRule(new GroupRule
+ {
+ Type = MatchType.ImportOrAlias,
+ NamespacePattern = "*",
+ AliasPattern = "*",
+ Priority = 2
+ })
+ };
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenNoUsings.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenNoUsings.cs
new file mode 100644
index 0000000..cb15d91
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenNoUsings.cs
@@ -0,0 +1,13 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificAdjacentPatternsWithoutSpaceThenFallback
+{
+ using NUnit.Framework;
+
+ public class WhenNoUsings : TwoSpecificAdjacentPatternsWithoutSpaceThenFallbackBase
+ {
+ [Test]
+ public void ProducesNoGroups()
+ {
+ VerifyEmptyHandling();
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenOneImport.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenOneImport.cs
new file mode 100644
index 0000000..632d5fe
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenOneImport.cs
@@ -0,0 +1,25 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificAdjacentPatternsWithoutSpaceThenFallback
+{
+ using NUnit.Framework;
+
+ public class WhenOneImport : TwoSpecificAdjacentPatternsWithoutSpaceThenFallbackBase
+ {
+ [Test]
+ public void FirstRuleMatchProducesOneGroup()
+ {
+ Verify(new[] { ImportSystem }, new[] { new[] { ImportSystem } });
+ }
+
+ [Test]
+ public void SecondRuleMatchProducesOneGroup()
+ {
+ Verify(new[] { ImportMicrosoftCSharp }, new[] { new[] { ImportMicrosoftCSharp } });
+ }
+
+ [Test]
+ public void FallbackRuleMatchProducesOneGroup()
+ {
+ Verify(new[] { ImportOther }, new[] { new[] { ImportOther } });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenThreeImports.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenThreeImports.cs
new file mode 100644
index 0000000..126445a
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificAdjacentPatternsWithoutSpaceThenFallback/WhenThreeImports.cs
@@ -0,0 +1,89 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificAdjacentPatternsWithoutSpaceThenFallback
+{
+ using NUnit.Framework;
+
+ public class WhenThreeImports : TwoSpecificAdjacentPatternsWithoutSpaceThenFallbackBase
+ {
+ [Test]
+ public void AllMatchingFirstProducesOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportSystemCollectionsGeneric,
+ ImportSystemLinq
+ },
+ new[]
+ {
+ new[] { ImportSystem, ImportSystemCollectionsGeneric, ImportSystemLinq }
+ });
+ }
+
+ [Test]
+ public void AllMatchingSecondProducesOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportSystemCollectionsGeneric,
+ ImportSystemLinq
+ },
+ new[]
+ {
+ new[] { ImportSystem, ImportSystemCollectionsGeneric, ImportSystemLinq }
+ });
+ }
+
+ [Test]
+ public void AllMatchingFirstAndSecondInOrderProducesOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportSystemCollectionsGeneric,
+ ImportSystemLinq,
+ ImportMicrosoftCSharp
+ },
+ new[]
+ {
+ new[] { ImportSystem, ImportSystemCollectionsGeneric, ImportSystemLinq, ImportMicrosoftCSharp }
+ });
+ }
+
+ [Test]
+ public void AllMatchingFirstAndSecondOutOfOrderProducesOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportMicrosoftCSharp,
+ ImportSystemCollectionsGeneric,
+ ImportSystemLinq
+ },
+ new[]
+ {
+ new[] { ImportSystem, ImportSystemCollectionsGeneric, ImportSystemLinq, ImportMicrosoftCSharp }
+ });
+ }
+
+ [Test]
+ public void AllMatchFallbackProducesOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportOther,
+ ImportOtherA,
+ ImportOtherB
+ },
+ new[]
+ {
+ new[] { ImportOther, ImportOtherA, ImportOtherB }
+ });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/TwoSpecificPatternsWithFallbackInMiddleAllSpacedBase.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/TwoSpecificPatternsWithFallbackInMiddleAllSpacedBase.cs
new file mode 100644
index 0000000..51a3e45
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/TwoSpecificPatternsWithFallbackInMiddleAllSpacedBase.cs
@@ -0,0 +1,41 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificPatternsWithFallbackInMiddleAllSpaced
+{
+ using System.Collections.Generic;
+
+ using OrderUsings.Configuration;
+
+ public abstract class TwoSpecificPatternsWithFallbackInMiddleAllSpacedBase : OrderAndSpacingDeterminationTestBase
+ {
+ internal override List GetRules()
+ {
+ return new List
+ {
+ ConfigurationRule.ForGroupRule(new GroupRule
+ {
+ Type = MatchType.Import,
+ NamespacePattern = "System*",
+ Priority = 1
+ }),
+
+ ConfigurationRule.ForSpace(),
+
+ ConfigurationRule.ForGroupRule(new GroupRule
+ {
+ Type = MatchType.ImportOrAlias,
+ NamespacePattern = "*",
+ AliasPattern = "*",
+ Priority = 2
+ }),
+
+ ConfigurationRule.ForSpace(),
+
+ ConfigurationRule.ForGroupRule(new GroupRule
+ {
+ Type = MatchType.Import,
+ NamespacePattern = "MyLocal*",
+ Priority = 1
+ })
+ };
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenNoUsings.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenNoUsings.cs
new file mode 100644
index 0000000..28ac5f9
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenNoUsings.cs
@@ -0,0 +1,13 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificPatternsWithFallbackInMiddleAllSpaced
+{
+ using NUnit.Framework;
+
+ public class WhenNoUsings : TwoSpecificPatternsWithFallbackInMiddleAllSpacedBase
+ {
+ [Test]
+ public void ProducesNoGroups()
+ {
+ VerifyEmptyHandling();
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenOneImport.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenOneImport.cs
new file mode 100644
index 0000000..d4b853c
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenOneImport.cs
@@ -0,0 +1,25 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificPatternsWithFallbackInMiddleAllSpaced
+{
+ using NUnit.Framework;
+
+ public class WhenOneImport : TwoSpecificPatternsWithFallbackInMiddleAllSpacedBase
+ {
+ [Test]
+ public void FirstRuleMatchProducesOneGroup()
+ {
+ Verify(new[] { ImportSystem }, new[] { new[] { ImportSystem } });
+ }
+
+ [Test]
+ public void FallbackRuleMatchProducesOneGroup()
+ {
+ Verify(new[] { ImportOther }, new[] { new[] { ImportOther } });
+ }
+
+ [Test]
+ public void LastRuleMatchProducesOneGroup()
+ {
+ Verify(new[] { ImportMyLocal }, new[] { new[] { ImportMyLocal } });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenThreeImports.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenThreeImports.cs
new file mode 100644
index 0000000..301f58f
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenThreeImports.cs
@@ -0,0 +1,124 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificPatternsWithFallbackInMiddleAllSpaced
+{
+ using NUnit.Framework;
+
+ public class WhenThreeImports : TwoSpecificPatternsWithFallbackInMiddleAllSpacedBase
+ {
+ [Test]
+ public void AllMatchingFirstProducesOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportSystemCollectionsGeneric,
+ ImportSystemLinq
+ },
+ new[]
+ {
+ new[] { ImportSystem, ImportSystemCollectionsGeneric, ImportSystemLinq }
+ });
+ }
+
+ [Test]
+ public void AllMatchFallbackProducesOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportOther,
+ ImportOtherA,
+ ImportOtherB
+ },
+ new[]
+ {
+ new[] { ImportOther, ImportOtherA, ImportOtherB }
+ });
+ }
+
+ [Test]
+ public void AllMatchLastProducesOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportMyLocal,
+ ImportMyLocalA,
+ ImportMyLocalB
+ },
+ new[]
+ {
+ new[] { ImportMyLocal, ImportMyLocalA, ImportMyLocalB }
+ });
+ }
+
+ [Test]
+ public void MatchingFirstAndFallbackProducesTwoGroups()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportSystemLinq,
+ ImportOther
+ },
+ new[]
+ {
+ new[] { ImportSystem, ImportSystemLinq },
+ new[] { ImportOther }
+ });
+ }
+
+ [Test]
+ public void MatchingFirstAndLastProducesTwoGroups()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportMyLocal,
+ ImportMyLocalA
+ },
+ new[]
+ {
+ new[] { ImportSystem },
+ new[] { ImportMyLocal, ImportMyLocalA }
+ });
+ }
+
+ [Test]
+ public void MatchingFallbackAndLastProducesTwoGroups()
+ {
+ Verify(
+ new[]
+ {
+ ImportOther,
+ ImportMyLocal,
+ ImportMyLocalA
+ },
+ new[]
+ {
+ new[] { ImportOther },
+ new[] { ImportMyLocal, ImportMyLocalA }
+ });
+ }
+
+ [Test]
+ public void MatchingFirstFallbackAndLastProducesThreeGroups()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportOther,
+ ImportMyLocal
+ },
+ new[]
+ {
+ new[] { ImportSystem },
+ new[] { ImportOther },
+ new[] { ImportMyLocal }
+ });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenTwoUsings.cs b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenTwoUsings.cs
new file mode 100644
index 0000000..e8e20c2
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderAndSpacingDetermination/TwoSpecificPatternsWithFallbackInMiddleAllSpaced/WhenTwoUsings.cs
@@ -0,0 +1,68 @@
+namespace OrderUsings.Tests.OrderAndSpacingDetermination.TwoSpecificPatternsWithFallbackInMiddleAllSpaced
+{
+ using NUnit.Framework;
+
+ public class WhenTwoUsings : TwoSpecificPatternsWithFallbackInMiddleAllSpacedBase
+ {
+ [Test]
+ public void UsingsMatchFirstAndLastProduceTwoGroups()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportMyLocal
+ },
+ new[]
+ {
+ new[] { ImportSystem },
+ new[] { ImportMyLocal }
+ });
+ }
+
+ [Test]
+ public void UsingsMatchingFirstRuleProduceOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportSystem,
+ ImportSystemLinq
+ },
+ new[]
+ {
+ new[] { ImportSystem, ImportSystemLinq }
+ });
+ }
+
+ [Test]
+ public void UsingsMatchFallbackRuleProduceOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportOtherA,
+ ImportOtherB
+ },
+ new[]
+ {
+ new[] { ImportOtherA, ImportOtherB }
+ });
+ }
+
+ [Test]
+ public void UsingsMatchingLastRuleProduceOneGroup()
+ {
+ Verify(
+ new[]
+ {
+ ImportMyLocalA,
+ ImportMyLocalB
+ },
+ new[]
+ {
+ new[] { ImportMyLocalA, ImportMyLocalB }
+ });
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderChecking/WhenListsDifferent.cs b/src/OrderUsings.Core.Tests/OrderChecking/WhenListsDifferent.cs
new file mode 100644
index 0000000..3c36af4
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderChecking/WhenListsDifferent.cs
@@ -0,0 +1,43 @@
+namespace OrderUsings.Tests.OrderChecking
+{
+ using NUnit.Framework;
+
+ using OrderUsings.Processing;
+
+ public class WhenListsDifferent
+ {
+ private static readonly UsingDirective D1 = new UsingDirective { Namespace = "System" };
+ private static readonly UsingDirective D2 = new UsingDirective { Namespace = "System.Collections.Generic" };
+ private static readonly UsingDirective D3 = new UsingDirective { Namespace = "System.Linq" };
+
+ [Test]
+ public void ReportsPositionWhenExpectedFirstItemInSecondPlace()
+ {
+ var required = new[] { D1, D2, D3 };
+ var current = new[] { D2, D1, D3 };
+ Relocation result = OrderChecker.GetNextUsingToMove(required, current);
+ Assert.AreEqual(1, result.From, "Source");
+ Assert.AreEqual(0, result.To, "Destination");
+ }
+
+ [Test]
+ public void ReportsPositionWhenExpectedFirstItemInThirdPlace()
+ {
+ var required = new[] { D1, D2, D3 };
+ var current = new[] { D2, D3, D1 };
+ Relocation result = OrderChecker.GetNextUsingToMove(required, current);
+ Assert.AreEqual(2, result.From, "Source");
+ Assert.AreEqual(0, result.To, "Destination");
+ }
+
+ [Test]
+ public void ReportsPositionWhenExpectedSecondItemInWrongPlace()
+ {
+ var required = new[] { D1, D2, D3 };
+ var current = new[] { D1, D3, D2 };
+ Relocation result = OrderChecker.GetNextUsingToMove(required, current);
+ Assert.AreEqual(2, result.From, "Source");
+ Assert.AreEqual(1, result.To, "Destination");
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderChecking/WhenListsEmpty.cs b/src/OrderUsings.Core.Tests/OrderChecking/WhenListsEmpty.cs
new file mode 100644
index 0000000..1b87f52
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderChecking/WhenListsEmpty.cs
@@ -0,0 +1,18 @@
+namespace OrderUsings.Tests.OrderChecking
+{
+ using System.Collections.Generic;
+
+ using NUnit.Framework;
+
+ using OrderUsings.Processing;
+
+ public class WhenListsEmpty
+ {
+ [Test]
+ public void ReturnsNull()
+ {
+ var empty = new List();
+ Assert.IsNull(OrderChecker.GetNextUsingToMove(empty, empty));
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderChecking/WhenListsIdentical.cs b/src/OrderUsings.Core.Tests/OrderChecking/WhenListsIdentical.cs
new file mode 100644
index 0000000..e2a175c
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderChecking/WhenListsIdentical.cs
@@ -0,0 +1,19 @@
+namespace OrderUsings.Tests.OrderChecking
+{
+ using NUnit.Framework;
+
+ using OrderUsings.Processing;
+
+ public class WhenListsIdentical
+ {
+ [Test]
+ public void ReturnsNull()
+ {
+ var d1 = new UsingDirective { Namespace = "System" };
+ var d2 = new UsingDirective { Namespace = "System.Linq" };
+ var required = new[] { d1, d2 };
+ var current = new[] { d1, d2 };
+ Assert.IsNull(OrderChecker.GetNextUsingToMove(required, current));
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/OrderUsings.Core.Tests.csproj b/src/OrderUsings.Core.Tests/OrderUsings.Core.Tests.csproj
new file mode 100644
index 0000000..30534bc
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/OrderUsings.Core.Tests.csproj
@@ -0,0 +1,92 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {638CD37F-59CB-42AE-873D-5E09E9C32EAA}
+ Library
+ Properties
+ OrderUsings.Tests
+ OrderUsings.Core.Tests
+ v4.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\packages\NUnit.2.6.3\lib\nunit.framework.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {9875da0a-db09-47b2-80b5-80b08e430cef}
+ OrderUsings.Core
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/OrderUsings.Core.Tests/Properties/AssemblyInfo.cs b/src/OrderUsings.Core.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4694ce8
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("OrderUsings.Core.Tests")]
+[assembly: AssemblyDescription("Tests for Order Usings core logic")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Ian Griffiths")]
+[assembly: AssemblyProduct("Order Usings")]
+[assembly: AssemblyCopyright("Copyright © Ian Griffiths, 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("5b04bc0f-0e52-463e-9e41-e4024a4bda3d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/OrderUsings.Core.Tests/Settings.StyleCop b/src/OrderUsings.Core.Tests/Settings.StyleCop
new file mode 100644
index 0000000..ca702f3
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/Settings.StyleCop
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/OrderUsings.Core.Tests/SpacingChecking/WhenListsEmpty.cs b/src/OrderUsings.Core.Tests/SpacingChecking/WhenListsEmpty.cs
new file mode 100644
index 0000000..0779bf1
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/SpacingChecking/WhenListsEmpty.cs
@@ -0,0 +1,18 @@
+namespace OrderUsings.Tests.SpacingChecking
+{
+ using System.Collections.Generic;
+
+ using NUnit.Framework;
+
+ using OrderUsings.Processing;
+
+ public class WhenListsEmpty
+ {
+ [Test]
+ public void ReturnsNull()
+ {
+ var empty = new List>();
+ Assert.IsNull(SpacingChecker.GetNextModification(empty, empty));
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/SpacingChecking/WhenRequiredSpaceNotPresent.cs b/src/OrderUsings.Core.Tests/SpacingChecking/WhenRequiredSpaceNotPresent.cs
new file mode 100644
index 0000000..bc5c434
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/SpacingChecking/WhenRequiredSpaceNotPresent.cs
@@ -0,0 +1,33 @@
+namespace OrderUsings.Tests.SpacingChecking
+{
+ using NUnit.Framework;
+
+ using OrderUsings.Processing;
+
+ public class WhenRequiredSpaceNotPresent
+ {
+ private static readonly UsingDirective D1 = new UsingDirective { Namespace = "System" };
+ private static readonly UsingDirective D2 = new UsingDirective { Namespace = "Moq" };
+ private static readonly UsingDirective D3 = new UsingDirective { Namespace = "MyProject" };
+
+ [Test]
+ public void ReportsPositionWhenNoSpacePresent()
+ {
+ var required = new[] { new[] { D1, D2 }, new[] { D3 } };
+ var current = new[] { new[] { D2, D1, D3 } };
+ SpaceChange result = SpacingChecker.GetNextModification(required, current);
+ Assert.IsTrue(result.ShouldInsert, "ShouldInsert");
+ Assert.AreEqual(2, result.Index);
+ }
+
+ [Test]
+ public void ReportsPositionWhenSpaceTooLate()
+ {
+ var required = new[] { new[] { D1 }, new[] { D3, D2 } };
+ var current = new[] { new[] { D1, D2 }, new[] { D3 } };
+ SpaceChange result = SpacingChecker.GetNextModification(required, current);
+ Assert.IsTrue(result.ShouldInsert, "ShouldInsert");
+ Assert.AreEqual(1, result.Index);
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/SpacingChecking/WhenSpacingCorrect.cs b/src/OrderUsings.Core.Tests/SpacingChecking/WhenSpacingCorrect.cs
new file mode 100644
index 0000000..d4f319f
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/SpacingChecking/WhenSpacingCorrect.cs
@@ -0,0 +1,29 @@
+namespace OrderUsings.Tests.SpacingChecking
+{
+ using NUnit.Framework;
+
+ using OrderUsings.Processing;
+
+ public class WhenSpacingCorrect
+ {
+ private static readonly UsingDirective D1 = new UsingDirective { Namespace = "System" };
+ private static readonly UsingDirective D2 = new UsingDirective { Namespace = "Moq" };
+ private static readonly UsingDirective D3 = new UsingDirective { Namespace = "MyProject" };
+
+ [Test]
+ public void WithTwoGroups()
+ {
+ var required = new[] { new[] { D1, D2 }, new[] { D3 } };
+ var current = new[] { new[] { D1, D2 }, new[] { D3 } };
+ Assert.IsNull(SpacingChecker.GetNextModification(required, current));
+ }
+
+ [Test]
+ public void WithOneGroup()
+ {
+ var required = new[] { new[] { D1, D3, D2 } };
+ var current = new[] { new[] { D1, D2, D3 } };
+ Assert.IsNull(SpacingChecker.GetNextModification(required, current));
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/SpacingChecking/WhenUnwantedSpacePresent.cs b/src/OrderUsings.Core.Tests/SpacingChecking/WhenUnwantedSpacePresent.cs
new file mode 100644
index 0000000..a24e5f6
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/SpacingChecking/WhenUnwantedSpacePresent.cs
@@ -0,0 +1,33 @@
+namespace OrderUsings.Tests.SpacingChecking
+{
+ using NUnit.Framework;
+
+ using OrderUsings.Processing;
+
+ public class WhenUnwantedSpacePresent
+ {
+ private static readonly UsingDirective D1 = new UsingDirective { Namespace = "System" };
+ private static readonly UsingDirective D2 = new UsingDirective { Namespace = "Moq" };
+ private static readonly UsingDirective D3 = new UsingDirective { Namespace = "MyProject" };
+
+ [Test]
+ public void ReportsPositionWhenOnlyOneWasExpected()
+ {
+ var required = new[] { new[] { D1, D2 }, new[] { D3 } };
+ var current = new[] { new[] { D1 }, new[] { D2 }, new[] { D3 } };
+ SpaceChange result = SpacingChecker.GetNextModification(required, current);
+ Assert.IsFalse(result.ShouldInsert, "ShouldInsert");
+ Assert.AreEqual(1, result.Index);
+ }
+
+ [Test]
+ public void ReportsPositionWhenNoSpaceExpected()
+ {
+ var required = new[] { new[] { D1, D3, D2 } };
+ var current = new[] { new[] { D1, D2 }, new[] { D3 } };
+ SpaceChange result = SpacingChecker.GetNextModification(required, current);
+ Assert.IsFalse(result.ShouldInsert, "ShouldInsert");
+ Assert.AreEqual(2, result.Index);
+ }
+ }
+}
diff --git a/src/OrderUsings.Core.Tests/packages.config b/src/OrderUsings.Core.Tests/packages.config
new file mode 100644
index 0000000..ad37a52
--- /dev/null
+++ b/src/OrderUsings.Core.Tests/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/OrderUsings.Core/Configuration/ConfigurationRule.cs b/src/OrderUsings.Core/Configuration/ConfigurationRule.cs
new file mode 100644
index 0000000..3387846
--- /dev/null
+++ b/src/OrderUsings.Core/Configuration/ConfigurationRule.cs
@@ -0,0 +1,66 @@
+namespace OrderUsings.Configuration
+{
+ using System;
+
+ ///
+ /// Represents a single entry from the configuration - either a
+ /// or an entry indicating that a blank line is required.
+ ///
+ public struct ConfigurationRule
+ {
+ private readonly GroupRule _rule;
+
+ ///
+ /// Initializes a representing a .
+ ///
+ /// The rule.
+ private ConfigurationRule(GroupRule rule)
+ {
+ _rule = rule;
+ }
+
+ ///
+ /// Gets a value indicating whether this rule represents a space or a group rule.
+ ///
+ public bool IsSpace
+ {
+ get { return _rule == null; }
+ }
+
+ ///
+ /// Gets this entry's group rule. (Throws if you use this on an entry representing
+ /// a space.)
+ ///
+ public GroupRule Rule
+ {
+ get
+ {
+ if (_rule == null)
+ {
+ throw new InvalidOperationException("Cannot fetch Rule for entry representing space");
+ }
+
+ return _rule;
+ }
+ }
+
+ ///
+ /// Creates a representing a .
+ ///
+ /// The rule.
+ /// The configuration rule.
+ public static ConfigurationRule ForGroupRule(GroupRule rule)
+ {
+ return new ConfigurationRule(rule);
+ }
+
+ ///
+ /// Creates a representing a space between group rules.
+ ///
+ /// The configuration rule.
+ public static ConfigurationRule ForSpace()
+ {
+ return new ConfigurationRule();
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Configuration/ConfigurationSerializer.cs b/src/OrderUsings.Core/Configuration/ConfigurationSerializer.cs
new file mode 100644
index 0000000..00ed5b0
--- /dev/null
+++ b/src/OrderUsings.Core/Configuration/ConfigurationSerializer.cs
@@ -0,0 +1,85 @@
+namespace OrderUsings.Configuration
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using System.Xml.Linq;
+
+ ///
+ /// Loads and saves configuration settings.
+ ///
+ public class ConfigurationSerializer
+ {
+ private const string Ns = "http://schemas.interact-sw.co.uk/OrderUsings/2014";
+ private static readonly XName GroupsName = XName.Get("Groups", Ns);
+ private static readonly XName GroupName = XName.Get("Group", Ns);
+ private static readonly XName SpaceName = XName.Get("Space", Ns);
+
+ ///
+ /// Loads configuration settings from XML.
+ ///
+ /// A stream containing settings in XML.
+ /// A representing the settings
+ /// in the file.
+ public static OrderUsingsConfiguration FromXml(TextReader xmlConfiguration)
+ {
+ var xml = XDocument.Load(xmlConfiguration);
+ var groupsElement = xml.Element(GroupsName);
+ if (groupsElement == null)
+ {
+ throw new ArgumentException("Root element must be Groups");
+ }
+
+ return new OrderUsingsConfiguration
+ {
+ GroupsAndSpaces = xml
+ .Elements(XName.Get("Groups", Ns))
+ .Elements()
+ .Select(RuleFromElement)
+ .ToList()
+ };
+ }
+
+ ///
+ /// Produces a from an XML element, which must be
+ /// either a Group or a Space element.
+ ///
+ /// The XML element describing the rule.
+ /// A .
+ private static ConfigurationRule RuleFromElement(XElement elem)
+ {
+ if (elem.Name == GroupName)
+ {
+ var aliasText = (string) elem.Attribute("Type");
+ var type = MatchType.Import;
+ switch (aliasText)
+ {
+ case "Alias":
+ type = MatchType.Alias;
+ break;
+
+ case "ImportOrAlias":
+ type = MatchType.ImportOrAlias;
+ break;
+ }
+
+ return ConfigurationRule.ForGroupRule(new GroupRule
+ {
+ Priority = ((int?) elem.Attribute("Priority")) ?? 1,
+ NamespacePattern = (string) elem.Attribute("NamespacePattern"),
+ AliasPattern = (string) elem.Attribute("AliasPattern"),
+ Type = type,
+ OrderAliasesBy = ((string) elem.Attribute("AliasOrderKey")) == "Namespace" ?
+ OrderAliasBy.Namespace : OrderAliasBy.Alias
+ });
+ }
+
+ if (elem.Name == SpaceName)
+ {
+ return ConfigurationRule.ForSpace();
+ }
+
+ throw new ArgumentException("Elements in must be either or ");
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Configuration/GroupRule.cs b/src/OrderUsings.Core/Configuration/GroupRule.cs
new file mode 100644
index 0000000..3bcded4
--- /dev/null
+++ b/src/OrderUsings.Core/Configuration/GroupRule.cs
@@ -0,0 +1,52 @@
+namespace OrderUsings.Configuration
+{
+ ///
+ /// Represents a configuration entry describing a group of namespaces.
+ ///
+ public class GroupRule
+ {
+ ///
+ /// Gets or sets a value that determines which group wins when a directive
+ /// matches multiple groups' rules.
+ ///
+ public int Priority { get; set; }
+
+ ///
+ /// Gets or sets the regular expression against which a using directive's
+ /// namespace will be tested, to determine whether it belongs to this group.
+ ///
+ ///
+ /// Only used for rules that match using directives that import namespaces,
+ /// i.e. when is either
+ /// or .
+ ///
+ public string NamespacePattern { get; set; }
+
+ ///
+ /// Gets or sets the pattern against which a using directive's
+ /// alias will be tested (in the case where the directive defines an
+ /// alias) to determine whether it belongs to this group.
+ ///
+ ///
+ /// Use a * for wildcard matching, e.g. System*.
+ ///
+ /// Only used for rules that match using alias directives i.e. when
+ /// is either
+ /// or .
+ ///
+ ///
+ public string AliasPattern { get; set; }
+
+ ///
+ /// Gets or sets a value indicating what types of using directives this
+ /// rule matches.
+ ///
+ public MatchType Type { get; set; }
+
+ ///
+ /// Gets or sets a value indicating how using alias directives should be ordered
+ /// within the group.
+ ///
+ public OrderAliasBy OrderAliasesBy { get; set; }
+ }
+}
diff --git a/src/OrderUsings.Core/Configuration/MatchType.cs b/src/OrderUsings.Core/Configuration/MatchType.cs
new file mode 100644
index 0000000..c7c22c3
--- /dev/null
+++ b/src/OrderUsings.Core/Configuration/MatchType.cs
@@ -0,0 +1,23 @@
+namespace OrderUsings.Configuration
+{
+ ///
+ /// Describes how a matches a using directive.
+ ///
+ public enum MatchType
+ {
+ ///
+ /// Match only directives that import types from a namespace (and not using alias directives).
+ ///
+ Import,
+
+ ///
+ /// Match only using alias directives.
+ ///
+ Alias,
+
+ ///
+ /// Match using directives of either kind.
+ ///
+ ImportOrAlias
+ }
+}
diff --git a/src/OrderUsings.Core/Configuration/OrderAliasBy.cs b/src/OrderUsings.Core/Configuration/OrderAliasBy.cs
new file mode 100644
index 0000000..a22f4c6
--- /dev/null
+++ b/src/OrderUsings.Core/Configuration/OrderAliasBy.cs
@@ -0,0 +1,68 @@
+namespace OrderUsings.Configuration
+{
+ ///
+ /// Determines how using alias directives are ordered within a group.
+ ///
+ ///
+ ///
+ /// If a single group ends up containing multiple using alias directives, we can
+ /// sort them either by the alias itself or the namespace. E.g., if you choose
+ /// , you get:
+ ///
+ ///
+ /// using B = Quux;
+ /// using D = Foo;
+ ///
+ ///
+ /// But with , the same directives would (if they end up matching
+ /// the same group) be sorted so that the namespaces are in order, i.e.:
+ ///
+ ///
+ /// using D = Foo;
+ /// using B = Quux;
+ ///
+ ///
+ /// If a single includes both using alias directives and
+ /// ordinary using directives, these will be intermingled. (You use separate rules
+ /// if you want them separated.) So with you would get this
+ /// sort of thing:
+ ///
+ ///
+ /// using A;
+ /// using A.Something;
+ /// using B = Quux;
+ /// using C.G;
+ /// using D = Foo;
+ /// using Faz;
+ /// using Foz;
+ /// using P;
+ /// using Z;
+ ///
+ ///
+ /// With , the same directives would go in this order:
+ ///
+ ///
+ /// using A;
+ /// using A.Something;
+ /// using C.G;
+ /// using Faz;
+ /// using D = Foo;
+ /// using Foz;
+ /// using P;
+ /// using B = Quux;
+ /// using Z;
+ ///
+ ///
+ public enum OrderAliasBy
+ {
+ ///
+ /// The order is based on the alias defined by the directive.
+ ///
+ Alias,
+
+ ///
+ /// The order is based on the directive's namespace.
+ ///
+ Namespace
+ }
+}
diff --git a/src/OrderUsings.Core/Configuration/OrderUsingsConfiguration.cs b/src/OrderUsings.Core/Configuration/OrderUsingsConfiguration.cs
new file mode 100644
index 0000000..33f85e7
--- /dev/null
+++ b/src/OrderUsings.Core/Configuration/OrderUsingsConfiguration.cs
@@ -0,0 +1,15 @@
+namespace OrderUsings.Configuration
+{
+ using System.Collections.Generic;
+
+ ///
+ /// Describes the required order and spacing for using directives.
+ ///
+ public class OrderUsingsConfiguration
+ {
+ ///
+ /// Gets or sets the grouping and spacing rules.
+ ///
+ public List GroupsAndSpaces { get; set; }
+ }
+}
diff --git a/src/OrderUsings.Core/OrderUsings.Core.csproj b/src/OrderUsings.Core/OrderUsings.Core.csproj
new file mode 100644
index 0000000..9aa0e66
--- /dev/null
+++ b/src/OrderUsings.Core/OrderUsings.Core.csproj
@@ -0,0 +1,67 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {9875DA0A-DB09-47B2-80B5-80B08E430CEF}
+ Library
+ Properties
+ OrderUsings
+ OrderUsings.Core
+ v4.0
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/OrderUsings.Core/Processing/ImportInspector.cs b/src/OrderUsings.Core/Processing/ImportInspector.cs
new file mode 100644
index 0000000..202e05e
--- /dev/null
+++ b/src/OrderUsings.Core/Processing/ImportInspector.cs
@@ -0,0 +1,136 @@
+namespace OrderUsings.Processing
+{
+ using System.Collections.Generic;
+ using System.Linq;
+
+ using OrderUsings.Configuration;
+
+ ///
+ /// Contains logic for import inspection that is common to various processing stages.
+ ///
+ public static class ImportInspector
+ {
+ ///
+ /// Takes a configuration and a description of a using directive list, and
+ /// produces two things: a list containing just the directives (with the
+ /// items representing blank lines removed), and a description of the
+ /// correct order and grouping for these items for the given configuration.
+ ///
+ /// The configuration that will determine the
+ /// correct order and grouping.
+ /// A list of using directives and the blank lines
+ /// interspersed therein. (To simplify processing, this may be null to
+ /// represent the absence of any using directives.)
+ /// The 'flattened' list (just the using directives,
+ /// with any blank lines stripped out) will be written to this argument,
+ /// unless items is null, in which case this will be set to null.
+ /// The correct ordering and spacing
+ /// for the using directives (as determined by the configuration) will be
+ /// written to this argument (unless items is null, in which case
+ /// this will be set to null).
+ ///
+ /// The correct order and spacing is represented as a list of lists. Each
+ /// nested list represents a group of usings, where each group should be
+ /// separated by a blank line.
+ ///
+ public static void FlattenImportsAndDetermineOrderAndSpacing(
+ OrderUsingsConfiguration configuration,
+ List items,
+ out List imports,
+ out List> requiredOrderByGroups)
+ {
+ imports = null;
+ requiredOrderByGroups = null;
+ if (items != null)
+ {
+ imports = items
+ .Where(i => !i.IsBlankLine)
+ .Select(i => i.Directive)
+ .ToList();
+
+ requiredOrderByGroups =
+ OrderAndSpacingGenerator.DetermineOrderAndSpacing(imports, configuration);
+ }
+ }
+
+ ///
+ /// Determines which using directive should be moved where to bring the directive one
+ /// step closer to the correct order. This method should be called repeatedly (applying
+ /// each update that it generates between each call) until it returns null to indicate
+ /// that the order is correct.
+ ///
+ /// The correct order (e.g., as determined by
+ /// a call to ).
+ /// The using directives in their current order.
+ /// Null if the directives are already in the correct order. Otherwise,
+ /// a describing which item to move where.
+ ///
+ /// This method will only ever indicate that directives should be moved backwards.
+ /// This means that it will not necessarily produce the optimal sequence of changes.
+ /// (E.g., given a target order of 1,2,3,4,5 and a current order of of 5,1,2,3,4,
+ /// you would end up calling this method 4 times even though an optimal re-ordering
+ /// that allowed items to move forwards would need only one move). It does keep
+ /// things simple, though.
+ ///
+ public static Relocation GetNextUsingToMove(
+ List> requiredOrderByGroups,
+ List imports)
+ {
+ var requiredOrder =
+ from itemGroup in requiredOrderByGroups
+ from item in itemGroup
+ select item;
+ return OrderChecker.GetNextUsingToMove(requiredOrder, imports);
+ }
+
+ ///
+ /// Given a set of using directives which are already in the correct order, determines
+ /// where to remove or add a blank line to bring them one step closer to the correct
+ /// spacing. This method should be called repeatedly (applying each update that it
+ /// generates between each call) until it returns null to indicate that the order
+ /// is correct.
+ ///
+ /// The correct order and spacing (e.g., as
+ /// determined by a call to ).
+ /// The directives as they are currently ordered and spaced.
+ /// Null if the directives are already in the correct order. Otherwise,
+ /// a describing where to add or remove a blank line.
+ public static SpaceChange GetNextSpacingModification(
+ List> requiredOrderByGroups,
+ List items)
+ {
+ SpaceChange nextChange = null;
+ if (requiredOrderByGroups != null)
+ {
+ var importsByGroup = new List>();
+ foreach (UsingDirectiveOrSpace item in items)
+ {
+ if (importsByGroup.Count == 0)
+ {
+ importsByGroup.Add(new List());
+ }
+
+ List currentGroup = importsByGroup[importsByGroup.Count - 1];
+
+ if (item.IsBlankLine)
+ {
+ if (currentGroup.Count > 0)
+ {
+ importsByGroup.Add(new List());
+ }
+ }
+ else
+ {
+ currentGroup.Add(item.Directive);
+ }
+ }
+
+ nextChange = SpacingChecker.GetNextModification(
+ requiredOrderByGroups,
+ importsByGroup);
+ }
+
+ return nextChange;
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Processing/OrderAndSpacingGenerator.cs b/src/OrderUsings.Core/Processing/OrderAndSpacingGenerator.cs
new file mode 100644
index 0000000..c032e44
--- /dev/null
+++ b/src/OrderUsings.Core/Processing/OrderAndSpacingGenerator.cs
@@ -0,0 +1,139 @@
+namespace OrderUsings.Processing
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+
+ using OrderUsings.Configuration;
+
+ ///
+ /// Generates the correct order and spacing for a set of using directives
+ /// given the rules in a particular configuration.
+ ///
+ public static class OrderAndSpacingGenerator
+ {
+ private static readonly UsingComparer CompareByAlias = new UsingComparer(OrderAliasBy.Alias);
+ private static readonly UsingComparer CompareByNamespace = new UsingComparer(OrderAliasBy.Namespace);
+
+ ///
+ /// Calculates how using directives should be ordered and, where appropriate,
+ /// split into groups separated by blank lines.
+ ///
+ /// The directives for which to determine the order
+ /// and spacing.
+ /// The configuration settings describing the
+ /// required ordering and spacing.
+ /// A list of lists. This will be empty if the input list was empty.
+ /// Otherwise there will be at least one list; if the rules require that any
+ /// of the directives be separated by spaces this will be indicated by
+ /// returning multiple lists - a blank line should appear between each of
+ /// the lists. Within each of the nested lists returned, the directives are
+ /// in the order they should appear.
+ public static List> DetermineOrderAndSpacing(
+ IEnumerable directives, OrderUsingsConfiguration configuration)
+ {
+ if (directives == null)
+ {
+ throw new ArgumentNullException("directives");
+ }
+
+ Dictionary groupNamespaceMatchers = configuration.GroupsAndSpaces
+ .Where(gs => !gs.IsSpace)
+ .ToDictionary(
+ gs => gs.Rule,
+ gs => new Regex(gs.Rule.NamespacePattern.Replace(".", "\\.").Replace("*", ".*")));
+
+ ILookup directivesByGroup = directives.ToLookup(
+ d => groupNamespaceMatchers
+ .Where(e => e.Value.IsMatch(d.Namespace))
+ .OrderBy(e => e.Key.Priority)
+ .First().Key);
+
+ List currentItemSet = null;
+ var results = new List>();
+ GroupRule lastRule = null;
+ foreach (ConfigurationRule ruleEntry in configuration.GroupsAndSpaces)
+ {
+ if (ruleEntry.IsSpace)
+ {
+ MakeGroupFromCurrentItemsIfAny(ref currentItemSet, lastRule, results);
+ }
+ else
+ {
+ UsingComparer comparer = ruleEntry.Rule.OrderAliasesBy == OrderAliasBy.Alias ?
+ CompareByAlias : CompareByNamespace;
+ if (currentItemSet == null)
+ {
+ currentItemSet = new List();
+ }
+
+ currentItemSet.AddRange(directivesByGroup[ruleEntry.Rule].OrderBy(d => d, comparer));
+ lastRule = ruleEntry.Rule;
+ }
+ }
+
+ MakeGroupFromCurrentItemsIfAny(ref currentItemSet, lastRule, results);
+
+ return results;
+ }
+
+ private static void MakeGroupFromCurrentItemsIfAny(
+ ref List currentItemSet, GroupRule lastRule, List> results)
+ {
+ if (currentItemSet != null && currentItemSet.Count > 0 && lastRule != null)
+ {
+ results.Add(currentItemSet);
+ currentItemSet = null;
+ }
+ }
+
+ ///
+ /// Determines the order in which directives should appear within a group.
+ ///
+ private class UsingComparer : IComparer
+ {
+ private readonly OrderAliasBy _orderType;
+
+ public UsingComparer(OrderAliasBy orderType)
+ {
+ _orderType = orderType;
+ }
+
+ public int Compare(UsingDirective x, UsingDirective y)
+ {
+ string left, right;
+ if (_orderType == OrderAliasBy.Alias)
+ {
+ left = x.Alias ?? x.Namespace;
+ right = y.Alias ?? y.Namespace;
+ }
+ else
+ {
+ left = x.Namespace;
+ right = y.Namespace;
+ }
+
+ int result = string.Compare(left, right, StringComparison.Ordinal);
+ if (result == 0)
+ {
+ // In general a match means that one is an alias, and the other is an import, e.g.
+ // using System;
+ // using System = Foo.Bar;
+ //
+ // or if we're sorting by Namespace,
+ // using System;
+ // using Bar = System;
+ //
+ // In either case, we want the one that's not an alias to go first.
+ if (!ReferenceEquals(x.Alias, y.Alias))
+ {
+ result = x.Alias == null ? -1 : 1;
+ }
+ }
+
+ return result;
+ }
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Processing/OrderChecker.cs b/src/OrderUsings.Core/Processing/OrderChecker.cs
new file mode 100644
index 0000000..82c1115
--- /dev/null
+++ b/src/OrderUsings.Core/Processing/OrderChecker.cs
@@ -0,0 +1,59 @@
+namespace OrderUsings.Processing
+{
+ using System;
+ using System.Collections.Generic;
+
+ ///
+ /// Checks that the order of using directives matches the required order.
+ ///
+ public static class OrderChecker
+ {
+ ///
+ /// Compares the order of using directives in two lists. Returns null if they are
+ /// the same, and otherwise returns a description of the first element to move
+ /// to bring the current order closer into line with the required order.
+ ///
+ /// The order in which the directives should appear.
+ /// The order in which the directives currently appear.
+ /// Null if the orders match. Otherwise, a describing
+ /// the first element to move to bring the order closer to the required one.
+ ///
+ /// Code that simply needs to know whether the order is correct (e.g., when we want to
+ /// highlight bad ordering) will just use this to get a yes/no answer. Code that wants
+ /// to fix the order will call this repeatedly to generate a sequence of moves.
+ ///
+ public static Relocation GetNextUsingToMove(
+ IEnumerable requiredOrder, IEnumerable currentOrder)
+ {
+ int expectedIndex = 0;
+ using (var reqIt = requiredOrder.GetEnumerator())
+ using (var currentIt = currentOrder.GetEnumerator())
+ {
+ while (reqIt.MoveNext() && currentIt.MoveNext())
+ {
+ UsingDirective expected = reqIt.Current;
+ if (!ReferenceEquals(expected, currentIt.Current))
+ {
+ int currentIndex = expectedIndex;
+ while (currentIt.MoveNext())
+ {
+ currentIndex += 1;
+ if (ReferenceEquals(expected, currentIt.Current))
+ {
+ return new Relocation(currentIndex, expectedIndex);
+ }
+ }
+
+ throw new ArgumentException(
+ "Lists should contain same items, but currentOrder was missing " + expected,
+ "currentOrder");
+ }
+
+ expectedIndex += 1;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Processing/Relocation.cs b/src/OrderUsings.Core/Processing/Relocation.cs
new file mode 100644
index 0000000..cead01f
--- /dev/null
+++ b/src/OrderUsings.Core/Processing/Relocation.cs
@@ -0,0 +1,39 @@
+namespace OrderUsings.Processing
+{
+ using System;
+
+ ///
+ /// Describes how a using directive should be repositioned to bring a list
+ /// of usings one step closer to the configured order.
+ ///
+ public class Relocation
+ {
+ ///
+ /// Initializes a .
+ ///
+ /// The index of the item to be moved.
+ /// The index to which to move the item. Must be lower than from.
+ public Relocation(int from, int to)
+ {
+ if (from <= to)
+ {
+ throw new ArgumentException(
+ "Items must move towards front of list, so to must be lower than from", "to");
+ }
+
+ From = from;
+ To = to;
+ }
+
+ ///
+ /// Gets the index of the item to move. This will always be higher than .
+ ///
+ public int From { get; private set; }
+
+ ///
+ /// Gets the index to which the item should be moved. This will always be lower than
+ /// .
+ ///
+ public int To { get; private set; }
+ }
+}
diff --git a/src/OrderUsings.Core/Processing/SpaceChange.cs b/src/OrderUsings.Core/Processing/SpaceChange.cs
new file mode 100644
index 0000000..b2e8147
--- /dev/null
+++ b/src/OrderUsings.Core/Processing/SpaceChange.cs
@@ -0,0 +1,42 @@
+namespace OrderUsings.Processing
+{
+ ///
+ /// Describes how to adjust the spacing in a list of using directives to bring them
+ /// one step closer to the configured spacing.
+ ///
+ public class SpaceChange
+ {
+ ///
+ /// Gets a value indicating whether the list should be changed by adding or
+ /// removing a space.
+ ///
+ public bool ShouldInsert { get; private set; }
+
+ ///
+ /// Gets the index at which to add or remove a space.
+ ///
+ public int Index { get; private set; }
+
+ ///
+ /// Creates a indicating that space should be inserted at
+ /// a particular index.
+ ///
+ /// The index at which space should be inserted.
+ /// A .
+ public static SpaceChange Insert(int index)
+ {
+ return new SpaceChange { ShouldInsert = true, Index = index };
+ }
+
+ ///
+ /// Creates a indicating that space should be removed at
+ /// a particular index.
+ ///
+ /// The index at which space should be removed.
+ /// A .
+ public static SpaceChange Remove(int index)
+ {
+ return new SpaceChange { ShouldInsert = false, Index = index };
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Processing/SpacingChecker.cs b/src/OrderUsings.Core/Processing/SpacingChecker.cs
new file mode 100644
index 0000000..79bf14b
--- /dev/null
+++ b/src/OrderUsings.Core/Processing/SpacingChecker.cs
@@ -0,0 +1,52 @@
+namespace OrderUsings.Processing
+{
+ using System.Collections.Generic;
+ using System.Linq;
+
+ ///
+ /// Checks that using directives have the required spacing.
+ ///
+ public static class SpacingChecker
+ {
+ ///
+ /// Compares the spacing (as represented by grouping) of using directives in
+ /// two lists. Returns null if they are the same, and otherwise returns a
+ /// description of the first line at which to either remove or insert space
+ /// to bring the list closer into line with the required spacing. (This presumes
+ /// that the order is already correct.)
+ ///
+ /// The required order and spacing, where
+ /// spacing is denoted by grouping.
+ /// The current order and spacing.
+ /// Null if the spacing matches. Otherwise, a
+ /// describing where to add or remove a blank line.
+ public static SpaceChange GetNextModification(
+ IEnumerable> requiredGroups,
+ IEnumerable> currentGroups)
+ {
+ var requiredByGroup = requiredGroups
+ .SelectMany((items, groupIndex) => items.Select(item => groupIndex));
+ var currentByGroup = currentGroups
+ .SelectMany((items, groupIndex) => items.Select(item => groupIndex));
+ var itemsByGroup = requiredByGroup.Zip(currentByGroup, (required, actual) => new { required, actual });
+
+ int index = 0;
+ foreach (var groupIndices in itemsByGroup)
+ {
+ if (groupIndices.actual < groupIndices.required)
+ {
+ return SpaceChange.Insert(index);
+ }
+
+ if (groupIndices.actual > groupIndices.required)
+ {
+ return SpaceChange.Remove(index);
+ }
+
+ index += 1;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Processing/UsingDirective.cs b/src/OrderUsings.Core/Processing/UsingDirective.cs
new file mode 100644
index 0000000..3abc603
--- /dev/null
+++ b/src/OrderUsings.Core/Processing/UsingDirective.cs
@@ -0,0 +1,32 @@
+namespace OrderUsings.Processing
+{
+ ///
+ /// A non-technology-specific representation of a using directive.
+ ///
+ ///
+ /// We use this so that the core logic doesn't need to depend on any particular
+ /// framework. (This decouples us from any single version of ReSharper, or even
+ /// ReSharper at all, since I'd like to offer a StyleCop plug-in at some point.)
+ ///
+ public class UsingDirective
+ {
+ ///
+ /// Gets or sets the alias name created by this declaration, or null
+ /// if this is an import.
+ ///
+ public string Alias { get; set; }
+
+ ///
+ /// Gets or sets either the namespace that this declaration imports, or
+ /// (if is non-null) the type or namespace for which
+ /// this defines an alias.
+ ///
+ public string Namespace { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return Alias == null ? Namespace : Alias + " = " + Namespace;
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Processing/UsingDirectiveOrSpace.cs b/src/OrderUsings.Core/Processing/UsingDirectiveOrSpace.cs
new file mode 100644
index 0000000..2d1ec4a
--- /dev/null
+++ b/src/OrderUsings.Core/Processing/UsingDirectiveOrSpace.cs
@@ -0,0 +1,49 @@
+namespace OrderUsings.Processing
+{
+ using System;
+
+ ///
+ /// Represents an entry in a list of using directives - either a directive or
+ /// a blank line.
+ ///
+ public struct UsingDirectiveOrSpace
+ {
+ private readonly UsingDirective _directive;
+
+ ///
+ /// Initialises a representing
+ /// a using directive. (Use the no-arguments constructor to represent
+ /// a blank line.)
+ ///
+ /// The directive.
+ public UsingDirectiveOrSpace(UsingDirective directive)
+ {
+ _directive = directive;
+ }
+
+ ///
+ /// Gets a value indicating whether this entry represents a blank line.
+ ///
+ public bool IsBlankLine
+ {
+ get { return _directive == null; }
+ }
+
+ ///
+ /// Gets the directive represented by this entry. (Throws if this entry
+ /// represents a blank line.)
+ ///
+ public UsingDirective Directive
+ {
+ get
+ {
+ if (_directive == null)
+ {
+ throw new InvalidOperationException("Cannot use Directive property on a blank line");
+ }
+
+ return _directive;
+ }
+ }
+ }
+}
diff --git a/src/OrderUsings.Core/Properties/AssemblyInfo.cs b/src/OrderUsings.Core/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a0d7cca
--- /dev/null
+++ b/src/OrderUsings.Core/Properties/AssemblyInfo.cs
@@ -0,0 +1,17 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("OrderUsings.Core")]
+[assembly: AssemblyDescription("Non-environment-specific logic for Rules-based ordering for C# using directives")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Ian Griffiths")]
+[assembly: AssemblyProduct("Order Usings")]
+[assembly: AssemblyCopyright("Copyright © Ian Griffiths, 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+[assembly: Guid("db0f7251-87d1-4d3f-a5b0-36da4a771c9d")]
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/src/OrderUsings.sln b/src/OrderUsings.sln
new file mode 100644
index 0000000..76ce04c
--- /dev/null
+++ b/src/OrderUsings.sln
@@ -0,0 +1,58 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.30110.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReSharper810", "ReSharper810\ReSharper810.csproj", "{D1291D67-AD57-4982-827B-0BEDD4B1C140}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resharper810.Tests", "Resharper810.Tests\Resharper810.Tests.csproj", "{372D6B66-6022-4656-AB50-4CBCADD09150}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1E2CF5ED-1599-4D40-A4BD-8F773A6D90DE}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data", "data", "{6D1823DC-EDB3-4423-88BF-0AB678000E68}"
+ ProjectSection(SolutionItems) = preProject
+ test\data\highlighting-order-01.cs = test\data\highlighting-order-01.cs
+ test\data\highlighting-order-01.cs.gold = test\data\highlighting-order-01.cs.gold
+ test\data\highlighting-spacing-01.cs = test\data\highlighting-spacing-01.cs
+ test\data\highlighting-spacing-01.cs.gold = test\data\highlighting-spacing-01.cs.gold
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderUsings.Core", "OrderUsings.Core\OrderUsings.Core.csproj", "{9875DA0A-DB09-47B2-80B5-80B08E430CEF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderUsings.Core.Tests", "OrderUsings.Core.Tests\OrderUsings.Core.Tests.csproj", "{638CD37F-59CB-42AE-873D-5E09E9C32EAA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{00A0C928-006D-41E5-AF17-F018C79A9430}"
+ ProjectSection(SolutionItems) = preProject
+ .nuget\packages.config = .nuget\packages.config
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D1291D67-AD57-4982-827B-0BEDD4B1C140}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D1291D67-AD57-4982-827B-0BEDD4B1C140}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D1291D67-AD57-4982-827B-0BEDD4B1C140}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D1291D67-AD57-4982-827B-0BEDD4B1C140}.Release|Any CPU.Build.0 = Release|Any CPU
+ {372D6B66-6022-4656-AB50-4CBCADD09150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {372D6B66-6022-4656-AB50-4CBCADD09150}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {372D6B66-6022-4656-AB50-4CBCADD09150}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {372D6B66-6022-4656-AB50-4CBCADD09150}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9875DA0A-DB09-47B2-80B5-80B08E430CEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9875DA0A-DB09-47B2-80B5-80B08E430CEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9875DA0A-DB09-47B2-80B5-80B08E430CEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9875DA0A-DB09-47B2-80B5-80B08E430CEF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {638CD37F-59CB-42AE-873D-5E09E9C32EAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {638CD37F-59CB-42AE-873D-5E09E9C32EAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {638CD37F-59CB-42AE-873D-5E09E9C32EAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {638CD37F-59CB-42AE-873D-5E09E9C32EAA}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {6D1823DC-EDB3-4423-88BF-0AB678000E68} = {1E2CF5ED-1599-4D40-A4BD-8F773A6D90DE}
+ EndGlobalSection
+EndGlobal
diff --git a/src/OrderUsings.sln.DotSettings b/src/OrderUsings.sln.DotSettings
new file mode 100644
index 0000000..6c1b61b
--- /dev/null
+++ b/src/OrderUsings.sln.DotSettings
@@ -0,0 +1,22 @@
+
+ True
+ True
+ True
+ True
+ <?xml version="1.0" encoding="utf-8" ?>
+<Groups
+ xmlns="http://schemas.interact-sw.co.uk/OrderUsings/2014"
+ >
+ <Group Priority="1" NamespacePattern="System*" />
+ <Group Priority="1" NamespacePattern="Microsoft*" />
+ <Space />
+ <Group Priority="1" NamespacePattern="NUnit*" />
+ <Space />
+ <Group Priority="1" NamespacePattern="JetBrains*" />
+ <Space />
+ <Group Priority="9999" NamespacePattern="*" />
+ <Space />
+ <Group Priority="9999" NamespacePattern="*" AliasPattern="*" Type="Alias" />
+ <Space />
+ <Group Priority="1" NamespacePattern="OrderUsings*" />
+</Groups>
\ No newline at end of file
diff --git a/src/ReSharper810/CodeModel/ImportReader.cs b/src/ReSharper810/CodeModel/ImportReader.cs
new file mode 100644
index 0000000..8e6ec0e
--- /dev/null
+++ b/src/ReSharper810/CodeModel/ImportReader.cs
@@ -0,0 +1,61 @@
+namespace OrderUsings.ReSharper.CodeModel
+{
+ using System.Collections.Generic;
+
+ using JetBrains.ReSharper.Psi.CSharp.Parsing;
+ using JetBrains.ReSharper.Psi.CSharp.Tree;
+
+ using OrderUsings.Processing;
+
+ ///
+ /// Converts from ReSharper's representation of an import list to our internal representation.
+ ///
+ internal static class ImportReader
+ {
+ ///
+ /// Returns a list of using directives and spacing from an element that can contain
+ /// a directive list (i.e., a file, or a namespace block). Returns null if the element
+ /// has no using directives.
+ ///
+ /// The file or namespace block.
+ /// Null if no using directives were present; a
+ /// list otherwise.
+ internal static List ReadImports(ICSharpTypeAndNamespaceHolderDeclaration holder)
+ {
+ List items = null;
+ foreach (IUsingDirective item in holder.Imports)
+ {
+ if (items == null)
+ {
+ items = new List();
+ }
+
+ var alias = item as IUsingAliasDirective;
+ items.Add(new UsingDirectiveOrSpace(new UsingDirective
+ {
+ Namespace = alias == null ? item.ImportedSymbolName.QualifiedName : alias.Alias.Name,
+ Alias = alias == null ? null : alias.AliasName
+ }));
+
+ var syb = item.NextSibling;
+ bool first = true;
+ for (; syb != null && !(syb is IUsingDirective); syb = syb.NextSibling)
+ {
+ if (syb.NodeType == CSharpTokenType.NEW_LINE)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ items.Add(new UsingDirectiveOrSpace());
+ }
+ }
+ }
+ }
+
+ return items;
+ }
+ }
+}
diff --git a/src/ReSharper810/Highlightings/BaseHighlighting.cs b/src/ReSharper810/Highlightings/BaseHighlighting.cs
new file mode 100644
index 0000000..c8fd2e2
--- /dev/null
+++ b/src/ReSharper810/Highlightings/BaseHighlighting.cs
@@ -0,0 +1,76 @@
+namespace OrderUsings.ReSharper.Highlightings
+{
+ using JetBrains.ReSharper.Daemon;
+ using JetBrains.ReSharper.Psi.CSharp.Tree;
+
+ using OrderUsings.Configuration;
+
+ ///
+ /// Common functionality for any highlighting for a using directive list.
+ ///
+ public abstract class BaseHighlighting : IHighlighting
+ {
+ private readonly ICSharpTypeAndNamespaceHolderDeclaration _typeAndNamespaceHolder;
+ private readonly OrderUsingsConfiguration _config;
+
+ ///
+ /// Initializes a .
+ ///
+ /// The file or namespace block that contains
+ /// the import list being highlighted.
+ /// The configuration that was active when we determined that
+ /// the import list does not match the requirements.
+ internal BaseHighlighting(
+ ICSharpTypeAndNamespaceHolderDeclaration typeAndNamespaceHolder, OrderUsingsConfiguration config)
+ {
+ _config = config;
+ _typeAndNamespaceHolder = typeAndNamespaceHolder;
+ }
+
+ ///
+ /// Gets file or namespace block that contains the import list being highlighted.
+ ///
+ public ICSharpTypeAndNamespaceHolderDeclaration TypeAndNamespaceHolder
+ {
+ get { return _typeAndNamespaceHolder; }
+ }
+
+ ///
+ public string ToolTip
+ {
+ get { return ToolTipText; }
+ }
+
+ ///
+ public string ErrorStripeToolTip
+ {
+ get { return ToolTipText; }
+ }
+
+ ///
+ public int NavigationOffsetPatch
+ {
+ get { return 0; }
+ }
+
+ ///
+ /// Gets configuration that was active when we determined that the import list
+ /// does not match the requirements.
+ ///
+ internal OrderUsingsConfiguration Config
+ {
+ get { return _config; }
+ }
+
+ ///
+ /// Gets the text used as the main tooltip and the error stripe tooltip.
+ ///
+ protected abstract string ToolTipText { get; }
+
+ ///
+ public bool IsValid()
+ {
+ return true;
+ }
+ }
+}
diff --git a/src/ReSharper810/Highlightings/UsingOrderHighlighting.cs b/src/ReSharper810/Highlightings/UsingOrderHighlighting.cs
new file mode 100644
index 0000000..ed0a04d
--- /dev/null
+++ b/src/ReSharper810/Highlightings/UsingOrderHighlighting.cs
@@ -0,0 +1,34 @@
+namespace OrderUsings.ReSharper.Highlightings
+{
+ using JetBrains.ReSharper.Daemon;
+ using JetBrains.ReSharper.Psi.CSharp.Tree;
+
+ using OrderUsings.Configuration;
+
+ ///
+ /// A ReSharper highlighting indicating that a set of using directives don't meet the
+ /// configured ordering requirements.
+ ///
+ [StaticSeverityHighlighting(Severity.WARNING, "Using Directive Order & Spacing", Title = "Using directive order")]
+ public class UsingOrderHighlighting : BaseHighlighting
+ {
+ ///
+ /// Initializes a .
+ ///
+ /// The file or namespace block that contains
+ /// the import list being highlighted.
+ /// The configuration that was active when we determined that
+ /// the import list does not match the requirements.
+ internal UsingOrderHighlighting(
+ ICSharpTypeAndNamespaceHolderDeclaration typeAndNamespaceHolder, OrderUsingsConfiguration config)
+ : base(typeAndNamespaceHolder, config)
+ {
+ }
+
+ ///
+ protected override string ToolTipText
+ {
+ get { return "Using directives do not match configured order"; }
+ }
+ }
+}
diff --git a/src/ReSharper810/Highlightings/UsingSpacingHighlighting.cs b/src/ReSharper810/Highlightings/UsingSpacingHighlighting.cs
new file mode 100644
index 0000000..36eccba
--- /dev/null
+++ b/src/ReSharper810/Highlightings/UsingSpacingHighlighting.cs
@@ -0,0 +1,36 @@
+namespace OrderUsings.ReSharper.Highlightings
+{
+ using JetBrains.ReSharper.Daemon;
+ using JetBrains.ReSharper.Psi.CSharp.Tree;
+
+ using OrderUsings.Configuration;
+
+ ///
+ /// A ReSharper highlighting indicating that a set of using statements don't meet the
+ /// configured spacing requirements.
+ ///
+ [StaticSeverityHighlighting(ViolationSeverity, "Using Directive Order & Spacing", Title = "Using directive spacing")]
+ public class UsingSpacingHighlighting : BaseHighlighting
+ {
+ private const Severity ViolationSeverity = Severity.WARNING;
+
+ ///
+ /// Initializes a .
+ ///
+ /// The file or namespace block that contains
+ /// the import list being highlighted.
+ /// The configuration that was active when we determined that
+ /// the import list does not match the requirements.
+ internal UsingSpacingHighlighting(
+ ICSharpTypeAndNamespaceHolderDeclaration typeAndNamespaceHolder, OrderUsingsConfiguration config)
+ : base(typeAndNamespaceHolder, config)
+ {
+ }
+
+ ///
+ protected override string ToolTipText
+ {
+ get { return "Using directives do not match configured spacing"; }
+ }
+ }
+}
diff --git a/src/ReSharper810/Inspection/OrderUsingsDaemonStage.cs b/src/ReSharper810/Inspection/OrderUsingsDaemonStage.cs
new file mode 100644
index 0000000..b558031
--- /dev/null
+++ b/src/ReSharper810/Inspection/OrderUsingsDaemonStage.cs
@@ -0,0 +1,82 @@
+namespace OrderUsings.ReSharper.Inspection
+{
+ using System;
+ using System.IO;
+
+ using JetBrains.Application.Progress;
+ using JetBrains.Application.Settings;
+ using JetBrains.ReSharper.Daemon;
+ using JetBrains.ReSharper.Daemon.CSharp.Stages;
+ using JetBrains.ReSharper.Psi.CSharp.Tree;
+
+ using OrderUsings.Configuration;
+ using OrderUsings.ReSharper.Settings;
+
+ ///
+ /// ReSharper entry point, enabling us to inspect source files in the background
+ /// as they are opened, and add highlights.
+ ///
+ [DaemonStage]
+ public class OrderUsingsDaemonStage : CSharpDaemonStageBase
+ {
+ ///
+ /// Invoked by ReSharper each time it wants us to perform some background processing
+ /// of a file.
+ ///
+ /// Provides information about and services relating to the
+ /// work we are being asked to do.
+ /// Settings information.
+ /// The kind of processing we're being asked to do.
+ /// The file to be processed.
+ /// A process object representing the work, or null if no work will be done.
+ protected override IDaemonStageProcess CreateProcess(
+ IDaemonProcess process,
+ IContextBoundSettingsStore settings,
+ DaemonProcessKind processKind,
+ ICSharpFile file)
+ {
+ if (process == null)
+ {
+ throw new ArgumentNullException("process");
+ }
+
+ // StyleCop's daemon stage looks for a processKind of DaemonProcessKind.OTHER
+ // and does nothing (returns null) if it sees it. This turns out to prevent
+ // highlights from showing up when you ask ReSharper to inspect code issues
+ // across the whole solution. I'm not sure why StyleCop deliberately opts out
+ // of it. Perhaps something goes horribly wrong, but I've not seen any sign
+ // of that yet, and we really do want solution-wide inspection to work.
+
+ try
+ {
+ // I guess the base class checks that this is actually a C# file?
+ if (!IsSupported(process.SourceFile))
+ {
+ return null;
+ }
+
+ // StyleCop checks to see if there are already any errors in the file, and if
+ // there are, it decides to do nothing.
+ // TODO: Do we need to do that?
+
+ // TODO: We should probably check for exemptions, e.g. generated source files.
+ }
+ catch (ProcessCancelledException)
+ {
+ return null;
+ }
+
+ // TODO: should we get an injected ISettingsOptimization?
+ var orderUsingSettings =
+ settings.GetKey(SettingsOptimization.DoMeSlowly);
+
+ OrderUsingsConfiguration config = null;
+ if (!string.IsNullOrWhiteSpace(orderUsingSettings.OrderSpecificationXml))
+ {
+ config = ConfigurationSerializer.FromXml(new StringReader(orderUsingSettings.OrderSpecificationXml));
+ }
+
+ return new OrderUsingsDaemonStageProcess(process, file, config);
+ }
+ }
+}
diff --git a/src/ReSharper810/Inspection/OrderUsingsDaemonStageProcess.cs b/src/ReSharper810/Inspection/OrderUsingsDaemonStageProcess.cs
new file mode 100644
index 0000000..a29533f
--- /dev/null
+++ b/src/ReSharper810/Inspection/OrderUsingsDaemonStageProcess.cs
@@ -0,0 +1,150 @@
+namespace OrderUsings.ReSharper.Inspection
+{
+ using System;
+ using System.Collections.Generic;
+
+ using JetBrains.ReSharper.Daemon;
+ using JetBrains.ReSharper.Psi.CSharp.Tree;
+ using JetBrains.ReSharper.Psi.Tree;
+
+ using OrderUsings.Configuration;
+ using OrderUsings.Processing;
+ using OrderUsings.ReSharper.CodeModel;
+ using OrderUsings.ReSharper.Highlightings;
+
+ ///
+ /// Represents the processing for a particular file in our daemon stage.
+ ///
+ internal class OrderUsingsDaemonStageProcess : IDaemonStageProcess
+ {
+ private readonly ICSharpFile _file;
+ private readonly OrderUsingsConfiguration _config;
+
+ ///
+ /// Initializes a .
+ ///
+ /// The process object supplied by R# for this work.
+ /// The file to process.
+ /// The order and spacing configuration to use.
+ public OrderUsingsDaemonStageProcess(IDaemonProcess process, ICSharpFile file, OrderUsingsConfiguration config)
+ {
+ _file = file;
+ _config = config;
+ DaemonProcess = process;
+ }
+
+ ///
+ /// Gets the process object associated with this work.
+ ///
+ ///
+ /// The interface requires this. Quite why R# doesn't
+ /// already know the association is beyond me.
+ ///
+ public IDaemonProcess DaemonProcess { get; private set; }
+
+ ///
+ /// Called by ReSharper when it wants us to execute our work.
+ ///
+ /// A call-back through which we supply the results
+ /// of our processing.
+ public void Execute(Action committer)
+ {
+ DaemonStageResult result = null;
+ if (_config != null)
+ {
+ List highlights = null;
+
+ // Top-level imports (outside of any namespace blocks) are a singular special
+ // case; the other place that imports can be found is in namespace blocks, and
+ // since those can be nested, we have to walk them recursively.
+ CheckImports(_file, ref highlights);
+ WalkNamespaceDeclarations(_file.NamespaceDeclarations, ref highlights);
+
+ if (highlights != null)
+ {
+ result = new DaemonStageResult(highlights);
+ }
+ }
+
+ committer(result);
+ }
+
+ ///
+ /// Recursively walk namespace declaration blocks, checking any import lists they
+ /// contain.
+ ///
+ /// The namespace declaration blocks
+ /// to check.
+ /// If any import lists are found that do not meet the
+ /// configured order and spacing requirements, they will be returned via this
+ /// argument. (To avoid unnecessary allocations in the happy path, we don't allocate
+ /// the list of highlights unless we need to generate at least one highlight,
+ /// which is why this is a ref parameter - it is initially null,
+ /// but gets allocated on demand if needed.)
+ private void WalkNamespaceDeclarations(
+ TreeNodeCollection namespaceDeclarationNodes,
+ ref List highlights)
+ {
+ foreach (var ns in namespaceDeclarationNodes)
+ {
+ CheckImports(ns, ref highlights);
+ WalkNamespaceDeclarations(ns.NamespaceDeclarations, ref highlights);
+ }
+ }
+
+ ///
+ /// Checks an import list against the configured ordering and spacing.
+ ///
+ /// The import list container - either a file, or a namespace
+ /// declaration block.
+ /// If the import does not meet the configured requirements,
+ /// we allocate a list containing a highlight describing the problem and return
+ /// it via this argument. (We allocate the list on demand to avoid allocations
+ /// in the happy path in which all the import lists are correctly ordered and
+ /// spaced.)
+ private void CheckImports(
+ ICSharpTypeAndNamespaceHolderDeclaration holder, ref List highlights)
+ {
+ List items = ImportReader.ReadImports(holder);
+ List imports;
+ List> requiredOrderByGroups;
+ ImportInspector.FlattenImportsAndDetermineOrderAndSpacing(
+ _config, items, out imports, out requiredOrderByGroups);
+
+ bool orderIsCorrect = true;
+ if (requiredOrderByGroups != null)
+ {
+ Relocation nextChange = ImportInspector.GetNextUsingToMove(requiredOrderByGroups, imports);
+ if (nextChange != null)
+ {
+ orderIsCorrect = false;
+ AddHighlight(holder, ref highlights, new UsingOrderHighlighting(holder, _config));
+ }
+ }
+
+ // If (and only if) the order is correct, we go on to check the spacing.
+ if (orderIsCorrect)
+ {
+ SpaceChange nextChange = ImportInspector.GetNextSpacingModification(requiredOrderByGroups, items);
+ if (nextChange != null)
+ {
+ AddHighlight(holder, ref highlights, new UsingSpacingHighlighting(holder, _config));
+ }
+ }
+ }
+
+ private void AddHighlight(
+ ICSharpTypeAndNamespaceHolderDeclaration holder,
+ ref List highlights,
+ IHighlighting highlight)
+ {
+ if (highlights == null)
+ {
+ highlights = new List();
+ }
+
+ highlights.Add(new HighlightingInfo(
+ holder.ImportsList.GetHighlightingRange(), highlight));
+ }
+ }
+}
diff --git a/src/ReSharper810/Properties/AssemblyInfo.cs b/src/ReSharper810/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..631e047
--- /dev/null
+++ b/src/ReSharper810/Properties/AssemblyInfo.cs
@@ -0,0 +1,23 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+using JetBrains.Application.PluginSupport;
+
+[assembly: AssemblyTitle("OrderUsings.ReSharper810")]
+[assembly: AssemblyDescription("Rules-based ordering for C# using directives")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Ian Griffiths")]
+[assembly: AssemblyProduct("Order Usings")]
+[assembly: AssemblyCopyright("Copyright © Ian Griffiths, 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+
+// The following information is displayed by ReSharper in the Plugins dialog
+[assembly: PluginTitle("Order Usings")]
+[assembly: PluginDescription("Rules-based ordering for C# using directives")]
+[assembly: PluginVendor("Ian Griffiths")]
+
+[assembly: InternalsVisibleTo("ReSharper810.Tests")]
\ No newline at end of file
diff --git a/src/ReSharper810/QuickFixes/UsingOrderAndSpacingQuickFix.cs b/src/ReSharper810/QuickFixes/UsingOrderAndSpacingQuickFix.cs
new file mode 100644
index 0000000..96df298
--- /dev/null
+++ b/src/ReSharper810/QuickFixes/UsingOrderAndSpacingQuickFix.cs
@@ -0,0 +1,189 @@
+namespace OrderUsings.ReSharper.QuickFixes
+{
+ using System;
+ using System.Collections.Generic;
+
+ using JetBrains.Application;
+ using JetBrains.Application.Progress;
+ using JetBrains.DocumentModel.Transactions;
+ using JetBrains.ProjectModel;
+ using JetBrains.ReSharper.Feature.Services.Bulbs;
+ using JetBrains.ReSharper.Intentions.Extensibility;
+ using JetBrains.ReSharper.Psi;
+ using JetBrains.ReSharper.Psi.CSharp.Parsing;
+ using JetBrains.ReSharper.Psi.CSharp.Tree;
+ using JetBrains.ReSharper.Psi.ExtensionsAPI;
+ using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
+ using JetBrains.Text;
+ using JetBrains.TextControl;
+ using JetBrains.Util;
+
+ using OrderUsings.Processing;
+ using OrderUsings.ReSharper.CodeModel;
+ using OrderUsings.ReSharper.Highlightings;
+ using OrderUsings.ReSharper.Inspection;
+
+ ///
+ /// ReSharper quick fix that fixes the ordering and spacing issues detected during inspection by
+ /// .
+ ///
+ ///
+ /// ReSharper constructs this automatically - when it wants to know if any quick fixes are
+ /// available for a highlighting, it goes looking for classes annotated with
+ /// that implement (directly or, as in
+ /// this case, indirectly), and which have a constructor accepting the relevant highlighting type.
+ ///
+ [QuickFix]
+ public class UsingOrderAndSpacingQuickFix : QuickFixBase
+ {
+ private readonly BaseHighlighting _highlighting;
+
+ ///
+ /// Initialises a for an order
+ /// mismatch highlighting.
+ ///
+ /// The highlighting for which this will be
+ /// a quick fix.
+ public UsingOrderAndSpacingQuickFix(UsingOrderHighlighting highlighting)
+ {
+ _highlighting = highlighting;
+ }
+
+ ///
+ /// Initialises a for a spacing
+ /// mismatch highlighting.
+ ///
+ /// The highlighting for which this will be
+ /// a quick fix.
+ public UsingOrderAndSpacingQuickFix(UsingSpacingHighlighting highlighting)
+ {
+ _highlighting = highlighting;
+ }
+
+ ///
+ public override string Text
+ {
+ get { return "Fix ordering and spacing"; }
+ }
+
+ ///
+ public override bool IsAvailable(IUserDataHolder cache)
+ {
+ return true;
+ }
+
+ ///
+ protected override Action ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
+ {
+ return textControl =>
+ {
+ using (solution.GetComponent()
+ .CreateTransactionCookie(DefaultAction.Commit, "action name"))
+ {
+ var services = solution.GetPsiServices();
+ services.Transactions.Execute(
+ "Code cleanup",
+ () => services.Locks.ExecuteWithWriteLock(() =>
+ {
+ ICSharpTypeAndNamespaceHolderDeclaration holder = _highlighting.TypeAndNamespaceHolder;
+
+ FixOrder(holder);
+ FixSpacing(holder);
+ }));
+ }
+ };
+ }
+
+ private void FixOrder(ICSharpTypeAndNamespaceHolderDeclaration holder)
+ {
+ // The reordering proceeds one item at a time, so we just keep reapplying it
+ // until there's nothing left to do.
+ // To avoid hanging VS in the event that an error in the logic causes the
+ // sequence of modifications not to terminate, we ensure we don't try to
+ // apply more changes than there are using directives.
+ int tries = 0;
+ int itemCount = 0;
+ while (tries == 0 || tries <= itemCount)
+ {
+ List items = ImportReader.ReadImports(holder);
+ List imports;
+ List> requiredOrderByGroups;
+ ImportInspector.FlattenImportsAndDetermineOrderAndSpacing(
+ _highlighting.Config, items, out imports, out requiredOrderByGroups);
+
+ if (requiredOrderByGroups != null)
+ {
+ itemCount = imports.Count;
+ Relocation nextChange = ImportInspector.GetNextUsingToMove(requiredOrderByGroups, imports);
+ if (nextChange != null)
+ {
+ IUsingDirective toMove = holder.Imports[nextChange.From];
+ IUsingDirective before = holder.Imports[nextChange.To];
+ holder.RemoveImport(toMove);
+ holder.AddImportBefore(toMove, before);
+ tries += 1;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ private void FixSpacing(ICSharpTypeAndNamespaceHolderDeclaration holder)
+ {
+ // The reordering proceeds one item at a time, so we just keep reapplying it
+ // until there's nothing left to do.
+ // To avoid hanging VS in the event that an error in the logic causes the
+ // sequence of modifications not to terminate, we ensure we don't try to
+ // apply more changes than there are either using directives or blank
+ // lines in the usings list.
+ int tries = 0;
+ int itemCount = 0;
+ while (tries == 0 || tries <= itemCount)
+ {
+ List items = ImportReader.ReadImports(holder);
+ itemCount = items.Count;
+ List imports;
+ List> requiredOrderByGroups;
+ ImportInspector.FlattenImportsAndDetermineOrderAndSpacing(
+ _highlighting.Config, items, out imports, out requiredOrderByGroups);
+
+ SpaceChange nextChange = ImportInspector.GetNextSpacingModification(requiredOrderByGroups, items);
+ if (nextChange != null)
+ {
+ IUsingDirective usingBeforeSpace = holder.Imports[nextChange.Index - 1];
+ if (nextChange.ShouldInsert)
+ {
+ using (WriteLockCookie.Create())
+ {
+ var newLineText = new StringBuffer("\r\n");
+
+ LeafElementBase newLine = TreeElementFactory.CreateLeafElement(
+ CSharpTokenType.NEW_LINE, newLineText, 0, newLineText.Length);
+ LowLevelModificationUtil.AddChildAfter(usingBeforeSpace, newLine);
+ }
+ }
+ else
+ {
+ var syb = usingBeforeSpace.NextSibling;
+ for (; syb != null && !(syb is IUsingDirective); syb = syb.NextSibling)
+ {
+ if (syb.NodeType == CSharpTokenType.NEW_LINE)
+ {
+ LowLevelModificationUtil.DeleteChild(syb);
+ }
+ }
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ tries += 1;
+ }
+ }
+ }
+}
diff --git a/src/ReSharper810/ReSharper810.csproj b/src/ReSharper810/ReSharper810.csproj
new file mode 100644
index 0000000..9912181
--- /dev/null
+++ b/src/ReSharper810/ReSharper810.csproj
@@ -0,0 +1,83 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {D1291D67-AD57-4982-827B-0BEDD4B1C140}
+ Library
+ Properties
+ OrderUsings.ReSharper
+ OrderUsings.ReSharper810
+ v4.0
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ JET_MODE_ASSERT;DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ Program
+ $(VsInstallDir)devenv.exe
+ /ReSharper.Plugin $(AssemblyName).dll /ReSharper.Internal
+ $(MSBuildProjectDirectory)\$(OutputPath)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+ {9875da0a-db09-47b2-80b5-80b08e430cef}
+ OrderUsings.Core
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ReSharper810/Settings/DefaultConfiguration.xml b/src/ReSharper810/Settings/DefaultConfiguration.xml
new file mode 100644
index 0000000..4b5c23b
--- /dev/null
+++ b/src/ReSharper810/Settings/DefaultConfiguration.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ReSharper810/Settings/OrderUsingsOptionsPage.cs b/src/ReSharper810/Settings/OrderUsingsOptionsPage.cs
new file mode 100644
index 0000000..04f85ce
--- /dev/null
+++ b/src/ReSharper810/Settings/OrderUsingsOptionsPage.cs
@@ -0,0 +1,114 @@
+namespace OrderUsings.ReSharper.Settings
+{
+ using System;
+ using System.IO;
+ using System.Windows;
+ using System.Windows.Controls;
+
+ using JetBrains.Annotations;
+ using JetBrains.Application.Settings;
+ using JetBrains.DataFlow;
+ using JetBrains.ReSharper.Features.Common.Options;
+ using JetBrains.UI.CrossFramework;
+ using JetBrains.UI.Options;
+
+ ///
+ /// UI for configuring the order and spacing of usings within ReSharper's
+ /// settings dialog.
+ ///
+ [OptionsPage(PageId, "Order Usings", null, ParentId = ToolsPage.PID)]
+ public class OrderUsingsOptionsPage : IOptionsPage
+ {
+ private const string PageId = "OrderUsingsOptionsId";
+
+ ///
+ /// Initializes a .
+ ///
+ /// Passed by ReSharper. Purpose unclear to me.
+ /// Passed by ReSharper, enabling us to get the
+ /// current configuration, and bind controls to the configuration system.
+ public OrderUsingsOptionsPage([NotNull] Lifetime lifetime, OptionsSettingsSmartContext settings)
+ {
+ if (lifetime == null) throw new ArgumentNullException("lifetime");
+
+ Control = InitView(lifetime, settings);
+ }
+
+ ///
+ public EitherControl Control { get; private set; }
+
+ ///
+ public string Id { get { return PageId; } }
+
+ ///
+ public bool OnOk()
+ {
+ return true;
+ }
+
+ ///
+ public bool ValidatePage()
+ {
+ return true;
+ }
+
+ private EitherControl InitView(Lifetime lifetime, OptionsSettingsSmartContext settings)
+ {
+ var grid = new Grid { Background = SystemColors.ControlBrush };
+
+ grid.ColumnDefinitions.Add(new ColumnDefinition());
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
+ grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
+ grid.RowDefinitions.Add(new RowDefinition());
+
+ var margin = new Thickness(3);
+ var label = new Label { Content = "Configuration:", Margin = margin };
+ var text = new TextBox { AcceptsReturn = true, Margin = margin };
+ settings.SetBinding(
+ lifetime,
+ x => x.OrderSpecificationXml,
+ text,
+ TextBox.TextProperty);
+
+ Grid.SetRow(text, 1);
+ Grid.SetColumnSpan(text, 3);
+
+ var buttonPadding = new Thickness(3);
+ var resetButton = new Button
+ {
+ Content = "Reset",
+ Margin = margin,
+ Padding = buttonPadding
+ };
+ resetButton.Click += (s, e) => text.SetCurrentValue(TextBox.TextProperty, string.Empty);
+ Grid.SetColumn(resetButton, 1);
+
+ var addBasicButton = new Button
+ {
+ Content = "Create basic configuration",
+ Margin = margin,
+ Padding = buttonPadding
+ };
+ addBasicButton.Click += (s, e) =>
+ {
+ var asm = typeof(OrderUsingsOptionsPage).Assembly;
+ Stream stream = asm.GetManifestResourceStream("OrderUsings.ReSharper.Settings.DefaultConfiguration.xml");
+ if (stream != null)
+ {
+ using (var reader = new StreamReader(stream))
+ {
+ text.SetCurrentValue(TextBox.TextProperty, reader.ReadToEnd());
+ }
+ }
+ };
+ Grid.SetColumn(addBasicButton, 2);
+
+ grid.Children.Add(label);
+ grid.Children.Add(text);
+ grid.Children.Add(resetButton);
+ grid.Children.Add(addBasicButton);
+ return grid;
+ }
+ }
+}
diff --git a/src/ReSharper810/Settings/OrderUsingsSettings.cs b/src/ReSharper810/Settings/OrderUsingsSettings.cs
new file mode 100644
index 0000000..fad011f
--- /dev/null
+++ b/src/ReSharper810/Settings/OrderUsingsSettings.cs
@@ -0,0 +1,27 @@
+namespace OrderUsings.ReSharper.Settings
+{
+ using System.Reflection;
+
+ using JetBrains.Application.Settings;
+
+ ///
+ /// Plug-in settings persisted for us by ReSharper.
+ ///
+ ///
+ /// TODO: what are we supposed to use as the 'parent' in SettingsKey?
+ /// The docs basically give you no help at all here - there doesn't seem to be a
+ /// list of suitable types. The example uses InternetSettings with no indication as
+ /// to why you might choose that. I'm going with Missing for now, because some
+ /// examples already out there do that too, but it just means you end up as an
+ /// uncategorised top-level setting, which is not ideal.
+ ///
+ [SettingsKey(typeof(Missing), "GitHub settings")]
+ public class OrderUsingsSettings
+ {
+ ///
+ /// Gets or sets the XML content specifying the required order.
+ ///
+ [SettingsEntry("", "XML file specifying the required order for using directives")]
+ public string OrderSpecificationXml { get; set; }
+ }
+}
diff --git a/src/ReSharper810/packages.config b/src/ReSharper810/packages.config
new file mode 100644
index 0000000..d631330
--- /dev/null
+++ b/src/ReSharper810/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Resharper810.Tests/Highlighting/WhenOrderIsWrong.cs b/src/Resharper810.Tests/Highlighting/WhenOrderIsWrong.cs
new file mode 100644
index 0000000..4c40cc8
--- /dev/null
+++ b/src/Resharper810.Tests/Highlighting/WhenOrderIsWrong.cs
@@ -0,0 +1,64 @@
+namespace Resharper810.Tests.Highlighting
+{
+ using System;
+
+ using NUnit.Framework;
+
+ using JetBrains.Application.Settings;
+ using JetBrains.ProjectModel;
+ using JetBrains.ReSharper.Daemon;
+ using JetBrains.ReSharper.Daemon.CSharp;
+ using JetBrains.ReSharper.TestFramework;
+
+ using OrderUsings.ReSharper;
+ using OrderUsings.ReSharper.Highlightings;
+ using OrderUsings.ReSharper.Settings;
+
+ [TestFixture]
+ [TestSettingsKey(typeof(OrderUsingsSettings))]
+ public class WhenOrderIsWrong : CSharpHighlightingTestBase
+ {
+ protected override bool HighlightingPredicate(IHighlighting highlighting, IContextBoundSettingsStore settingsstore)
+ {
+ return highlighting is UsingOrderHighlighting;
+ }
+
+ // This seems to be the earliest place from which we can get a settings store. We use
+ // this to push in settings without having to use a real settings file. (It looks like
+ // you can actually provide a test-local settings file, but for now, just providing it
+ // programmatically is easiest.)
+ protected override void WithProject(IProject project, ISettingsStore settingsStore, Action action)
+ {
+ // The docs all say to use plain BindToContext, but that has been marked as [Obsolete].
+ // This appears to be what that obsolete method actually does. (And teh DataContexts.Empty
+ // just copies what the test code uses when it creates a bound settings store to pass to the
+ // code under test.)
+ IContextBoundSettingsStore boundStore = settingsStore.BindToContextTransient(
+ ContextRange.ManuallyRestrictWritesToOneContext(
+ (lifetime, contexts) => settingsStore.DataContexts.Empty));
+ boundStore.SetValue(
+ settings => settings.OrderSpecificationXml,
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "");
+ base.WithProject(project, settingsStore, action);
+ }
+
+ protected override string RelativeTestDataPath
+ {
+ get { return @""; }
+ }
+
+ [Test]
+ [TestCase("highlighting-order-01.cs")]
+ public void Test(string testName)
+ {
+ DoTestFiles(testName);
+ }
+ }
+}
diff --git a/src/Resharper810.Tests/Highlighting/WhenSpacingIsWrong.cs b/src/Resharper810.Tests/Highlighting/WhenSpacingIsWrong.cs
new file mode 100644
index 0000000..b9e6204
--- /dev/null
+++ b/src/Resharper810.Tests/Highlighting/WhenSpacingIsWrong.cs
@@ -0,0 +1,64 @@
+namespace Resharper810.Tests.Highlighting
+{
+ using System;
+
+ using NUnit.Framework;
+
+ using JetBrains.Application.Settings;
+ using JetBrains.ProjectModel;
+ using JetBrains.ReSharper.Daemon;
+ using JetBrains.ReSharper.Daemon.CSharp;
+ using JetBrains.ReSharper.TestFramework;
+
+ using OrderUsings.ReSharper;
+ using OrderUsings.ReSharper.Highlightings;
+ using OrderUsings.ReSharper.Settings;
+
+ [TestFixture]
+ [TestSettingsKey(typeof(OrderUsingsSettings))]
+ public class WhenSpacingIsWrong : CSharpHighlightingTestBase
+ {
+ protected override bool HighlightingPredicate(IHighlighting highlighting, IContextBoundSettingsStore settingsstore)
+ {
+ return highlighting is UsingSpacingHighlighting;
+ }
+
+ // This seems to be the earliest place from which we can get a settings store. We use
+ // this to push in settings without having to use a real settings file. (It looks like
+ // you can actually provide a test-local settings file, but for now, just providing it
+ // programmatically is easiest.)
+ protected override void WithProject(IProject project, ISettingsStore settingsStore, Action action)
+ {
+ // The docs all say to use plain BindToContext, but that has been marked as [Obsolete].
+ // This appears to be what that obsolete method actually does. (And teh DataContexts.Empty
+ // just copies what the test code uses when it creates a bound settings store to pass to the
+ // code under test.)
+ IContextBoundSettingsStore boundStore = settingsStore.BindToContextTransient(
+ ContextRange.ManuallyRestrictWritesToOneContext(
+ (lifetime, contexts) => settingsStore.DataContexts.Empty));
+ boundStore.SetValue(
+ settings => settings.OrderSpecificationXml,
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "");
+ base.WithProject(project, settingsStore, action);
+ }
+
+ protected override string RelativeTestDataPath
+ {
+ get { return @""; }
+ }
+
+ [Test]
+ [TestCase("highlighting-spacing-01.cs")]
+ public void Test(string testName)
+ {
+ DoTestFiles(testName);
+ }
+ }
+}
diff --git a/src/Resharper810.Tests/Properties/AssemblyInfo.cs b/src/Resharper810.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..e5a7d93
--- /dev/null
+++ b/src/Resharper810.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using System.Reflection;
+
+using NUnit.Framework;
+
+using JetBrains.Application;
+using JetBrains.Threading;
+
+using OrderUsings.Configuration;
+using OrderUsings.ReSharper;
+using OrderUsings.ReSharper.Settings;
+
+///
+/// Test environment. Must be in the global namespace.
+///
+[SetUpFixture]
+// ReSharper disable once CheckNamespace
+public class TestEnvironmentAssembly : ReSharperTestEnvironmentAssembly
+{
+ ///
+ /// Gets the assemblies to load into test environment.
+ /// Should include all assemblies which contain components.
+ ///
+ ///
+ /// The assemblies to load.
+ ///
+ private static IEnumerable GetAssembliesToLoad()
+ {
+ // Test assembly
+ yield return Assembly.GetExecutingAssembly();
+
+ yield return typeof(GroupRule).Assembly;
+ yield return typeof(OrderUsingsSettings).Assembly;
+ }
+
+ public override void SetUp()
+ {
+ base.SetUp();
+ ReentrancyGuard.Current.Execute(
+ "LoadAssemblies",
+ () => Shell.Instance.GetComponent().LoadAssemblies(
+ GetType().Name, GetAssembliesToLoad()));
+ }
+
+ public override void TearDown()
+ {
+ ReentrancyGuard.Current.Execute(
+ "UnloadAssemblies",
+ () => Shell.Instance.GetComponent().UnloadAssemblies(
+ GetType().Name, GetAssembliesToLoad()));
+ base.TearDown();
+ }
+}
diff --git a/src/Resharper810.Tests/Resharper810.Tests.csproj b/src/Resharper810.Tests/Resharper810.Tests.csproj
new file mode 100644
index 0000000..5b6a537
--- /dev/null
+++ b/src/Resharper810.Tests/Resharper810.Tests.csproj
@@ -0,0 +1,75 @@
+
+
+
+
+
+ Tests
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {372D6B66-6022-4656-AB50-4CBCADD09150}
+ Library
+ Properties
+ Resharper810.Tests
+ Resharper810.Tests
+ v4.0
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ JET_MODE_ASSERT;DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {9875da0a-db09-47b2-80b5-80b08e430cef}
+ OrderUsings.Core
+
+
+ {d1291d67-ad57-4982-827b-0bedd4b1c140}
+ ReSharper810
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Resharper810.Tests/Settings.StyleCop b/src/Resharper810.Tests/Settings.StyleCop
new file mode 100644
index 0000000..ca702f3
--- /dev/null
+++ b/src/Resharper810.Tests/Settings.StyleCop
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Resharper810.Tests/packages.config b/src/Resharper810.Tests/packages.config
new file mode 100644
index 0000000..4017bc3
--- /dev/null
+++ b/src/Resharper810.Tests/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Settings.StyleCop b/src/Settings.StyleCop
new file mode 100644
index 0000000..c2235fd
--- /dev/null
+++ b/src/Settings.StyleCop
@@ -0,0 +1,169 @@
+
+
+ en-GB
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ True
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/data/highlighting-order-01.cs b/src/test/data/highlighting-order-01.cs
new file mode 100644
index 0000000..a9cc675
--- /dev/null
+++ b/src/test/data/highlighting-order-01.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using System;
+
+namespace Test
+{
+ public class Foo
+ {
+ public virtual bool Bar(List data)
+ {
+ return data.Count > 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/data/highlighting-order-01.cs.gold b/src/test/data/highlighting-order-01.cs.gold
new file mode 100644
index 0000000..faf9890
--- /dev/null
+++ b/src/test/data/highlighting-order-01.cs.gold
@@ -0,0 +1,15 @@
+|using System.Collections.Generic;
+using System;|(0)
+
+namespace Test
+{
+ public class Foo
+ {
+ public virtual bool Bar(List data)
+ {
+ return data.Count > 0;
+ }
+ }
+}
+---------------------------------------------------------
+(0): ReSharper Warning: Using directives do not match configured order
diff --git a/src/test/data/highlighting-spacing-01.cs b/src/test/data/highlighting-spacing-01.cs
new file mode 100644
index 0000000..ee02455
--- /dev/null
+++ b/src/test/data/highlighting-spacing-01.cs
@@ -0,0 +1,14 @@
+using System;
+
+using System.Collections.Generic;
+
+namespace Test
+{
+ public class Foo
+ {
+ public virtual bool Bar(List data)
+ {
+ return data.Count > 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/data/highlighting-spacing-01.cs.gold b/src/test/data/highlighting-spacing-01.cs.gold
new file mode 100644
index 0000000..b2e7902
--- /dev/null
+++ b/src/test/data/highlighting-spacing-01.cs.gold
@@ -0,0 +1,16 @@
+|using System;
+
+using System.Collections.Generic;|(0)
+
+namespace Test
+{
+ public class Foo
+ {
+ public virtual bool Bar(List data)
+ {
+ return data.Count > 0;
+ }
+ }
+}
+---------------------------------------------------------
+(0): ReSharper Warning: Using directives do not match configured spacing