Simplifies selector traversal in advanced merging.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 8 Feb 2015 11:42:42 +0000 (11:42 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 10 Feb 2015 20:43:29 +0000 (20:43 +0000)
Unifies selector traversal from `@media` merge algorithm and one
merging selectors with same name.

Effectively solves `@media` merging issue when traversed property
can be a shorthand.

lib/selectors/optimizers/advanced.js
test/fixtures/big-min.css
test/fixtures/reset-min.css
test/integration-test.js
test/media-queries-test.js

index ed74b7e..11c4d45 100644 (file)
@@ -23,22 +23,41 @@ function unsafeSelector(value) {
   return /\.|\*| :/.test(value);
 }
 
-function extractProperties(token) {
-  return (token.metadata.body.match(/([a-z\-]+):/g) || [])
-    .join('/')
-    .replace(/\-[a-z]+/g, '')
-    .split('/');
+function allProperties(token) {
+  var properties = [];
+
+  if (token.kind == 'selector') {
+    for (var i = token.metadata.bodiesList.length - 1; i >= 0; i--) {
+      var property = token.metadata.bodiesList[i];
+      var splitAt = property.indexOf(':');
+      properties.push([
+        property.substring(0, splitAt).match(/([a-z]+)/)[0],
+        property.substring(splitAt + 1)
+      ]);
+    }
+  } else if (token.kind == 'block') {
+    for (var j = token.body.length - 1; j >= 0; j--) {
+      properties = properties.concat(allProperties(token.body[j]));
+    }
+  }
+
+  return properties;
 }
 
-function unsafeTraversal(token, allProperties) {
-  var properties = extractProperties(token);
+function canReorder(left, right) {
+  for (var i = right.length - 1; i >= 0; i--) {
+    for (var j = left.length - 1; j >= 0; j--) {
+      var rightName = right[i][0];
+      var rightValue = right[i][1];
+      var leftName = left[j][0];
+      var leftValue = left[j][1];
 
-  for (var j = 0; j < properties.length; j++) {
-    if (allProperties.indexOf(properties[j]) > -1)
-      return true;
+      if (rightName == leftName && rightValue != leftValue)
+        return false;
+    }
   }
 
-  return false;
+  return true;
 }
 
 AdvancedOptimizer.prototype.isSpecial = function (selector) {
@@ -307,11 +326,17 @@ AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) {
       var targetToken = tokens[targetPosition];
       var movedPosition = positions[j];
       var movedToken = tokens[movedPosition];
-      var movedProperties = extractProperties(movedToken);
+      var movedProperties = allProperties(movedToken);
 
       for (var k = movedPosition - 1; k > targetPosition; k--) {
-        if (tokens[k].isFlatBlock === false || tokens[k].kind == 'selector' && unsafeTraversal(tokens[k], movedProperties))
+        if (tokens[k].isFlatBlock === false)
           continue selectorIterator;
+
+        if (tokens[k].kind == 'selector') {
+          var traversedProperties = allProperties(tokens[k]);
+          if (!canReorder(movedProperties, traversedProperties))
+            continue selectorIterator;
+        }
       }
 
       var joinAt = [movedToken.body.length];
@@ -353,41 +378,6 @@ AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) {
   var candidates = {};
   var reduced = [];
 
-  function allProperties(token) {
-    var properties = [];
-
-    if (token.kind == 'selector') {
-      for (var i = token.metadata.bodiesList.length - 1; i >= 0; i--) {
-        var property = token.metadata.bodiesList[i];
-        var splitAt = property.indexOf(':');
-        properties.push([
-          property.substring(0, splitAt),
-          property.substring(splitAt + 1)
-        ]);
-      }
-    } else if (token.kind == 'block') {
-      for (var j = token.body.length - 1; j >= 0; j--) {
-        properties = properties.concat(allProperties(token.body[j]));
-      }
-    }
-
-    return properties;
-  }
-
-  function breakingMove(moved, traversed) {
-    for (var i = traversed.length - 1; i >= 0; i--) {
-      for (var j = moved.length - 1; j >= 0; j--) {
-        var traversedName = traversed[i][0];
-        var traversedValue = traversed[i][1];
-        var movedName = moved[j][0];
-        var movedValue = moved[j][1];
-
-        if (traversedName == movedName && traversedValue != movedValue)
-          return true;
-      }
-    }
-  }
-
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
     if (token.kind != 'block' || token.isFlatBlock === true)
@@ -413,7 +403,7 @@ AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) {
 
       for (var k = positions[j] + 1; k < positions[j - 1]; k++) {
         var traversedProperties = allProperties(tokens[k]);
-        if (breakingMove(movedProperties, traversedProperties))
+        if (!canReorder(movedProperties, traversedProperties))
           continue positionLoop;
       }
 
index 069a321..3ec3640 100644 (file)
@@ -982,7 +982,7 @@ label i{font-style:normal;display:none}
 .liste_reactions .references .date{color:#8b9299}
 .liste_reactions input[class=btn],.liste_reactions input[class=btn_abo]{margin:5px 0 10px}
 .reaction_identifier,.reaction_redaction{margin:20px 0;background:#f5f8f9;border-top:3px solid #e9ecf0}
-.reaction_redaction{padding:0 0 10px}
+.reaction_redaction{padding:0 0 10px;overflow:hidden}
 .reaction_identifier .deja_abo{float:left;width:275px;border-left:1px solid #ebeff0}
 .reaction_identifier .deja_abo .erreur{display:none;padding:7px 28px 7px 15px}
 .reaction_identifier .form{padding:10px 10px 10px 14px}
@@ -997,7 +997,6 @@ label i{font-style:normal;display:none}
 .reaction_identifier .deja_abo label+input{width:210px}
 .reaction_identifier .deja_abo p>label{float:left}
 .reaction_identifier .deja_abo p>label+a{float:left;margin:14px 0 0 20px}
-.reaction_redaction{overflow:hidden}
 .reaction_redaction>div{margin:0 16px;padding-top:10px}
 .reaction_redaction label+.annotation{float:right}
 .reaction_redaction textarea{width:495px;max-width:495px;height:144px;margin:10px 0}
index 01f5132..92fc845 100644 (file)
@@ -6,7 +6,6 @@ table{border-collapse:separate;border-spacing:0}
 caption,td,th{text-align:left;font-weight:400}
 blockquote:after,blockquote:before,q:after,q:before{content:""}
 blockquote,q{quotes:"" ""}
-.clear{clear:both}
+.clear{clear:both;display:block}
 .clear:after,.container:after{content:".";display:block;height:0;clear:both;visibility:hidden}
-* html .clear{height:1%}
-.clear{display:block}
\ No newline at end of file
+* html .clear{height:1%}
\ No newline at end of file
index 2b0e449..70f1dc6 100644 (file)
@@ -1124,8 +1124,8 @@ 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{color:#fff}a{*color:#fff}',
-    'overriding an underscore by a non-ajacent selector': 'a{color:red}.one{color:#fff}a{_color:#fff}',
+    'overriding a star by a non-ajacent selector': 'a{color:red}.one{color:#000}a{*color:#fff}',
+    'overriding an underscore by a non-ajacent selector': 'a{color:red}.one{color:#000}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)}',
index ea1b317..abeff2e 100644 (file)
@@ -66,6 +66,12 @@ vows.describe('media queries')
         assert.equal(minified.styles, '@media screen{a{color:red}div{display:block}}@media (min-width:1024px){p{width:100%}body{height:100%}}');
       }
     },
+    'same two with overriding shorthand in between': {
+      topic: new CleanCSS().minify('@media screen{a{font-size:10px}}@media (min-width:1024px){.one{font:12px Helvetica}}@media screen{div{display:block}}'),
+      'get merged': function(minified) {
+        assert.equal(minified.styles, '@media screen{a{font-size:10px}}@media (min-width:1024px){.one{font:12px Helvetica}}@media screen{div{display:block}}');
+      }
+    },
     'same two with same values as moved in between': {
       topic: new CleanCSS().minify('@media screen{a{color:red}}@media (min-width:1024px){.one{color:red}}@media screen{div{display:block}}'),
       'get merged': function(minified) {