├── README.md ├── CssSorter ├── key.snk ├── ValueList.cs ├── CssItemCollector.cs ├── Properties │ └── AssemblyInfo.cs ├── CssSorter.csproj ├── DeclarationComparer.cs ├── Sorter.cs └── PropertyList.cs ├── CssSorter.Test ├── LESS │ ├── NestingTest.cs │ ├── VariablesTest.cs │ └── MixinTest.cs ├── SCSS │ ├── NestingTest.cs │ ├── VariablesTest.cs │ └── MixinTest.cs ├── CSS │ ├── FilterTest.cs │ ├── WhitespaceTest.cs │ ├── RuleTest.cs │ ├── SorterTest.cs │ ├── HackTest.cs │ ├── VendorTest.cs │ └── CommentTest.cs ├── Properties │ └── AssemblyInfo.cs ├── Shared │ └── VsHost.cs └── CssSorter.Test.csproj ├── CssSorter.sln └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | # CSS Sorting for Web Essentials -------------------------------------------------------------------------------- /CssSorter/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/CssSorter/master/CssSorter/key.snk -------------------------------------------------------------------------------- /CssSorter/ValueList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CssSorter 4 | { 5 | public class ValueList 6 | { 7 | public static List Values { get; set; } 8 | 9 | static ValueList() 10 | { 11 | FillList(); 12 | } 13 | 14 | private static void FillList() 15 | { 16 | Values = new List(new string[] { 17 | "#", 18 | "url", 19 | "-moz-", 20 | "-webkit-", 21 | "-webkit-", 22 | "-o-", 23 | "-ms-", 24 | "-" 25 | }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CssSorter.Test/LESS/NestingTest.cs: -------------------------------------------------------------------------------- 1 | using CssSorter.Test.Shared; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace CssSorter.Test.Less 5 | { 6 | [TestClass] 7 | public class NestingTest 8 | { 9 | private Sorter _sorter = new Sorter(); 10 | 11 | [HostType("VS IDE")] 12 | [TestMethod, TestCategory("Nesting")] 13 | public void Nesting1() 14 | { 15 | VSHost.ReadyingSolution(); 16 | 17 | string input = "div.item { top: 10px; position: relative; div{color:red; top: 5px;}}"; 18 | string expected = "div.item {position: relative;top: 10px; div{top: 5px;color:red;}}"; 19 | 20 | string result = _sorter.SortLess(input); 21 | 22 | Assert.AreEqual(expected, result); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CssSorter.Test/SCSS/NestingTest.cs: -------------------------------------------------------------------------------- 1 | using CssSorter.Test.Shared; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace CssSorter.Test.Scss 5 | { 6 | [TestClass] 7 | public class NestingTest 8 | { 9 | private Sorter _sorter = new Sorter(); 10 | 11 | [HostType("VS IDE")] 12 | [TestMethod, TestCategory("Nesting")] 13 | public void Nesting1() 14 | { 15 | VSHost.ReadyingSolution(); 16 | 17 | string input = "div.item { top: 10px; position: relative; div{color:red; top: 5px;}}"; 18 | string expected = "div.item {position: relative;top: 10px; div{top: 5px;color:red;}}"; 19 | 20 | string result = _sorter.SortScss(input); 21 | 22 | Assert.AreEqual(expected, result); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CssSorter.Test/CSS/FilterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace CssSorter.Test 4 | { 5 | [TestClass] 6 | public class FilterTest 7 | { 8 | private Sorter _sorter = new Sorter(); 9 | 10 | [TestMethod, TestCategory("Filter")] 11 | public void Filter1() 12 | { 13 | string[] input = new[] { 14 | "*background: blue;", 15 | "background: blue;", 16 | "filter: 10px;", 17 | }; 18 | 19 | string[] expected = new[]{ 20 | "background: blue;", 21 | "*background: blue;", 22 | "filter: 10px;", 23 | }; 24 | 25 | string[] result = _sorter.SortDeclarations(input); 26 | 27 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CssSorter/CssItemCollector.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CSS.Core; 3 | 4 | namespace CssSorter 5 | { 6 | /// 7 | /// Creates a list of CSS ParseItems of a certain type 8 | /// (when passed in as the visitor to any item's Accept() function) 9 | /// 10 | internal class CssItemCollector : ICssSimpleTreeVisitor where T : ParseItem 11 | { 12 | public IList Items { get; private set; } 13 | private bool _includeChildren; 14 | 15 | public CssItemCollector() : this(false) { } 16 | 17 | public CssItemCollector(bool includeChildren) 18 | { 19 | _includeChildren = includeChildren; 20 | Items = new List(); 21 | } 22 | 23 | public VisitItemResult Visit(ParseItem parseItem) 24 | { 25 | var item = parseItem as T; 26 | 27 | if (item != null) 28 | { 29 | Items.Add(item); 30 | return (_includeChildren) ? VisitItemResult.Continue : VisitItemResult.SkipChildren; 31 | } 32 | 33 | return VisitItemResult.Continue; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CssSorter.Test/LESS/VariablesTest.cs: -------------------------------------------------------------------------------- 1 | using CssSorter.Test.Shared; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace CssSorter.Test.Less 5 | { 6 | [TestClass] 7 | public class VariablesTest 8 | { 9 | private Sorter _sorter = new Sorter(); 10 | 11 | [HostType("VS IDE")] 12 | [TestMethod, TestCategory("Variables")] 13 | public void Variables1() 14 | { 15 | VSHost.ReadyingSolution(); 16 | 17 | string input = "div.item { @bg: blue; color: @bg}"; 18 | string expected = "div.item {@bg: blue;color: @bg;}"; 19 | 20 | string result = _sorter.SortLess(input); 21 | 22 | Assert.AreEqual(expected, result); 23 | } 24 | 25 | [HostType("VS IDE")] 26 | [TestMethod, TestCategory("Variables")] 27 | public void Variables2() 28 | { 29 | VSHost.ReadyingSolution(); 30 | 31 | string input = "div.item { color: @bg; @bg: blue; }"; 32 | string expected = "div.item {@bg: blue;color: @bg;}"; 33 | 34 | string result = _sorter.SortLess(input); 35 | 36 | Assert.AreEqual(expected, result); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CssSorter.Test/SCSS/VariablesTest.cs: -------------------------------------------------------------------------------- 1 | using CssSorter.Test.Shared; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace CssSorter.Test.Scss 5 | { 6 | [TestClass] 7 | public class VariablesTest 8 | { 9 | private Sorter _sorter = new Sorter(); 10 | 11 | [HostType("VS IDE")] 12 | [TestMethod, TestCategory("Variables")] 13 | public void Variables1() 14 | { 15 | VSHost.ReadyingSolution(); 16 | 17 | string input = "div.item { $bg: blue; color: $bg}"; 18 | string expected = "div.item {$bg: blue;color: $bg;}"; 19 | 20 | string result = _sorter.SortScss(input); 21 | 22 | Assert.AreEqual(expected, result); 23 | } 24 | 25 | [HostType("VS IDE")] 26 | [TestMethod, TestCategory("Variables")] 27 | public void Variables2() 28 | { 29 | VSHost.ReadyingSolution(); 30 | 31 | string input = "div.item { color: $bg; $bg: blue; }"; 32 | string expected = "div.item {$bg: blue;color: $bg;}"; 33 | 34 | string result = _sorter.SortScss(input); 35 | 36 | Assert.AreEqual(expected, result); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CssSorter.Test/LESS/MixinTest.cs: -------------------------------------------------------------------------------- 1 | using CssSorter.Test.Shared; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace CssSorter.Test.Less 5 | { 6 | [TestClass] 7 | public class MixinTest 8 | { 9 | private Sorter _sorter = new Sorter(); 10 | 11 | [HostType("VS IDE")] 12 | [TestMethod, TestCategory("Mixins")] 13 | public void Mixin1() 14 | { 15 | VSHost.ReadyingSolution(); 16 | 17 | string input = "div.item { top: 10px; position: relative; .mixin(); }"; 18 | string expected = "div.item {position: relative;top: 10px;.mixin();}"; 19 | 20 | string result = _sorter.SortLess(input); 21 | 22 | Assert.AreEqual(expected, result); 23 | } 24 | 25 | [HostType("VS IDE")] 26 | [TestMethod, TestCategory("Mixins")] 27 | public void Mixin2() 28 | { 29 | VSHost.ReadyingSolution(); 30 | 31 | string input = "div.item { top: 10px; position: relative; .mixin(@red, 5px); display: block; }"; 32 | string expected = "div.item {position: relative;top: 10px;display: block;.mixin(@red, 5px);}"; 33 | 34 | string result = _sorter.SortLess(input); 35 | 36 | Assert.AreEqual(expected, result); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CssSorter.Test/SCSS/MixinTest.cs: -------------------------------------------------------------------------------- 1 | using CssSorter.Test.Shared; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace CssSorter.Test.Scss 5 | { 6 | [TestClass] 7 | public class MixinTest 8 | { 9 | private Sorter _sorter = new Sorter(); 10 | 11 | [HostType("VS IDE")] 12 | [TestMethod, TestCategory("Mixins")] 13 | public void Mixin1() 14 | { 15 | VSHost.ReadyingSolution(); 16 | 17 | string input = "div.item { top: 10px; position: relative; .mixin(); }"; 18 | string expected = "div.item {position: relative;top: 10px;.mixin();}"; 19 | 20 | string result = _sorter.SortScss(input); 21 | 22 | Assert.AreEqual(expected, result); 23 | } 24 | 25 | [HostType("VS IDE")] 26 | [TestMethod, TestCategory("Mixins")] 27 | public void Mixin2() 28 | { 29 | VSHost.ReadyingSolution(); 30 | 31 | string input = "div.item { top: 10px; position: relative; .mixin(@red, 5px); display: block; }"; 32 | string expected = "div.item {position: relative;top: 10px;display: block;.mixin(@red, 5px);}"; 33 | 34 | string result = _sorter.SortScss(input); 35 | 36 | Assert.AreEqual(expected, result); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CssSorter.Test/CSS/WhitespaceTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace CssSorter.Test 4 | { 5 | [TestClass] 6 | public class WhitespaceTest 7 | { 8 | private Sorter _sorter = new Sorter(); 9 | 10 | [TestMethod, TestCategory("Whitespace")] 11 | public void Whitespace1() 12 | { 13 | string[] input = new[] { 14 | "top: 10px;", 15 | " ", 16 | "position: relative;" 17 | }; 18 | 19 | string[] expected = new[]{ 20 | "position: relative;", 21 | "top: 10px;", 22 | }; 23 | 24 | string[] result = _sorter.SortDeclarations(input); 25 | 26 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 27 | } 28 | 29 | [TestMethod, TestCategory("Whitespace")] 30 | public void Whitespace2() 31 | { 32 | string[] input = new[] { 33 | "top: 10px;", 34 | "\t\t\n\r", 35 | "position: relative;" 36 | }; 37 | 38 | string[] expected = new[]{ 39 | "position: relative;", 40 | "top: 10px;", 41 | }; 42 | 43 | string[] result = _sorter.SortDeclarations(input); 44 | 45 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /CssSorter/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CssSorter")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CssSorter")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("399fe47e-9cef-4792-9392-3c6288b8ed26")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CssSorter.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CssSorter.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CssSorter.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("3d28f4b7-b1e2-44cc-899e-6fe20d12f16f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CssSorter.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CssSorter", "CssSorter\CssSorter.csproj", "{CCDFBB2E-E2A1-4770-8CB2-B7D091F40769}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CssSorter.Test", "CssSorter.Test\CssSorter.Test.csproj", "{96DC6F86-D7CB-4091-A725-2780CFAB0047}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{04711577-16F6-44D4-82E9-F61DD2C1203C}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {CCDFBB2E-E2A1-4770-8CB2-B7D091F40769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {CCDFBB2E-E2A1-4770-8CB2-B7D091F40769}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {CCDFBB2E-E2A1-4770-8CB2-B7D091F40769}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {CCDFBB2E-E2A1-4770-8CB2-B7D091F40769}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {96DC6F86-D7CB-4091-A725-2780CFAB0047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {96DC6F86-D7CB-4091-A725-2780CFAB0047}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {96DC6F86-D7CB-4091-A725-2780CFAB0047}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {96DC6F86-D7CB-4091-A725-2780CFAB0047}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(SolutionProperties) = preSolution 29 | HideSolutionNode = FALSE 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /CssSorter.Test/CSS/RuleTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace CssSorter.Test 5 | { 6 | [TestClass] 7 | public class RuleTest 8 | { 9 | private Sorter _sorter = new Sorter(); 10 | 11 | [TestMethod, TestCategory("Rule")] 12 | public void RuleBasic() 13 | { 14 | string input = "div.item { top: 10px; position: relative; }"; 15 | string expected = "div.item {position: relative;top: 10px;}"; 16 | 17 | string result = _sorter.SortStyleSheet(input); 18 | 19 | Assert.AreEqual(expected, result); 20 | } 21 | 22 | [TestMethod, TestCategory("Rule")] 23 | public void RuleWithInlineComments() 24 | { 25 | string input = "div.item { top: 10px; /* ostehat */ \r\n position: relative; }"; 26 | string expected = "div.item {position: relative;top: 10px; /* ostehat */}"; 27 | 28 | string result = _sorter.SortStyleSheet(input); 29 | 30 | Assert.AreEqual(expected, result); 31 | } 32 | 33 | [TestMethod, TestCategory("Rule")] 34 | public void RuleMultipleRules() 35 | { 36 | string input = "div.item { top: 10px; position: relative; }" + Environment.NewLine + "div.item.last { bottom:10px; }"; 37 | string expected = "div.item {position: relative;top: 10px;}\r\ndiv.item.last { bottom:10px; }"; 38 | 39 | string result = _sorter.SortStyleSheet(input); 40 | 41 | Assert.AreEqual(expected, result); 42 | } 43 | 44 | [TestMethod, TestCategory("Rule")] 45 | public void RuleMultipleRulesWithBlanks() 46 | { 47 | string input = "\t" + Environment.NewLine + " \t " + Environment.NewLine + "div.item { top: 10px; position: relative; }" + Environment.NewLine + "div.item.last { bottom:10px; }"; 48 | string expected = "div.item {position: relative;top: 10px;}\r\ndiv.item.last { bottom:10px; }"; 49 | 50 | string result = _sorter.SortStyleSheet(input); 51 | 52 | Assert.AreEqual(expected, result); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CssSorter.Test/CSS/SorterTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace CssSorter.Test 4 | { 5 | [TestClass] 6 | public class SorterTest 7 | { 8 | private Sorter _sorter = new Sorter(); 9 | 10 | [TestMethod, TestCategory("Sorter")] 11 | public void Sorter1() 12 | { 13 | string[] input = new[] { 14 | "top: 10px;", 15 | "position: relative;" 16 | }; 17 | 18 | string[] expected = new[]{ 19 | "position: relative;", 20 | "top: 10px;", 21 | }; 22 | 23 | string[] result = _sorter.SortDeclarations(input); 24 | 25 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 26 | } 27 | 28 | [TestMethod, TestCategory("Sorter")] 29 | public void Sorter2() 30 | { 31 | string[] input = new[] { 32 | "width: 24px;", 33 | "height: 24px;", 34 | "display: block;", 35 | "float: left;", 36 | "margin-right: 6px;", 37 | "background: url(../images/mini-networks-sprite3.png) 0 0 no-repeat;" 38 | }; 39 | 40 | string[] expected = new[]{ 41 | "display: block;", 42 | "float: left;", 43 | "margin-right: 6px;", 44 | "width: 24px;", 45 | "height: 24px;", 46 | "background: url(../images/mini-networks-sprite3.png) 0 0 no-repeat;" 47 | }; 48 | 49 | string[] result = _sorter.SortDeclarations(input); 50 | 51 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 52 | } 53 | 54 | [TestMethod, TestCategory("Sorter")] 55 | public void SorterMissingSemicolon() 56 | { 57 | string[] input = new[] { 58 | "top: 10px;", 59 | "position: relative" 60 | }; 61 | 62 | string[] expected = new[]{ 63 | "position: relative;", 64 | "top: 10px;", 65 | }; 66 | 67 | string[] result = _sorter.SortDeclarations(input); 68 | 69 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CssSorter.Test/CSS/HackTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace CssSorter.Test 4 | { 5 | [TestClass] 6 | public class HackTest 7 | { 8 | private Sorter _sorter = new Sorter(); 9 | 10 | [TestMethod, TestCategory("Hacks")] 11 | public void Hack1() 12 | { 13 | string[] input = new[] { 14 | "top: 10px;", 15 | "_position: relative;" 16 | }; 17 | 18 | string[] expected = new[]{ 19 | "_position: relative;", 20 | "top: 10px;", 21 | }; 22 | 23 | string[] result = _sorter.SortDeclarations(input); 24 | 25 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 26 | } 27 | 28 | [TestMethod, TestCategory("Hacks")] 29 | public void Hack2() 30 | { 31 | string[] input = new[] { 32 | "width: 24px;", 33 | "*height: 24px;", 34 | "display: block;", 35 | "_float: left;", 36 | "margin-right: 6px;", 37 | "background: url(../images/mini-networks-sprite3.png) 0 0 no-repeat;" 38 | }; 39 | 40 | string[] expected = new[]{ 41 | "display: block;", 42 | "_float: left;", 43 | "margin-right: 6px;", 44 | "width: 24px;", 45 | "*height: 24px;", 46 | "background: url(../images/mini-networks-sprite3.png) 0 0 no-repeat;" 47 | }; 48 | 49 | string[] result = _sorter.SortDeclarations(input); 50 | 51 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 52 | } 53 | 54 | [TestMethod, TestCategory("Hacks")] 55 | public void Hack3() 56 | { 57 | string[] input = new[] { 58 | "_position: relative;", 59 | "position: aboslute;", 60 | }; 61 | 62 | string[] expected = new[]{ 63 | "position: aboslute;", 64 | "_position: relative;", 65 | }; 66 | 67 | string[] result = _sorter.SortDeclarations(input); 68 | 69 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CssSorter.Test/Shared/VsHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows.Threading; 4 | using EnvDTE; 5 | using EnvDTE80; 6 | using Microsoft.VisualStudio.Shell; 7 | using Microsoft.VSSDK.Tools.VsIdeTesting; 8 | using WebEditor = Microsoft.Web.Editor.WebEditor; 9 | 10 | namespace CssSorter.Test.Shared 11 | { 12 | public static class VSHost 13 | { 14 | static readonly string BaseDirectory = Path.GetDirectoryName(typeof(VSHost).Assembly.Location); 15 | static readonly string FixtureDirectory = Path.Combine(BaseDirectory, "fixtures", "Visual Studio"); 16 | 17 | static string TestCaseDirectory { get { return Path.Combine(Path.GetTempPath(), "Css Sorter Test Files", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")); } } 18 | static DTE DTE { get { return VsIdeTestHostContext.Dte; } } 19 | static System.IServiceProvider ServiceProvider { get { return VsIdeTestHostContext.ServiceProvider; } } 20 | 21 | static T GetService(Type idType) { return (T)ServiceProvider.GetService(idType); } 22 | 23 | static Dispatcher Dispatcher 24 | { 25 | get { return Dispatcher.FromThread(WebEditor.UIThread); } 26 | } 27 | 28 | ///Gets the currently active project (as reported by the Solution Explorer), if any. 29 | static Project GetActiveProject() 30 | { 31 | try 32 | { 33 | Array activeSolutionProjects = VSHost.DTE.ActiveSolutionProjects as Array; 34 | 35 | if (activeSolutionProjects != null && activeSolutionProjects.Length > 0) 36 | return activeSolutionProjects.GetValue(0) as Project; 37 | } 38 | catch (Exception ex) 39 | { 40 | throw new InvalidOperationException("Error getting the active project" + ex); 41 | } 42 | 43 | return null; 44 | } 45 | 46 | public static void ReadyingSolution() 47 | { 48 | // Waste of electrons 49 | var directory = TestCaseDirectory; 50 | var solution = (Solution2)DTE.Solution; 51 | solution.Create(directory, "DependencyCreationTests"); 52 | var template = solution.GetProjectTemplate("EmptyWebApplicationProject40.zip", "CSharp"); 53 | solution.AddFromTemplate(template, Path.Combine(directory, "WebAppProject"), "WebAppProject.csproj"); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | webdevchecklist.com.sln 2 | Content/all*.css 3 | *.map 4 | *.min.js 5 | 6 | ################# 7 | ## Visual Studio 8 | ################# 9 | 10 | ## Ignore Visual Studio temporary files, build results, and 11 | ## files generated by popular Visual Studio add-ons. 12 | 13 | # User-specific files 14 | *.suo 15 | *.user 16 | *.sln.docstates 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Rr]elease/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | *_i.c 24 | *_p.c 25 | *.ilk 26 | *.meta 27 | *.obj 28 | *.pch 29 | *.pdb 30 | *.pgc 31 | *.pgd 32 | *.rsp 33 | *.sbr 34 | *.tlb 35 | *.tli 36 | *.tlh 37 | *.tmp 38 | *.vspscc 39 | .builds 40 | *.dotCover 41 | *.pubxml 42 | x64/ 43 | 44 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 45 | #packages/ 46 | 47 | # Visual C++ cache files 48 | ipch/ 49 | *.aps 50 | *.ncb 51 | *.opensdf 52 | *.sdf 53 | 54 | # Visual Studio profiler 55 | *.psess 56 | *.vsp 57 | 58 | # ReSharper is a .NET coding add-in 59 | _ReSharper* 60 | 61 | # Installshield output folder 62 | [Ee]xpress 63 | 64 | # DocProject is a documentation generator add-in 65 | DocProject/buildhelp/ 66 | DocProject/Help/*.HxT 67 | DocProject/Help/*.HxC 68 | DocProject/Help/*.hhc 69 | DocProject/Help/*.hhk 70 | DocProject/Help/*.hhp 71 | DocProject/Help/Html2 72 | DocProject/Help/html 73 | 74 | # Click-Once directory 75 | publish 76 | 77 | # Others 78 | [Bb]in 79 | [Oo]bj 80 | sql 81 | TestResults 82 | *.Cache 83 | ClientBin 84 | stylecop.* 85 | ~$* 86 | *.dbmdl 87 | Generated_Code #added for RIA/Silverlight projects 88 | 89 | # Backup & report files from converting an old project file to a newer 90 | # Visual Studio version. Backup files are not needed, because we have git ;-) 91 | _UpgradeReport_Files/ 92 | Backup*/ 93 | UpgradeLog*.XML 94 | 95 | 96 | 97 | ############ 98 | ## Windows 99 | ############ 100 | 101 | # Windows image file caches 102 | Thumbs.db 103 | 104 | # Folder config file 105 | Desktop.ini 106 | 107 | 108 | ############# 109 | ## Python 110 | ############# 111 | 112 | *.py[co] 113 | 114 | # Packages 115 | *.egg 116 | *.egg-info 117 | dist 118 | build 119 | eggs 120 | parts 121 | bin 122 | var 123 | sdist 124 | develop-eggs 125 | .installed.cfg 126 | 127 | # Installer logs 128 | pip-log.txt 129 | 130 | # Unit test / coverage reports 131 | .coverage 132 | .tox 133 | 134 | #Translations 135 | *.mo 136 | 137 | #Mr Developer 138 | .mr.developer.cfg 139 | 140 | # Mac crap 141 | .DS_Store 142 | -------------------------------------------------------------------------------- /CssSorter.Test/CSS/VendorTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace CssSorter.Test 4 | { 5 | [TestClass] 6 | public class VendorTest 7 | { 8 | private Sorter _sorter = new Sorter(); 9 | 10 | [TestMethod, TestCategory("Vendor")] 11 | public void Vendor1() 12 | { 13 | string[] input = new[] { 14 | "box-shadow: 10px;", 15 | "-webkit-box-shadow: 10px;", 16 | "-moz-box-shadow: 10px;", 17 | }; 18 | 19 | string[] expected = new[]{ 20 | "-moz-box-shadow: 10px;", 21 | "-webkit-box-shadow: 10px;", 22 | "box-shadow: 10px;", 23 | }; 24 | 25 | string[] result = _sorter.SortDeclarations(input); 26 | 27 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 28 | } 29 | 30 | [TestMethod, TestCategory("Vendor")] 31 | public void Vendor2() 32 | { 33 | string[] input = new[] { 34 | "-webkit-text-shadow: 10px;", 35 | "text-shadow: 10px;", 36 | "-ms-text-shadow: 10px;", 37 | "box-shadow: 10px;", 38 | "-webkit-box-shadow: 10px;", 39 | "-moz-box-shadow: 10px;", 40 | }; 41 | 42 | string[] expected = new[]{ 43 | "-moz-box-shadow: 10px;", 44 | "-webkit-box-shadow: 10px;", 45 | "box-shadow: 10px;", 46 | "-ms-text-shadow: 10px;", 47 | "-webkit-text-shadow: 10px;", 48 | "text-shadow: 10px;", 49 | }; 50 | 51 | string[] result = _sorter.SortDeclarations(input); 52 | 53 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 54 | } 55 | 56 | [TestMethod, TestCategory("Vendor")] 57 | public void Vendor3() 58 | { 59 | string[] input = new[] { 60 | "background: 10px;", 61 | "-shadow: 10px;", 62 | }; 63 | 64 | string[] expected = new[]{ 65 | "-shadow: 10px;", 66 | "background: 10px;", 67 | }; 68 | 69 | string[] result = _sorter.SortDeclarations(input); 70 | 71 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 72 | } 73 | 74 | [TestMethod, TestCategory("Vendor")] 75 | public void VendorValues() 76 | { 77 | string[] input = new[] { 78 | "white-space: pre-wrap;", 79 | "white-space: -moz-pre-wrap;", 80 | "white-space: -pre-wrap;", 81 | "white-space: -o-pre-wrap;", 82 | }; 83 | 84 | string[] expected = new[]{ 85 | "white-space: -moz-pre-wrap;", 86 | "white-space: -o-pre-wrap;", 87 | "white-space: -pre-wrap;", 88 | "white-space: pre-wrap;", 89 | }; 90 | 91 | string[] result = _sorter.SortDeclarations(input); 92 | 93 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /CssSorter/CssSorter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {CCDFBB2E-E2A1-4770-8CB2-B7D091F40769} 8 | Library 9 | Properties 10 | CssSorter 11 | CssSorter 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | true 34 | 35 | 36 | key.snk 37 | 38 | 39 | 40 | 41 | False 42 | $(DevEnvDir)\CommonExtensions\Microsoft\Web\Editor\Microsoft.CSS.Editor.dll 43 | 44 | 45 | False 46 | $(DevEnvDir)\CommonExtensions\Microsoft\Web\Editor\Microsoft.Html.Editor.dll 47 | 48 | 49 | 50 | $(DevEnvDir)\Extensions\Microsoft\Web Tools\Languages\Microsoft.VisualStudio.Web.Extensions.dll 51 | 52 | 53 | False 54 | $(DevEnvDir)\CommonExtensions\Microsoft\Web\Editor\Microsoft.Web.Core.dll 55 | 56 | 57 | False 58 | $(DevEnvDir)\CommonExtensions\Microsoft\Web\Editor\Microsoft.Web.Editor.dll 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /CssSorter/DeclarationComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CssSorter 5 | { 6 | public class DeclarationComparer : IComparer 7 | { 8 | public int Compare(string x, string y) 9 | { 10 | string xOrig = GetPropertyName(x); 11 | string yOrig = GetPropertyName(y); 12 | 13 | if (xOrig == yOrig) 14 | { 15 | int xPrefixIndex = ValueList.Values.FindIndex(prefix => GetPropertyValue(x).StartsWith(prefix)); 16 | 17 | 18 | int yPrefixIndex = ValueList.Values.FindIndex(prefix => GetPropertyValue(y).StartsWith(prefix)); 19 | 20 | if (!xPrefixIndex.Equals(yPrefixIndex)) 21 | { 22 | xPrefixIndex = xPrefixIndex == -1 ? ValueList.Values.Count : xPrefixIndex; 23 | yPrefixIndex = yPrefixIndex == -1 ? ValueList.Values.Count : yPrefixIndex; 24 | } 25 | 26 | return xPrefixIndex.CompareTo(yPrefixIndex); 27 | } 28 | 29 | string xNorm = GetNormalizedName(x); 30 | string yNorm = GetNormalizedName(y); 31 | 32 | // Vendor specifics 33 | if (x.StartsWith("-") && y.StartsWith("-")) 34 | { 35 | if (xNorm == yNorm) 36 | return string.Compare(x, y, StringComparison.Ordinal); 37 | else 38 | return xNorm.CompareTo(yNorm); 39 | } 40 | 41 | if (x.StartsWith("-") && !y.StartsWith("-")) 42 | { 43 | if (xNorm == yNorm) 44 | return -1; 45 | } 46 | 47 | if (!x.StartsWith("-") && y.StartsWith("-")) 48 | { 49 | if (xNorm == yNorm) 50 | return 1; 51 | } 52 | 53 | // IE hacks 54 | if (xNorm == yNorm) 55 | { 56 | if ((x[0] == '*' || x[0] == '_') && (y[0] != '*' && y[0] != '_')) 57 | return 1; 58 | 59 | if ((y[0] == '*' || y[0] == '_') && (x[0] != '*' && x[0] != '_')) 60 | return -1; 61 | } 62 | 63 | //if (xNorm == yNorm || (xNorm == null && yNorm == null)) 64 | // return 0; 65 | 66 | if (xNorm == null) 67 | return -1; 68 | 69 | if (yNorm == null) 70 | return 1; 71 | 72 | // Mixins 73 | if (xNorm[0] == '.' && yNorm[0] != '0') 74 | return 1; 75 | 76 | if (yNorm[0] == '.' && xNorm[0] != '0') 77 | return -1; 78 | 79 | int xPos = PropertyList.Properties.IndexOf(xNorm); 80 | int yPos = PropertyList.Properties.IndexOf(yNorm); 81 | 82 | return xPos < yPos ? -1 : 1; 83 | } 84 | 85 | private static string GetPropertyValue(string declaration) 86 | { 87 | // For Mixins 88 | if (declaration[0] == '.') 89 | return declaration; 90 | 91 | // For commented out properties 92 | int indexStart = declaration.IndexOf(':'); 93 | int indexEnd = declaration.IndexOf(";"); 94 | 95 | if (indexStart > -1) 96 | { 97 | indexStart++; 98 | return declaration.Substring(indexStart, indexEnd - indexStart).Trim(); 99 | } 100 | 101 | return null; 102 | } 103 | 104 | private static string GetPropertyName(string declaration) 105 | { 106 | // For Mixins 107 | if (declaration[0] == '.') 108 | return declaration; 109 | 110 | // For commented out properties 111 | int index = declaration.IndexOf(':'); 112 | 113 | if (index > -1) 114 | { 115 | return declaration.Substring(0, index);//.Replace(""); 116 | } 117 | 118 | return null; 119 | } 120 | 121 | private static string GetNormalizedName(string declaration) 122 | { 123 | // For Mixins 124 | if (declaration[0] == '.') 125 | return declaration; 126 | 127 | declaration = declaration.TrimStart('*', '_', ' '); 128 | 129 | int index = declaration.IndexOf(':'); 130 | 131 | if (index > -1) 132 | { 133 | string name = declaration.Substring(0, index).TrimStart('/', '*', ' '); 134 | 135 | if (name[0] == '-') 136 | { 137 | int ost = name.IndexOf('-', 1) + 1; 138 | name = ost > 0 ? name.Substring(ost) : name; 139 | } 140 | 141 | return name; 142 | } 143 | 144 | return null; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /CssSorter.Test/CssSorter.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {96DC6F86-D7CB-4091-A725-2780CFAB0047} 7 | Library 8 | Properties 9 | CssSorter.Test 10 | CssSorter.Test 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | False 40 | False 41 | $(ProgramFiles)\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\envdte.dll 42 | 43 | 44 | False 45 | False 46 | $(ProgramFiles)\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\envdte80.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | {ccdfbb2e-e2a1-4770-8cb2-b7d091f40769} 85 | CssSorter 86 | 87 | 88 | 89 | 90 | 91 | 92 | False 93 | 94 | 95 | False 96 | 97 | 98 | False 99 | 100 | 101 | False 102 | 103 | 104 | 105 | 106 | 107 | 108 | 115 | -------------------------------------------------------------------------------- /CssSorter.Test/CSS/CommentTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace CssSorter.Test 4 | { 5 | [TestClass] 6 | public class CommentTest 7 | { 8 | private Sorter _sorter = new Sorter(); 9 | 10 | [TestMethod, TestCategory("Comments")] 11 | public void SortDeclarations_InlineComment_StaysWithAttribute() 12 | { 13 | string[] input = new[] { 14 | "clear: none;", 15 | "float: none; /* inline comment 1 */" 16 | }; 17 | 18 | string[] expected = new[]{ 19 | "float: none; /* inline comment 1 */", 20 | "clear: none;" 21 | }; 22 | 23 | string[] result = _sorter.SortDeclarations(input); 24 | 25 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 26 | } 27 | 28 | [TestMethod, TestCategory("Comments")] 29 | public void Comment1() 30 | { 31 | string[] input = new[] { 32 | "top: 10px;", 33 | "/* border: none; */", 34 | "_position: relative;" 35 | }; 36 | 37 | string[] expected = new[]{ 38 | "_position: relative;", 39 | "top: 10px;", 40 | "\r\n/* border: none; */", 41 | }; 42 | 43 | string[] result = _sorter.SortDeclarations(input); 44 | 45 | Assert.AreEqual(string.Join("", expected), string.Join("", result)); 46 | } 47 | 48 | [TestMethod, TestCategory("Comments")] 49 | public void Comment2() 50 | { 51 | string[] input = new[] { 52 | "/* some comment */", 53 | "top: 10px;", 54 | "position: relative;" 55 | }; 56 | 57 | string[] expected = new[]{ 58 | "position: relative;", 59 | "top: 10px;", 60 | "\r\n/* some comment */", 61 | }; 62 | 63 | string[] result = _sorter.SortDeclarations(input); 64 | 65 | Assert.AreEqual(string.Join("\n", expected), string.Join("\n", result)); 66 | } 67 | 68 | [TestMethod, TestCategory("Comments")] 69 | public void Comment3() 70 | { 71 | string[] input = new[] { 72 | "top: 10px;", 73 | "/* some comment */", 74 | "position: relative;" 75 | }; 76 | 77 | string[] expected = new[]{ 78 | "position: relative;", 79 | "top: 10px;", 80 | "\r\n/* some comment */", 81 | }; 82 | 83 | string[] result = _sorter.SortDeclarations(input); 84 | 85 | Assert.AreEqual(string.Join("\n", expected), string.Join("\n", result)); 86 | } 87 | 88 | [TestMethod, TestCategory("Comments")] 89 | public void Comment4() 90 | { 91 | string[] input = new[] { 92 | "top: 10px;", 93 | "/* some comment2 */", 94 | "position: relative;", 95 | "/* some comment */", 96 | }; 97 | 98 | string[] expected = new[]{ 99 | "position: relative;", 100 | "top: 10px;", 101 | "\r\n/* some comment */", 102 | "\r\n/* some comment2 */", 103 | }; 104 | 105 | string[] result = _sorter.SortDeclarations(input); 106 | 107 | Assert.AreEqual(string.Join("\n", expected), string.Join("\n", result)); 108 | } 109 | 110 | [TestMethod, TestCategory("Comments")] 111 | public void Comment5() 112 | { 113 | string[] input = new[] { 114 | "top: 10px;", 115 | "/* some comment2 */", 116 | "position: relative;", 117 | "\r\n/* some comment3 */", 118 | "\r\n/* some comment */", 119 | }; 120 | 121 | string[] expected = new[]{ 122 | "position: relative;", 123 | "top: 10px;", 124 | "\r\n/* some comment */", 125 | "\r\n/* some comment2 */", 126 | "\r\n/* some comment3 */", 127 | }; 128 | 129 | string[] result = _sorter.SortDeclarations(input); 130 | 131 | Assert.AreEqual(string.Join("\n", expected), string.Join("\n", result)); 132 | } 133 | 134 | [TestMethod, TestCategory("Comments")] 135 | public void Comment6() 136 | { 137 | string[] input = new[] { 138 | "/* some comment */", 139 | "top: 10px;", 140 | "/* some comment2 */", 141 | "position: relative;", 142 | "/* some comment3", 143 | "some comment3 continued", 144 | "some comment3 continued again*/", 145 | }; 146 | 147 | string[] expected = new[]{ 148 | "position: relative;", 149 | "top: 10px;", 150 | "\r\n/* some comment */", 151 | "\r\n/* some comment2 */", 152 | "\r\n/* some comment3\r\n" + 153 | "some comment3 continued\r\n" + 154 | "some comment3 continued again*/", 155 | }; 156 | 157 | string[] result = _sorter.SortDeclarations(input); 158 | 159 | Assert.AreEqual(string.Join("\n", expected), string.Join("\n", result)); 160 | } 161 | 162 | [TestMethod, TestCategory("Comments")] 163 | public void Comment7() 164 | { 165 | string[] input = new[] { 166 | "/* some comment */", 167 | "top: 10px;", 168 | "/* some comment2 */", 169 | "position: relative;", 170 | "/* some comment3", 171 | "some comment3 continued*/", 172 | }; 173 | 174 | string[] expected = new[]{ 175 | "position: relative;", 176 | "top: 10px;", 177 | "\r\n/* some comment */", 178 | "\r\n/* some comment2 */", 179 | "\r\n/* some comment3\r\n" + 180 | "some comment3 continued*/", 181 | }; 182 | 183 | string[] result = _sorter.SortDeclarations(input); 184 | 185 | Assert.AreEqual(string.Join("\n", expected), string.Join("\n", result)); 186 | } 187 | 188 | [TestMethod, TestCategory("Comments")] 189 | public void Comment8() 190 | { 191 | string[] input = new[] { 192 | "float: left;", 193 | "margin-top: 25px;", 194 | "margin-left: 50px;", 195 | "margin-bottom: 10px;", 196 | "/*clear: both;", 197 | "float: left;*/", 198 | }; 199 | 200 | string[] expected = new[]{ 201 | "float: left;", 202 | "margin-top: 25px;", 203 | "margin-bottom: 10px;", 204 | "margin-left: 50px;", 205 | "\r\n/*clear: both;\r\n" + 206 | "float: left;*/", 207 | }; 208 | 209 | string[] result = _sorter.SortDeclarations(input); 210 | 211 | Assert.AreEqual(string.Join("\n", expected), string.Join("\n", result)); 212 | } 213 | } 214 | } 215 | 216 | -------------------------------------------------------------------------------- /CssSorter/Sorter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using Microsoft.Css.Extensions; 7 | using Microsoft.CSS.Core; 8 | using Microsoft.CSS.Editor; 9 | using Microsoft.Html.Editor; 10 | using Microsoft.Less.Core; 11 | using Microsoft.Scss.Core; 12 | using Microsoft.Web.Editor; 13 | 14 | namespace CssSorter 15 | { 16 | public class Sorter 17 | { 18 | public string[] SortDeclarations(IEnumerable declarations) 19 | { 20 | Dictionary inlineCommentStorage = new Dictionary(); 21 | List filteredDeclarations = new List(); 22 | 23 | // How could you do this with clever linq stuff? 24 | foreach (string attribute in declarations) 25 | { 26 | string trimmedAttribute = attribute.Trim(); 27 | Match inlineCommentRegexMatch = Regex.Match(trimmedAttribute, "/\\*.*\\*/"); 28 | if (inlineCommentRegexMatch.Success 29 | && inlineCommentRegexMatch.Index > 0) 30 | { 31 | string comment = inlineCommentRegexMatch.Value.Trim(); 32 | string value = trimmedAttribute.Remove(inlineCommentRegexMatch.Index).Trim(); 33 | inlineCommentStorage.Add(value, comment); 34 | filteredDeclarations.Add(value); 35 | } 36 | else 37 | { 38 | filteredDeclarations.Add(attribute.Trim()); 39 | } 40 | } 41 | 42 | string rule = "div {" + string.Join(Environment.NewLine, filteredDeclarations) + "}"; 43 | CssParser parser = new CssParser(); 44 | StyleSheet sheet = parser.Parse(rule, true); 45 | 46 | var comments = sheet.RuleSets[0].Block.Children.Where(c => c is CComment).Select(c => Environment.NewLine + c.Text); 47 | var decls = sheet.RuleSets[0].Block.Declarations.Select(d => d.Text); 48 | var sorted = decls.OrderBy(d => d, new DeclarationComparer()); 49 | 50 | List list = new List(Stringify(sorted)); 51 | list.AddRange(comments.OrderBy(c => c)); 52 | 53 | var query = from c in list 54 | join o in inlineCommentStorage on c equals o.Key into gj 55 | from sublist in gj.DefaultIfEmpty() 56 | select c + (sublist.Key == null ? string.Empty : ' ' + sublist.Value); 57 | 58 | return query.ToArray(); 59 | } 60 | 61 | private string[] SortDeclarations2(IEnumerable declarations) 62 | { 63 | var clean = from d in declarations 64 | where !string.IsNullOrWhiteSpace(d) 65 | select d.Trim(); 66 | 67 | var sorted = clean.OrderBy(d => d, new DeclarationComparer()); 68 | 69 | return Stringify(sorted).ToArray(); 70 | } 71 | 72 | public string SortStyleSheet(string css) 73 | { 74 | ICssParser parser = new CssParser(); 75 | StyleSheet stylesheet = parser.Parse(css.Trim(), true); 76 | 77 | CssFormatter formatter = new CssFormatter(); 78 | formatter.Options.RemoveLastSemicolon = false; 79 | 80 | StringBuilder sb = new StringBuilder(stylesheet.Text); 81 | 82 | var visitor = new CssItemCollector(true); 83 | stylesheet.Accept(visitor); 84 | 85 | foreach (RuleBlock rule in visitor.Items.Where(r => r.IsValid).Reverse()) 86 | { 87 | if (rule.Declarations.Count <= 1) 88 | continue; 89 | 90 | int start = rule.OpenCurlyBrace.AfterEnd; 91 | int length = rule.Length - 2; 92 | 93 | string text = formatter.Format(rule.Text).Trim().Trim('}', '{'); 94 | string[] declarations = text.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); 95 | 96 | var sorted = SortDeclarations(declarations); 97 | 98 | sb.Remove(start, length); 99 | sb.Insert(start, string.Join("", sorted)); 100 | } 101 | 102 | return sb.ToString(); 103 | } 104 | 105 | public string SortLess(string less) 106 | { 107 | ICssParser parser = CssParserLocator.FindComponent(ContentTypeManager.GetContentType(LessContentTypeDefinition.LessContentType)).CreateParser(); 108 | StyleSheet stylesheet = parser.Parse(less.Trim(), true); 109 | 110 | return PreprocessorSorting(stylesheet); 111 | } 112 | 113 | public string SortScss(string scss) 114 | { 115 | ICssParser parser = CssParserLocator.FindComponent(ContentTypeManager.GetContentType(ScssContentTypeDefinition.ScssContentType)).CreateParser(); 116 | StyleSheet stylesheet = parser.Parse(scss.Trim(), true); 117 | 118 | return PreprocessorSorting(stylesheet); 119 | } 120 | 121 | private string PreprocessorSorting(StyleSheet stylesheet) 122 | { 123 | StringBuilder sb = new StringBuilder(stylesheet.Text); 124 | 125 | var visitor = new CssItemCollector(true); 126 | stylesheet.Accept(visitor); 127 | 128 | foreach (var rule in visitor.Items.Where(r => r.IsValid).Reverse()) 129 | { 130 | if (rule.Children.Count < 2) 131 | continue; 132 | 133 | int start = rule.OpenCurlyBrace.AfterEnd; 134 | int length = rule.Length - 1; 135 | 136 | length = AdjustLength(rule, start, length); 137 | 138 | if (length < 1) 139 | continue; 140 | 141 | string text = GetNormalizedText(rule, start, length); 142 | 143 | string[] declarations = text.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); 144 | //.Where(t => !string.IsNullOrWhiteSpace(t)).ToArray(); 145 | 146 | var sorted = SortDeclarations2(declarations); 147 | 148 | sb.Remove(start, length - 1); 149 | sb.Insert(start, string.Join("", sorted)); 150 | } 151 | 152 | return sb.ToString(); 153 | } 154 | 155 | private static string GetNormalizedText(CssRuleBlock rule, int start, int length) 156 | { 157 | StringBuilder text = new StringBuilder(); 158 | 159 | foreach (ParseItem dec in rule.Children.Where( 160 | c => 161 | c is Declaration || 162 | c is Comment || 163 | c is LessMixinReferenceList || 164 | c is LessVariableDeclaration || 165 | c is ScssMixinReference || 166 | c is ScssVariableDeclaration)) 167 | { 168 | if (dec.Start > start + length) 169 | break; 170 | 171 | text.AppendLine(dec.Text); 172 | } 173 | 174 | return text.ToString(); 175 | } 176 | 177 | private static int AdjustLength(RuleBlock rule, int start, int length) 178 | { 179 | var inner = new CssItemCollector(); 180 | rule.Accept(inner); 181 | 182 | if (inner.Items.Count > 0) 183 | { 184 | length = inner.Items[0].Start - start; 185 | } 186 | return length; 187 | } 188 | 189 | private IEnumerable Stringify(IEnumerable declarations) 190 | { 191 | List list = new List(); 192 | 193 | bool isInMultiLineComment = false; 194 | 195 | foreach (string dec in declarations) 196 | { 197 | bool hasCommentStart = dec.Contains("/*"); 198 | bool hasCommentEnd = dec.Contains("*/"); 199 | if (!dec.EndsWith(";") && !hasCommentStart && !hasCommentEnd && !isInMultiLineComment) 200 | list.Add(dec + ";"); 201 | else 202 | list.Add(dec); 203 | if (isInMultiLineComment && hasCommentEnd) isInMultiLineComment = false; 204 | if (hasCommentStart && !hasCommentEnd) isInMultiLineComment = true; 205 | } 206 | 207 | return list; 208 | } 209 | } 210 | } 211 | 212 | -------------------------------------------------------------------------------- /CssSorter/PropertyList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CssSorter 4 | { 5 | public class PropertyList 6 | { 7 | public static List Properties { get; set; } 8 | 9 | static PropertyList() 10 | { 11 | FillList(); 12 | } 13 | 14 | // List comes from https://github.com/miripiruni/CSScomb 15 | private static void FillList() 16 | { 17 | Properties = new List(new string[] { 18 | "position", 19 | "top", 20 | "right", 21 | "bottom", 22 | "left", 23 | "z-index", 24 | "display", 25 | "visibility", 26 | //"-webkit-flex-direction", 27 | //"-moz-flex-direction", 28 | //"-ms-flex-direction", 29 | //"-o-flex-direction", 30 | "flex-direction", 31 | //"-webkit-flex-order", 32 | //"-moz-flex-order", 33 | //"-ms-flex-order", 34 | //"-o-flex-order", 35 | "flex-order", 36 | //"-webkit-flex-pack", 37 | //"-moz-flex-pack", 38 | //"-ms-flex-pack", 39 | //"-o-flex-pack", 40 | "flex-pack", 41 | "float", 42 | "clear", 43 | //"-webkit-flex-align", 44 | //"-moz-flex-align", 45 | //"-ms-flex-align", 46 | //"-o-flex-align", 47 | "flex-align", 48 | "overflow", 49 | //"-ms-overflow-x", 50 | //"-ms-overflow-y", 51 | "overflow-x", 52 | "overflow-y", 53 | "clip", 54 | //"-webkit-box-sizing", 55 | //"-moz-box-sizing", 56 | "box-sizing", 57 | "margin", 58 | "margin-top", 59 | "margin-right", 60 | "margin-bottom", 61 | "margin-left", 62 | "padding", 63 | "padding-top", 64 | "padding-right", 65 | "padding-bottom", 66 | "padding-left", 67 | "min-width", 68 | "min-height", 69 | "max-width", 70 | "max-height", 71 | "width", 72 | "height", 73 | "outline", 74 | "outline-width", 75 | "outline-style", 76 | "outline-color", 77 | "outline-offset", 78 | "border", 79 | "border-spacing", 80 | "border-collapse", 81 | "border-width", 82 | "border-style", 83 | "border-color", 84 | "border-top", 85 | "border-top-width", 86 | "border-top-style", 87 | "border-top-color", 88 | "border-right", 89 | "border-right-width", 90 | "border-right-style", 91 | "border-right-color", 92 | "border-bottom", 93 | "border-bottom-width", 94 | "border-bottom-style", 95 | "border-bottom-color", 96 | "border-left", 97 | "border-left-width", 98 | "border-left-style", 99 | "border-left-color", 100 | //"-webkit-border-radius", 101 | //"-moz-border-radius", 102 | "border-radius", 103 | //"-webkit-border-top-left-radius", 104 | //"-moz-border-radius-topleft", 105 | "border-top-left-radius", 106 | //"-webkit-border-top-right-radius", 107 | //"-moz-border-radius-topright", 108 | "border-top-right-radius", 109 | //"-webkit-border-bottom-right-radius", 110 | //"-moz-border-radius-bottomright", 111 | "border-bottom-right-radius", 112 | //"-webkit-border-bottom-left-radius", 113 | //"-moz-border-radius-bottomleft", 114 | "border-bottom-left-radius", 115 | //"-webkit-border-image", 116 | //"-moz-border-image", 117 | //"-o-border-image", 118 | "border-image", 119 | //"-webkit-border-image-source", 120 | //"-moz-border-image-source", 121 | //"-o-border-image-source", 122 | "border-image-source", 123 | //"-webkit-border-image-slice", 124 | //"-moz-border-image-slice", 125 | //"-o-border-image-slice", 126 | "border-image-slice", 127 | //"-webkit-border-image-width", 128 | //"-moz-border-image-width", 129 | //"-o-border-image-width", 130 | "border-image-width", 131 | //"-webkit-border-image-outset", 132 | //"-moz-border-image-outset", 133 | //"-o-border-image-outset", 134 | "border-image-outset", 135 | //"-webkit-border-image-repeat", 136 | //"-moz-border-image-repeat", 137 | //"-o-border-image-repeat", 138 | "border-image-repeat", 139 | //"-webkit-border-top-image", 140 | //"-moz-border-top-image", 141 | //"-o-border-top-image", 142 | "border-top-image", 143 | //"-webkit-border-right-image", 144 | //"-moz-border-right-image", 145 | //"-o-border-right-image", 146 | "border-right-image", 147 | //"-webkit-border-bottom-image", 148 | //"-moz-border-bottom-image", 149 | //"-o-border-bottom-image", 150 | "border-bottom-image", 151 | //"-webkit-border-left-image", 152 | //"-moz-border-left-image", 153 | //"-o-border-left-image", 154 | "border-left-image", 155 | //"-webkit-border-corner-image", 156 | //"-moz-border-corner-image", 157 | //"-o-border-corner-image", 158 | "border-corner-image", 159 | //"-webkit-border-top-left-image", 160 | //"-moz-border-top-left-image", 161 | //"-o-border-top-left-image", 162 | "border-top-left-image", 163 | //"-webkit-border-top-right-image", 164 | //"-moz-border-top-right-image", 165 | //"-o-border-top-right-image", 166 | "border-top-right-image", 167 | //"-webkit-border-bottom-right-image", 168 | //"-moz-border-bottom-right-image", 169 | //"-o-border-bottom-right-image", 170 | "border-bottom-right-image", 171 | //"-webkit-border-bottom-left-image", 172 | //"-moz-border-bottom-left-image", 173 | //"-o-border-bottom-left-image", 174 | "border-bottom-left-image", 175 | "background", 176 | "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", 177 | "background-color", 178 | "background-image", 179 | "background-attachment", 180 | "background-position", 181 | //"-ms-background-position-x", 182 | //"-ms-background-position-y", 183 | "background-position-x", 184 | "background-position-y", 185 | "background-clip", 186 | "background-origin", 187 | "background-size", 188 | "background-repeat", 189 | "box-decoration-break", 190 | //"-webkit-box-shadow", 191 | //"-moz-box-shadow", 192 | "box-shadow", 193 | "color", 194 | "table-layout", 195 | "caption-side", 196 | "empty-cells", 197 | "list-style", 198 | "list-style-position", 199 | "list-style-type", 200 | "list-style-image", 201 | "quotes", 202 | "content", 203 | "counter-increment", 204 | "counter-reset", 205 | //"-ms-writing-mode", 206 | "vertical-align", 207 | "text-align", 208 | //"-webkit-text-align-last", 209 | //"-moz-text-align-last", 210 | //"-ms-text-align-last", 211 | "text-align-last", 212 | "text-decoration", 213 | "text-emphasis", 214 | "text-emphasis-position", 215 | "text-emphasis-style", 216 | "text-emphasis-color", 217 | "text-indent", 218 | //"-ms-text-justify", 219 | "text-justify", 220 | "text-outline", 221 | "text-transform", 222 | "text-wrap", 223 | //"-ms-text-overflow", 224 | "text-overflow", 225 | "text-overflow-ellipsis", 226 | "text-overflow-mode", 227 | "text-shadow", 228 | "white-space", 229 | "word-spacing", 230 | //"-ms-word-wrap", 231 | "word-wrap", 232 | //"-ms-word-break", 233 | "word-break", 234 | //"-moz-tab-size", 235 | //"-o-tab-size", 236 | "tab-size", 237 | //"-webkit-hyphens", 238 | //"-moz-hyphens", 239 | "hyphens", 240 | "letter-spacing", 241 | "font", 242 | "font-weight", 243 | "font-style", 244 | "font-variant", 245 | "font-size-adjust", 246 | "font-stretch", 247 | "font-size", 248 | "font-family", 249 | "src", 250 | "line-height", 251 | "opacity", 252 | //"-ms-filter:\'progid:DXImageTransform.Microsoft.Alpha", 253 | "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", 254 | //"-ms-interpolation-mode", 255 | //"-webkit-filter", 256 | //"-ms-filter", 257 | "filter", 258 | "resize", 259 | "cursor", 260 | "nav-index", 261 | "nav-up", 262 | "nav-right", 263 | "nav-down", 264 | "nav-left", 265 | //"-webkit-transition", 266 | //"-moz-transition", 267 | //"-ms-transition", 268 | //"-o-transition", 269 | "transition", 270 | //"-webkit-transition-delay", 271 | //"-moz-transition-delay", 272 | //"-ms-transition-delay", 273 | //"-o-transition-delay", 274 | "transition-delay", 275 | //"-webkit-transition-timing-function", 276 | //"-moz-transition-timing-function", 277 | //"-ms-transition-timing-function", 278 | //"-o-transition-timing-function", 279 | "transition-timing-function", 280 | //"-webkit-transition-duration", 281 | //"-moz-transition-duration", 282 | //"-ms-transition-duration", 283 | //"-o-transition-duration", 284 | "transition-duration", 285 | //"-webkit-transition-property", 286 | //"-moz-transition-property", 287 | //"-ms-transition-property", 288 | //"-o-transition-property", 289 | "transition-property", 290 | //"-webkit-transform", 291 | //"-moz-transform", 292 | //"-ms-transform", 293 | //"-o-transform", 294 | "transform", 295 | //"-webkit-transform-origin", 296 | //"-moz-transform-origin", 297 | //"-ms-transform-origin", 298 | //"-o-transform-origin", 299 | "transform-origin", 300 | //"-webkit-animation", 301 | //"-moz-animation", 302 | //"-ms-animation", 303 | //"-o-animation", 304 | "animation", 305 | //"-webkit-animation-name", 306 | //"-moz-animation-name", 307 | //"-ms-animation-name", 308 | //"-o-animation-name", 309 | "animation-name", 310 | //"-webkit-animation-duration", 311 | //"-moz-animation-duration", 312 | //"-ms-animation-duration", 313 | //"-o-animation-duration", 314 | "animation-duration", 315 | //"-webkit-animation-play-state", 316 | //"-moz-animation-play-state", 317 | //"-ms-animation-play-state", 318 | //"-o-animation-play-state", 319 | "animation-play-state", 320 | //"-webkit-animation-timing-function", 321 | //"-moz-animation-timing-function", 322 | //"-ms-animation-timing-function", 323 | //"-o-animation-timing-function", 324 | "animation-timing-function", 325 | //"-webkit-animation-delay", 326 | //"-moz-animation-delay", 327 | //"-ms-animation-delay", 328 | //"-o-animation-delay", 329 | "animation-delay", 330 | //"-webkit-animation-iteration-count", 331 | //"-moz-animation-iteration-count", 332 | //"-ms-animation-iteration-count", 333 | //"-o-animation-iteration-count", 334 | "animation-iteration-count", 335 | //"-webkit-animation-direction", 336 | //"-moz-animation-direction", 337 | //"-ms-animation-direction", 338 | //"-o-animation-direction", 339 | "animation-direction", 340 | "pointer-events", 341 | "unicode-bidi", 342 | "direction", 343 | //"-webkit-columns", 344 | //"-moz-columns", 345 | "columns", 346 | //"-webkit-column-span", 347 | //"-moz-column-span", 348 | "column-span", 349 | //"-webkit-column-width", 350 | //"-moz-column-width", 351 | "column-width", 352 | //"-webkit-column-count", 353 | //"-moz-column-count", 354 | "column-count", 355 | //"-webkit-column-fill", 356 | //"-moz-column-fill", 357 | "column-fill", 358 | //"-webkit-column-gap", 359 | //"-moz-column-gap", 360 | "column-gap", 361 | //"-webkit-column-rule", 362 | //"-moz-column-rule", 363 | "column-rule", 364 | //"-webkit-column-rule-width", 365 | //"-moz-column-rule-width", 366 | "column-rule-width", 367 | //"-webkit-column-rule-style", 368 | //"-moz-column-rule-style", 369 | "column-rule-style", 370 | //"-webkit-column-rule-color", 371 | //"-moz-column-rule-color", 372 | "column-rule-color", 373 | "break-before", 374 | "break-inside", 375 | "break-after", 376 | "page-break-before", 377 | "page-break-inside", 378 | "page-break-after", 379 | "orphans", 380 | "widows", 381 | //"-ms-zoom", 382 | "zoom", 383 | "max-zoom", 384 | "min-zoom", 385 | "user-zoom", 386 | "orientation" 387 | }); 388 | } 389 | } 390 | } 391 | --------------------------------------------------------------------------------