Fixes #158 - adds advanced selectors rearrangements.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 6 Jan 2015 20:29:07 +0000 (20:29 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 6 Jan 2015 23:20:11 +0000 (23:20 +0000)
* Moves selectors around if moved properties are not redefined in between.

History.md
lib/selectors/optimizers/advanced.js
test/fixtures/big-min.css
test/integration-test.js
test/selectors/optimizer-test.js

index b5582fc..35e42df 100644 (file)
@@ -3,6 +3,7 @@
 
 * Adds 0deg to 0 minification where possible.
 * Adds better non-adjacent selector merging when body is the same.
+* Fixed issue [#158](https://github.com/GoalSmashers/clean-css/issues/158) - adds body-based selectors reduction.
 * Fixed issue [#182](https://github.com/GoalSmashers/clean-css/issues/182) - removing space after closing brace.
 
 [3.0.2 / 2015-01-04](https://github.com/jakubpawlowicz/clean-css/compare/v3.0.1...v3.0.2)
index 5b71569..7fe297b 100644 (file)
@@ -23,6 +23,24 @@ function unsafeSelector(value) {
   return /\.|\*| :/.test(value);
 }
 
+function extractProperties(token) {
+  return (token.metadata.body.match(/([a-z\-]+):/g) || [])
+    .join('/')
+    .replace(/\-[a-z]+/g, '')
+    .split('/');
+}
+
+function unsafeTraversal(token, allProperties) {
+  var properties = extractProperties(token);
+
+  for (var j = 0; j < properties.length; j++) {
+    if (allProperties.indexOf(properties[j]) > -1)
+      return true;
+  }
+
+  return false;
+}
+
 AdvancedOptimizer.prototype.isSpecial = function (selector) {
   return this.options.compatibility.selectors.special.test(selector);
 };
@@ -259,6 +277,48 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o
   }
 };
 
+AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) {
+  var allSelectors = {};
+  var repeatedSelectors = [];
+  var i;
+
+  for (i = tokens.length - 1; i >= 0; i--) {
+    if (tokens[i].kind != 'selector')
+      continue;
+    if (tokens[i].body.length === 0)
+      continue;
+
+    var selector = tokens[i].metadata.selector;
+    allSelectors[selector] = [i].concat(allSelectors[selector] || []);
+
+    if (allSelectors[selector].length == 2)
+      repeatedSelectors.push(selector);
+  }
+
+  for (i = repeatedSelectors.length - 1; i >= 0; i--) {
+    var positions = allSelectors[repeatedSelectors[i]];
+
+    selectorIterator:
+    for (var j = positions.length - 1; j > 0; j--) {
+      var targetPosition = positions[j - 1];
+      var targetToken = tokens[targetPosition];
+      var movedPosition = positions[j];
+      var movedToken = tokens[movedPosition];
+      var movedProperties = extractProperties(movedToken);
+
+      for (var k = movedPosition - 1; k > targetPosition; k--) {
+        if (tokens[k].kind == 'selector' && unsafeTraversal(tokens[k], movedProperties))
+          continue selectorIterator;
+      }
+
+      var joinAt = [movedToken.body.length];
+      var newBody = this.propertyOptimizer.process(targetToken.value, targetToken.body.concat(movedToken.body), joinAt, true);
+      changeBodyOf(targetToken, newBody);
+      movedToken.body = [];
+    }
+  }
+};
+
 AdvancedOptimizer.prototype.mergeNonAdjacentByBody = function (tokens) {
   var candidates = {};
 
@@ -318,6 +378,7 @@ AdvancedOptimizer.prototype.optimize = function (tokens) {
     self.removeDuplicates(tokens);
     self.mergeAdjacent(tokens);
 
+    self.mergeNonAdjacentBySelector(tokens);
     self.mergeNonAdjacentByBody(tokens);
   }
 
index fc8116b..069a321 100644 (file)
@@ -750,10 +750,9 @@ img[height="97"]+.ico29x29{bottom:6%;left:3.5%}
 .portfolio_appel_revolutionnaire.conteneur_carrousel .navigation .suivant{left:auto;right:0}
 .portfolio_appel_revolutionnaire a .legende.bg_fonce{color:#fff}
 #barre_titre,#header,#nav{position:relative}
-#header{z-index:3}
+#header{z-index:3;font-size:12px;text-align:left}
 #barre-titre{z-index:2}
 #nav{z-index:1}
-#header{font-size:12px;text-align:left}
 #header a{display:inline-block}
 .conteneur_haut{width:1000px;margin:0 auto}
 #surheader,#surheader .conteneur_haut{background:#1e5799;background:-moz-linear-gradient(top,#1e5799 0,#2d3841 0,#010c16 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#1e5799),color-stop(0,#2d3841),color-stop(100%,#010c16));background:-webkit-linear-gradient(top,#1e5799 0,#2d3841 0,#010c16 100%);background:-o-linear-gradient(top,#1e5799 0,#2d3841 0,#010c16 100%);background:-ms-linear-gradient(top,#1e5799 0,#2d3841 0,#010c16 100%);background:linear-gradient(top,#1e5799 0,#2d3841 0,#010c16 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2d3841', endColorstr='#010c16', GradientType=0);height:25px;line-height:25px}
index 4886c50..92be116 100644 (file)
@@ -1112,9 +1112,9 @@ path")}',
       'a{color:red\\9;display:block;color:#fff\\9}',
       'a{display:block;color:#fff\\9}'
     ],
-    'overriding a star by a non-ajacent selector': 'a{color:red}.one{display:block}a{*color:#fff}',
-    'overriding a unserscore by a non-ajacent selector': 'a{color:red}.one{display:block}a{_color:#fff}',
-    'overriding a backslash by a non-ajacent selector': 'a{color:red}.one{display:block}a{color:#fff\\9}',
+    'overriding a star by a non-ajacent selector': 'a{color:red}.one{color:#fff}a{*color:#fff}',
+    'overriding an underscore by a non-ajacent selector': 'a{color:red}.one{color:#fff}a{_color:#fff}',
+    'overriding a backslash by a non-ajacent selector': 'a{color:red}.one{color:#fff}a{color:#fff\\9}',
     'keeps rgba(0,0,0,0)': 'a{color:rgba(0,0,0,0)}',
     'keeps rgba(255,255,255,0)': 'a{color:rgba(255,255,255,0)}',
     'keeps hsla(120,100%,50%,0)': 'a{color:hsla(120,100%,50%,0)}'
@@ -1670,7 +1670,10 @@ title']{display:block}",
     ]
   }, { aggressiveMerging: false }),
   'same selectors': cssContext({
-    'of two non-adjacent selectors': '.one{color:red}.two{color:#00f}.one{font-weight:700}',
+    'of two non-adjacent selectors': [
+      '.one{color:red}.two{color:#00f}.one{font-weight:700}',
+      '.one{color:red;font-weight:700}.two{color:#00f}'
+    ],
     'of two adjacent single selectors': [
       '.one{color:red}.one{font-weight:700}',
       '.one{color:red;font-weight:700}'
@@ -1710,18 +1713,18 @@ title']{display:block}",
     'when overriden with a browser specific selector': 'a{color:red}::-webkit-scrollbar,a{color:#fff}'
   }),
   'same non-adjacent selectors': cssContext({
-    'with different properties': 'a{color:red;display:block}.one{font-size:12px}a{margin:2px}',
+    'with different properties': 'a{color:red;display:block}.one{margin:5px}a{margin:2px}',
     'with one redefined property': [
-      'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}',
-      'a{display:block}.one{font-size:12px}a{color:#fff;margin:2px}'
+      'a{color:red;display:block}.one{color:red}a{color:#fff;margin:2px}',
+      'a{display:block}.one{color:red}a{color:#fff;margin:2px}'
     ],
     'with intentionally redefined properties on joins': [
-      'a{display:inline-block;display:-moz-inline-box;color:red}.one{font-size:12px}a{color:#fff;margin:2px}',
-      'a{display:inline-block;display:-moz-inline-box}.one{font-size:12px}a{color:#fff;margin:2px}'
+      'a{display:inline-block;display:-moz-inline-box;color:red}.one{margin:12px}a{color:#fff;margin:2px}',
+      'a{display:inline-block;display:-moz-inline-box}.one{margin:12px}a{color:#fff;margin:2px}'
     ],
-    'with intentionally redefined properties on nultiple joins': [
-      'a{color:red}.one{font-size:12px}a{color:#fff;margin:2px}.two{font-weight:400}a{margin:0}',
-      '.one{font-size:12px}a{color:#fff}.two{font-weight:400}a{margin:0}'
+    'with intentionally redefined properties on multiple joins': [
+      'a{color:red}.one{font-size:12px}a{color:#fff;margin:2px}.two{margin:10px}a{margin:0}',
+      '.one{font-size:12px}a{color:#fff}.two{margin:10px}a{margin:0}'
     ],
     'with all redefined properties': [
       'a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
@@ -1744,16 +1747,15 @@ title']{display:block}",
       'a{margin:0}a,p{color:red;padding:0}.one,a{color:#fff}'
     ],
     'when complex selector overriden by simple selectors': 'a,p{margin:0;color:red}a{color:#fff}',
-    // Pending re-run selectors merge - see #160
     'when complex selector overriden by complex and simple selectors': [
       'a,p{margin:0;color:red}a{color:#fff}a,p{color:#00f}p{color:#0f0}',
-      'a,p{margin:0}a,p{color:#00f}p{color:#0f0}'
+      'a,p{margin:0;color:#00f}p{color:#0f0}'
     ],
     'when complex selector overriden by complex selectors': [
       '.one>.two,.three{color:red;line-height:1rem}#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}',
       '#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}'
     ],
-    'when undefined is used as a value': '.one{text-shadow:undefined}p{color:red}.one{font-size:12px}',
+    'when undefined is used as a value': '.one{text-shadow:undefined}p{font-size:14px}.one{font-size:12px}',
     'when undefined is used as a value with reduction': [
       '.one{text-shadow:undefined}p{color:red}.one{font-size:12px;text-shadow:none}',
       'p{color:red}.one{font-size:12px;text-shadow:none}'
@@ -1763,7 +1765,19 @@ title']{display:block}",
       'a,::-moz-selection{color:red}p{display:block}a,::-moz-selection{color:#fff}',
       'p{display:block}::-moz-selection,a{color:#fff}'
     ],
-    'with full property comparison': '.one{height:7rem}.two{height:auto}.one{line-height:7rem;color:red}'
+    'with full property comparison': '.one{height:7rem}.two{color:#fff}.one{line-height:7rem;color:red}',
+    'with two intermediate, non-overriding selectors': [
+      '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}',
+      '.one{color:red;margin:0;font-size:12px}.two{color:#fff}'
+    ],
+    'with two intermediate, overriding more specific selectors': [
+      '.one{color:red;margin:0}.two{font:12px serif}.one{font-size:12px}',
+      '.one{color:red;margin:0}.two{font:12px serif}.one{font-size:12px}'
+    ],
+    'with three intermediate, non-overriding selectors': [
+      '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}.three{color:#000}.one{padding:0}',
+      '.one{color:red;margin:0;font-size:12px;padding:0}.two{color:#fff}.three{color:#000}'
+    ]
   }),
   'rerun optimizers': cssContext({
     'selectors reducible once': [
index e4185d9..4bc43a4 100644 (file)
@@ -148,8 +148,8 @@ vows.describe(SelectorsOptimizer)
         'a{color:red;display:block;width:100px}div{color:#fff}'
       ],
       'non-adjacent': [
-        'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}',
-        'a{display:block}.one{font-size:12px}a{color:#fff;margin:2px}'
+        'a{color:red;display:block}.one{margin:12px}a{color:#fff;margin:2px}',
+        'a{display:block}.one{margin:12px}a{color:#fff;margin:2px}'
       ],
       'non-adjacent with multi selectors': [
         'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',