New optimizer supports border-radius, border-color, border-style, border-width
authorTimur Kristóf <venemo@msn.com>
Fri, 28 Feb 2014 14:49:05 +0000 (15:49 +0100)
committerTimur Kristóf <venemo@msn.com>
Fri, 28 Feb 2014 14:49:05 +0000 (15:49 +0100)
lib/clean.js
lib/properties/processable.js
lib/properties/shorthand-notations.js [deleted file]
test/data/big-min.css
test/unit-test.js

index ed00f51..05df04a 100644 (file)
@@ -12,7 +12,6 @@ var ColorHSLToHex = require('./colors/hsl-to-hex');
 var ColorRGBToHex = require('./colors/rgb-to-hex');
 var ColorLongToShortHex = require('./colors/long-to-short-hex');
 
-var ShorthandNotations = require('./properties/shorthand-notations');
 var ImportInliner = require('./imports/inliner');
 var UrlRebase = require('./images/url-rebase');
 var EmptyRemoval = require('./selectors/empty-removal');
@@ -308,10 +307,6 @@ var minify = function(data, callback) {
   replace(/:0 0 0 0([^\.])/g, ':0$1');
   replace(/([: ,=\-])0\.(\d)/g, '$1.$2');
 
-  replace(function shorthandNotations() {
-    data = new ShorthandNotations(data).process();
-  });
-
   // restore rect(...) zeros syntax for 4 zeros
   replace(/rect\(\s?0(\s|,)0[ ,]0[ ,]0\s?\)/g, 'rect(0$10$10$10)');
 
index c339e00..91e00e0 100644 (file)
@@ -90,13 +90,12 @@ module.exports = (function () {
   canOverride = Object.freeze(canOverride);
 
   // Functions for breaking up shorthands to components
-  var breakUp = {
-    // Use this for properties with 4 unit values (like margin or padding)
-    // NOTE: it handles shorter forms of these properties too (like, only 1, 2, or 3 units)
-    fourUnits: function (token) {
+  var breakUp = {};
+  breakUp.takeCareOfFourValues = function (splitfunc) {
+    return function (token) {
       var descriptor = processable[token.prop];
       var result = [];
-      var splitval = token.value.match(new RegExp(validator.cssUnitAnyRegexStr, 'gi'));
+      var splitval = splitfunc(token.value);
 
       if (splitval.length === 0 || (splitval.length < descriptor.components.length && descriptor.components.length > 4)) {
         // This token is malformed and we have no idea how to fix it. So let's just keep it intact
@@ -124,150 +123,192 @@ module.exports = (function () {
       }
 
       return result;
-    },
-    // Breaks up a background property value
-    background: function (token) {
-      // Default values
-      var result = Token.makeDefaults(['background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment'], token.isImportant);
-      var color = result[0], image = result[1], repeat = result[2], position = result[3], attachment = result[4];
-
-      // Take care of inherit
-      if (token.value === 'inherit') {
-        // NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value
-        color.value = image.value =  repeat.value = position.value = attachment.value = 'inherit';
-        return result;
+    };
+  };
+  // Use this for properties with 4 unit values (like margin or padding)
+  // NOTE: it handles shorter forms of these properties too (like, only 1, 2, or 3 units)
+  breakUp.fourUnits = breakUp.takeCareOfFourValues(function (val) {
+    return val.match(new RegExp(validator.cssUnitAnyRegexStr, 'gi'));
+  });
+  // Use this when you simply want to break up four values along spaces
+  breakUp.fourBySpaces = breakUp.takeCareOfFourValues(function (val) {
+    return val.split(' ').filter(function (v) { return v; });
+  });
+  // Use this for non-length values that can also contain functions
+  breakUp.fourBySpacesOrFunctions = breakUp.takeCareOfFourValues(function (val) {
+    var result = [];
+    var curr = '';
+    var parenthesisLevel = 0;
+
+    for (var i = 0; i < val.length; i++) {
+      var c = val[i];
+      curr += c;
+
+      if (c === '(') {
+        parenthesisLevel++;
+      }
+      else if (c === ')') {
+        parenthesisLevel--;
+        if (parenthesisLevel === 0) {
+          result.push(curr.trim());
+          curr = '';
+        }
       }
-
-      // Break the background up into parts
-      var parts = token.value.split(' ');
-      if (parts.length === 0) {
-        return result;
+      else if (c === ' ' && parenthesisLevel === 0) {
+        result.push(curr.trim());
+        curr = '';
       }
+    }
 
-      // The trick here is that we start going through the parts from the end, then stop after background repeat,
-      // then start from the from the beginning until we find a valid color value. What remains will be treated as background-image.
-
-      var currentIndex = parts.length - 1;
-      var current = parts[currentIndex];
-      // Attachment
-      if (validator.isValidBackgroundAttachment(current)) {
-        // Found attachment
-        attachment.value = current;
-        currentIndex--;
-        current = parts[currentIndex];
-      }
-      // Position
-      var pos = parts[currentIndex - 1] + ' ' + parts[currentIndex];
-      if (currentIndex >= 1 && validator.isValidBackgroundPosition(pos)) {
-        // Found position (containing two parts)
-        position.value = pos;
-        currentIndex -= 2;
-        current = parts[currentIndex];
-      }
-      else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) {
-        // Found position (containing just one part)
-        position.value = current;
-        currentIndex--;
-        current = parts[currentIndex];
-      }
-      // Repeat
-      if (currentIndex >= 0 && validator.isValidBackgroundRepeat(current)) {
-        // Found repeat
-        repeat.value = current;
-        currentIndex--;
-        current = parts[currentIndex];
-      }
-      // Color
-      var fromBeginning = 0;
-      if (validator.isValidColor(parts[0])) {
-        // Found color
-        color.value = parts[0];
-        fromBeginning = 1;
-      }
-      // Image
-      image.value = (parts.splice(fromBeginning, currentIndex - fromBeginning + 1).join(' ')) || 'none';
+    if (curr) {
+      result.push(curr.trim());
+      curr = '';
+    }
 
+    return result;
+  });
+  // Breaks up a background property value
+  breakUp.background = function (token) {
+    // Default values
+    var result = Token.makeDefaults(['background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment'], token.isImportant);
+    var color = result[0], image = result[1], repeat = result[2], position = result[3], attachment = result[4];
+
+    // Take care of inherit
+    if (token.value === 'inherit') {
+      // NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value
+      color.value = image.value =  repeat.value = position.value = attachment.value = 'inherit';
       return result;
-    },
-    // Breaks up a list-style property value
-    listStyle: function (token) {
-      // Default values
-      var result = Token.makeDefaults(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant);
-      var type = result[0], position = result[1], image = result[2];
-
-      if (token.value === 'inherit') {
-        type.value = position.value = image.value = 'inherit';
-        return result;
-      }
+    }
 
-      var parts = token.value.split(' ');
-      var ci = 0;
+    // Break the background up into parts
+    var parts = token.value.split(' ');
+    if (parts.length === 0) {
+      return result;
+    }
 
-      // Type
-      if (ci < parts.length && validator.isValidListStyleType(parts[ci])) {
-        type.value = parts[ci];
-        ci++;
-      }
-      // Position
-      if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) {
-        position.value = parts[ci];
-        ci++;
-      }
-      // Image
-      if (ci < parts.length) {
-        image.value = parts.splice(ci, parts.length - ci + 1).join(' ');
-      }
+    // The trick here is that we start going through the parts from the end, then stop after background repeat,
+    // then start from the from the beginning until we find a valid color value. What remains will be treated as background-image.
+
+    var currentIndex = parts.length - 1;
+    var current = parts[currentIndex];
+    // Attachment
+    if (validator.isValidBackgroundAttachment(current)) {
+      // Found attachment
+      attachment.value = current;
+      currentIndex--;
+      current = parts[currentIndex];
+    }
+    // Position
+    var pos = parts[currentIndex - 1] + ' ' + parts[currentIndex];
+    if (currentIndex >= 1 && validator.isValidBackgroundPosition(pos)) {
+      // Found position (containing two parts)
+      position.value = pos;
+      currentIndex -= 2;
+      current = parts[currentIndex];
+    }
+    else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) {
+      // Found position (containing just one part)
+      position.value = current;
+      currentIndex--;
+      current = parts[currentIndex];
+    }
+    // Repeat
+    if (currentIndex >= 0 && validator.isValidBackgroundRepeat(current)) {
+      // Found repeat
+      repeat.value = current;
+      currentIndex--;
+      current = parts[currentIndex];
+    }
+    // Color
+    var fromBeginning = 0;
+    if (validator.isValidColor(parts[0])) {
+      // Found color
+      color.value = parts[0];
+      fromBeginning = 1;
+    }
+    // Image
+    image.value = (parts.splice(fromBeginning, currentIndex - fromBeginning + 1).join(' ')) || 'none';
 
+    return result;
+  };
+  // Breaks up a list-style property value
+  breakUp.listStyle = function (token) {
+    // Default values
+    var result = Token.makeDefaults(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant);
+    var type = result[0], position = result[1], image = result[2];
+
+    if (token.value === 'inherit') {
+      type.value = position.value = image.value = 'inherit';
       return result;
-    },
-    // Breaks up outline
-    outline: function (token) {
-      // Default values
-      var result = Token.makeDefaults(['outline-color', 'outline-style', 'outline-width'], token.isImportant);
-      var color = result[0], style = result[1], width = result[2];
-
-      // Take care of inherit
-      if (token.value === 'inherit' || token.value === 'inherit inherit inherit') {
-        color.value = style.value = width.value = 'inherit';
-        return result;
-      }
+    }
 
-      // NOTE: usually users don't follow the required order of parts in this shorthand,
-      // so we'll try to parse it caring as little about order as possible
+    var parts = token.value.split(' ');
+    var ci = 0;
 
-      var parts = token.value.split(' '), w;
+    // Type
+    if (ci < parts.length && validator.isValidListStyleType(parts[ci])) {
+      type.value = parts[ci];
+      ci++;
+    }
+    // Position
+    if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) {
+      position.value = parts[ci];
+      ci++;
+    }
+    // Image
+    if (ci < parts.length) {
+      image.value = parts.splice(ci, parts.length - ci + 1).join(' ');
+    }
 
-      if (parts.length === 0) {
-        return result;
-      }
+    return result;
+  };
+  // Breaks up outline
+  breakUp.outline = function (token) {
+    // Default values
+    var result = Token.makeDefaults(['outline-color', 'outline-style', 'outline-width'], token.isImportant);
+    var color = result[0], style = result[1], width = result[2];
+
+    // Take care of inherit
+    if (token.value === 'inherit' || token.value === 'inherit inherit inherit') {
+      color.value = style.value = width.value = 'inherit';
+      return result;
+    }
 
-      if (parts.length >= 1) {
-        // Try to find outline-width, excluding inherit because that can be anything
-        w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); });
-        if (w.length) {
-          width.value = w[0];
-          parts.splice(parts.indexOf(w[0]), 1);
-        }
+    // NOTE: usually users don't follow the required order of parts in this shorthand,
+    // so we'll try to parse it caring as little about order as possible
+
+    var parts = token.value.split(' '), w;
+
+    if (parts.length === 0) {
+      return result;
+    }
+
+    if (parts.length >= 1) {
+      // Try to find outline-width, excluding inherit because that can be anything
+      w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); });
+      if (w.length) {
+        width.value = w[0];
+        parts.splice(parts.indexOf(w[0]), 1);
       }
-      if (parts.length >= 1) {
-        // Try to find outline-style, excluding inherit because that can be anything
-        w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); });
-        if (w.length) {
-          style.value = w[0];
-          parts.splice(parts.indexOf(w[0]), 1);
-        }
+    }
+    if (parts.length >= 1) {
+      // Try to find outline-style, excluding inherit because that can be anything
+      w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); });
+      if (w.length) {
+        style.value = w[0];
+        parts.splice(parts.indexOf(w[0]), 1);
       }
-      if (parts.length >= 1) {
-        // Find outline-color but this time can catch inherit
-        w = parts.filter(function(p) { return validator.isValidOutlineColor(p); });
-        if (w.length) {
-          color.value = w[0];
-          parts.splice(parts.indexOf(w[0]), 1);
-        }
+    }
+    if (parts.length >= 1) {
+      // Find outline-color but this time can catch inherit
+      w = parts.filter(function(p) { return validator.isValidOutlineColor(p); });
+      if (w.length) {
+        color.value = w[0];
+        parts.splice(parts.indexOf(w[0]), 1);
       }
-
-      return result;
     }
+
+    return result;
   };
 
   // Contains functions that can put together shorthands from their components
@@ -465,60 +506,12 @@ module.exports = (function () {
   //   Puts the shorthand together from its components.
   //
   var processable = {
-    'margin': {
-      components: [
-        'margin-top',
-        'margin-right',
-        'margin-bottom',
-        'margin-left'
-      ],
-      breakUp: breakUp.fourUnits,
-      putTogether: putTogether.takeCareOfInherit(putTogether.fourUnits),
-      defaultValue: '0'
-    },
-    'margin-top': {
-      defaultValue: '0',
-      canOverride: canOverride.unit
-    },
-    'margin-right': {
-      defaultValue: '0',
-      canOverride: canOverride.unit
-    },
-    'margin-bottom': {
-      defaultValue: '0',
-      canOverride: canOverride.unit
-    },
-    'margin-left': {
-      defaultValue: '0',
-      canOverride: canOverride.unit
-    },
-    'padding': {
-      components: [
-        'padding-top',
-        'padding-right',
-        'padding-bottom',
-        'padding-left'
-      ],
-      breakUp: breakUp.fourUnits,
-      putTogether: putTogether.takeCareOfInherit(putTogether.fourUnits),
-      defaultValue: '0'
-    },
-    'padding-top': {
-      defaultValue: '0',
-      canOverride: canOverride.unit
-    },
-    'padding-right': {
-      defaultValue: '0',
-      canOverride: canOverride.unit
-    },
-    'padding-bottom': {
-      defaultValue: '0',
-      canOverride: canOverride.unit
-    },
-    'padding-left': {
-      defaultValue: '0',
-      canOverride: canOverride.unit
+    'color': {
+      canOverride: canOverride.color,
+      defaultValue: 'transparent',
+      shortestValue: 'red'
     },
+    // background ------------------------------------------------------------------------------
     'background': {
       components: [
         'background-color',
@@ -532,11 +525,6 @@ module.exports = (function () {
       defaultValue: '0 0',
       shortestValue: '0'
     },
-    'color': {
-      canOverride: canOverride.color,
-      defaultValue: 'transparent',
-      shortestValue: 'red'
-    },
     'background-color': {
       canOverride: canOverride.color,
       defaultValue: 'transparent',
@@ -559,6 +547,7 @@ module.exports = (function () {
       canOverride: canOverride.always,
       defaultValue: 'scroll'
     },
+    // list-style ------------------------------------------------------------------------------
     'list-style': {
       components: [
         'list-style-type',
@@ -587,6 +576,7 @@ module.exports = (function () {
       canOverride: canOverride.always,
       defaultValue: 'none'
     },
+    // outline ------------------------------------------------------------------------------
     'outline': {
       components: [
         'outline-color',
@@ -611,9 +601,88 @@ module.exports = (function () {
       defaultValue: 'medium',
       shortestValue: '0'
     }
-    // TODO: add more
   };
 
+  var addFourValueShorthand = function (prop, components, breakup, puttogether, canoverride, defaultValue, shortestValue) {
+    processable[prop] = {
+      components: components,
+      breakUp: breakup || breakUp.fourUnits,
+      putTogether: puttogether || putTogether.takeCareOfInherit(putTogether.fourUnits),
+      defaultValue: defaultValue || '0',
+      shortestValue: shortestValue
+    };
+    for (var i = 0; i < components.length; i++) {
+      processable[components[i]] = {
+        canOverride: canoverride || canOverride.unit,
+        defaultValue: defaultValue || '0',
+        shortestValue: shortestValue
+      };
+    }
+  };
+
+  addFourValueShorthand('border-radius', [
+    'border-top-left-radius',
+    'border-top-right-radius',
+    'border-bottom-right-radius',
+    'border-bottom-left-radius'
+  ]);
+
+  addFourValueShorthand('-moz-border-radius', [
+    '-moz-border-top-left-radius',
+    '-moz-border-top-right-radius',
+    '-moz-border-bottom-right-radius',
+    '-moz-border-bottom-left-radius'
+  ]);
+
+  addFourValueShorthand('-webkit-border-radius', [
+    '-webkit-border-top-left-radius',
+    '-webkit-border-top-right-radius',
+    '-webkit-border-bottom-right-radius',
+    '-webkit-border-bottom-left-radius'
+  ]);
+
+  addFourValueShorthand('-o-border-radius', [
+    '-o-border-top-left-radius',
+    '-o-border-top-right-radius',
+    '-o-border-bottom-right-radius',
+    '-o-border-bottom-left-radius'
+  ]);
+
+  addFourValueShorthand('border-color', [
+    'border-top-color',
+    'border-right-color',
+    'border-bottom-color',
+    'border-left-color'
+  ], breakUp.fourBySpacesOrFunctions, null, canOverride.color, 'currentColor', 'red');
+
+  addFourValueShorthand('border-style', [
+    'border-top-style',
+    'border-right-style',
+    'border-bottom-style',
+    'border-left-style'
+  ], breakUp.fourBySpaces, null, canOverride.always, 'none');
+
+  addFourValueShorthand('border-width', [
+    'border-top-width',
+    'border-right-width',
+    'border-bottom-width',
+    'border-left-width'
+  ], null, null, null, 'medium', '0');
+
+  addFourValueShorthand('padding', [
+    'padding-top',
+    'padding-right',
+    'padding-bottom',
+    'padding-left'
+  ]);
+
+  addFourValueShorthand('margin', [
+    'margin-top',
+    'margin-right',
+    'margin-bottom',
+    'margin-left'
+  ]);
+
   // Set some stuff iteratively
   for (var proc in processable) {
     if (processable.hasOwnProperty(proc)) {
diff --git a/lib/properties/shorthand-notations.js b/lib/properties/shorthand-notations.js
deleted file mode 100644 (file)
index f29b2d3..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-module.exports = function ShorthandNotations(data) {
-  // shorthand notations
-  var shorthandRegex = function(repeats, hasSuffix) {
-    var pattern = '(padding|margin|border\\-width|border\\-color|border\\-style|border\\-radius):';
-    for (var i = 0; i < repeats; i++)
-      pattern += '([\\d\\w\\.%#\\(\\),]+)' + (i < repeats - 1 ? ' ' : '');
-    return new RegExp(pattern + (hasSuffix ? '([;}])' : ''), 'g');
-  };
-
-  var from4Values = function() {
-    return data.replace(shorthandRegex(4), function(match, property, size1, size2, size3, size4) {
-      if (size1 === size2 && size1 === size3 && size1 === size4)
-        return property + ':' + size1;
-      else if (size1 === size3 && size2 === size4)
-        return property + ':' + size1 + ' ' + size2;
-      else if (size2 === size4)
-        return property + ':' + size1 + ' ' + size2 + ' ' + size3;
-      else
-        return match;
-    });
-  };
-
-  var from3Values = function() {
-    return data.replace(shorthandRegex(3, true), function(match, property, size1, size2, size3, suffix) {
-      if (size1 === size2 && size1 === size3)
-        return property + ':' + size1 + suffix;
-      else if (size1 === size3)
-        return property + ':' + size1 + ' ' + size2 + suffix;
-      else
-        return match;
-    });
-  };
-
-  var from2Values = function() {
-    return data.replace(shorthandRegex(2, true), function(match, property, size1, size2, suffix) {
-      if (size1 === size2)
-        return property + ':' + size1 + suffix;
-      else
-        return match;
-    });
-  };
-
-  return {
-    process: function() {
-      data = from4Values();
-      data = from3Values();
-      return from2Values();
-    }
-  };
-};
index 8c094cd..7f12fa9 100644 (file)
@@ -2194,7 +2194,7 @@ a.god:hover{background:#3c3c3c;color:#fff;text-decoration:none}
 #core-liberation .block-search-results .block-content .object-picture img{display:block;width:87px}
 #core-liberation .block-search-results .block-content .article .object-picture,#core-liberation .block-search-results .block-content .article .object-picture img{height:116px}
 #core-liberation .block-search-results .block-content .object-picture .np{position:relative}
-#core-liberation .block-search-results .block-content .object-picture .np .p1,#core-liberation .block-search-results .block-content .object-picture .np .p2{position:absolute;width:79px;height:102px;border-style:solid;border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}
+#core-liberation .block-search-results .block-content .object-picture .np .p1,#core-liberation .block-search-results .block-content .object-picture .np .p2{position:absolute;width:79px;height:102px;border-style:solid;border-width:7px 4px}
 #core-liberation .block-search-results .block-content .object-picture .np a.date{position:absolute;display:block;width:80px;top:10px;padding:2px 2px 3px 4px}
 #core-liberation .block-search-results .block-content .object-picture .np .p1{z-index:2000}
 #core-liberation .block-search-results .block-content .object-picture .np .p2{z-index:1000}
index 86ee3c9..4f77319 100644 (file)
@@ -1633,13 +1633,33 @@ title']{display:block}",
     ]
   }),
   'shorthand properties': cssContext({
-    'shorthand background' : [
+    'shorthand background #1' : [
       'div{background-color:#111;background-image:url(aaa);background-repeat:repeat;background-position:0 0;background-attachment:scroll}',
       'div{background:#111 url(aaa)}'
     ],
+    'shorthand background #2' : [
+      'div{background-color:#111;background-image:url(aaa);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll}',
+      'div{background:#111 url(aaa) no-repeat}'
+    ],
     'shorthand important background' : [
       'div{background-color:#111!important;background-image:url(aaa)!important;background-repeat:repeat!important;background-position:0 0!important;background-attachment:scroll!important}',
       'div{background:#111 url(aaa)!important}'
+    ],
+    'shorthand border-width': [
+      '.t{border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}',
+      '.t{border-width:7px 4px}'
+    ],
+    'shorthand border-color #1': [
+      '.t{border-top-color:#9fce00;border-bottom-color:#9fce00;border-left-color:#9fce00;border-right-color:#9fce00}',
+      '.t{border-color:#9fce00}'
+    ],
+    'shorthand border-color #2': [
+      '.t{border-right-color:#002;border-bottom-color:#003;border-top-color:#001;border-left-color:#004}',
+      '.t{border-color:#001 #002 #003 #004}'
+    ],
+    'shorthand border-radius': [
+      '.t{border-top-left-radius:7px;border-bottom-right-radius:6px;border-bottom-left-radius:5px;border-top-right-radius:3px}',
+      '.t{border-radius:7px 3px 6px 5px}'
     ]
   }),
   'care about understandability of shorthand components': cssContext({
@@ -1788,7 +1808,14 @@ title']{display:block}",
   }),
   'complex granular properties': cssContext({
     'two granular properties': 'a{border-bottom:1px solid red;border-color:red}',
-    'two same granular properties': 'a{border-color:rgba(0,0,0,.5);border-color:red}',
+    'more understandable granular property should override less understandable': [
+      'a{border-color:rgba(0,0,0,.5);border-color:red}',
+      'a{border-color:red}'
+    ],
+    'less understandable granular property should NOT override more understandable': [
+      'a{border-color:red;border-color:rgba(0,0,0,.5)}',
+      'a{border-color:red;border-color:rgba(0,0,0,.5)}'
+    ],
     'two same granular properties redefined': [
       'a{border-color:rgba(0,0,0,.5);border-color:red;border:0}',
       'a{border:0}'