Adds support for Polymer mixins.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 24 Aug 2015 04:37:23 +0000 (05:37 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 24 Aug 2015 05:51:27 +0000 (06:51 +0100)
We lacked support of property-level blocks (just simple values) so
this commit adds this with a special handling of properties in
tokenizer, simple & advanced optimization, and stringifier.

See: https://www.polymer-project.org/1.0/docs/devguide/styling.html#custom-css-mixins

15 files changed:
History.md
lib/properties/optimizer.js
lib/properties/override-compactor.js
lib/properties/shorthand-compactor.js
lib/properties/wrap-for-optimizing.js
lib/selectors/extractor.js
lib/selectors/simple.js
lib/stringifier/helpers.js
lib/tokenizer/extract-properties.js
lib/tokenizer/tokenize.js
test/integration-test.js
test/properties/wrap-for-optimizing-test.js
test/selectors/extractor-test.js
test/tokenizer/tokenizer-source-maps-test.js
test/tokenizer/tokenizer-test.js

index 2d6946d..c4db416 100644 (file)
@@ -4,6 +4,7 @@
 * Adds unit compatibility switches to disable length optimizations.
 * Adds inferring proxy settings from HTTP_PROXY environment variable.
 * Adds support for Polymer / Web Components special selectors.
+* Adds support for Polymer mixins.
 * Unifies wrappers for simple & advanced optimizations.
 * Fixed issue [#596](https://github.com/jakubpawlowicz/clean-css/issues/596) - support for !ie IE<8 hack.
 * Fixed issue [#599](https://github.com/jakubpawlowicz/clean-css/issues/599) - support for inlined source maps.
index 54c7c12..702a63d 100644 (file)
@@ -188,6 +188,12 @@ function optimize(selector, properties, mergeAdjacent, withCompacting, options,
   populateComponents(_properties, validator);
   _optimize(_properties, mergeAdjacent, options.aggressiveMerging, validator);
 
+  for (var i = 0, l = _properties.length; i < l; i++) {
+    var _property = _properties[i];
+    if (_property.variable && _property.block)
+      optimize(selector, _property.value[0], mergeAdjacent, withCompacting, options, validator);
+  }
+
   if (withCompacting && options.shorthandCompacting) {
     compactOverrides(_properties, options.compatibility, validator);
     compactShorthands(_properties, options.sourceMap, validator);
index a8a1eb9..c25ce79 100644 (file)
@@ -226,6 +226,9 @@ function compactOverrides(properties, compatibility, validator) {
     if (!isCompactable(right))
       continue;
 
+    if (right.variable)
+      continue;
+
     mayOverride = compactable[right.name].canOverride || canOverride.sameValue;
 
     for (j = i - 1; j >= 0; j--) {
@@ -234,6 +237,9 @@ function compactOverrides(properties, compatibility, validator) {
       if (!isCompactable(left))
         continue;
 
+      if (left.variable)
+        continue;
+
       if (left.unused || right.unused)
         continue;
 
index e378ba3..1cd48f6 100644 (file)
@@ -112,6 +112,9 @@ function compactShortands(properties, sourceMaps, validator) {
     if (property.hack)
       continue;
 
+    if (property.variable)
+      continue;
+
     var descriptor = compactable[property.name];
     if (!descriptor || !descriptor.componentOf)
       continue;
index cf493e6..4de3c64 100644 (file)
@@ -87,7 +87,10 @@ function wrapSingle(property) {
   else if (_hackType == 'backslash' || _hackType == 'bang')
     stripSuffixHack(property, _hackType);
 
+  var isVariable = property[0][0].indexOf('--') === 0;
+
   return {
+    block: isVariable && property[1] && Array.isArray(property[1][0][0]),
     components: [],
     dirty: false,
     hack: _hackType,
@@ -97,7 +100,8 @@ function wrapSingle(property) {
     position: 0,
     shorthand: false,
     unused: property.length < 2,
-    value: property.slice(1)
+    value: property.slice(1),
+    variable: isVariable
   };
 }
 
index df62e0e..497198b 100644 (file)
@@ -25,6 +25,9 @@ function extract(token) {
       if (name.length === 0)
         continue;
 
+      if (name.indexOf('--') === 0)
+        continue;
+
       var value = stringifyValue(token[2], i);
 
       properties.push([
index da586ab..3e27e9d 100644 (file)
@@ -311,6 +311,12 @@ function optimizeBody(properties, options) {
     if (property.unused)
       continue;
 
+    if (property.variable) {
+      if (property.block)
+        optimizeBody(property.value[0], options);
+      continue;
+    }
+
     for (var j = 0, m = property.value.length; j < m; j++) {
       value = property.value[j][0];
 
index 1411619..32b331e 100644 (file)
@@ -95,6 +95,14 @@ function propertyAtRule(value, isLast, context) {
 function value(tokens, position, isLast, context) {
   var store = context.store;
   var token = tokens[position];
+  var isVariableDeclaration = token[0][0].indexOf('--') === 0;
+
+  if (isVariableDeclaration && Array.isArray(token[1][0][0])) {
+    store('{', context);
+    body(token[1], context);
+    store('};', context);
+    return;
+  }
 
   for (var j = 1, m = token.length; j < m; j++) {
     store(token[j], context);
index 2eca69c..1805a84 100644 (file)
@@ -57,7 +57,7 @@ function extractProperties(string, selectors, context) {
   if (string.indexOf('ESCAPED_URL_CLEAN_CSS') > -1)
     string = string.replace(/(ESCAPED_URL_CLEAN_CSS[^_]+?__)/g, context.sourceMap ? '$1 __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__ ' : '$1 ');
 
-  var candidates = string.split(';');
+  var candidates = split(string, ';', false, '{', '}');
 
   for (var i = 0, l = candidates.length; i < l; i++) {
     var candidate = candidates[i];
@@ -77,7 +77,7 @@ function extractProperties(string, selectors, context) {
       continue;
     }
 
-    if (candidate.indexOf('{') > 0) {
+    if (candidate.indexOf('{') > 0 && candidate.indexOf('{') < firstColonAt) {
       context.track(candidate);
       continue;
     }
@@ -98,6 +98,22 @@ function extractProperties(string, selectors, context) {
 
     trackComments(innerComments, list, context);
 
+    var firstBraceAt = candidate.indexOf('{');
+    var isVariable = name.trim().indexOf('--') === 0;
+    if (isVariable && firstBraceAt > 0) {
+      var blockPrefix = candidate.substring(firstColonAt + 1, firstBraceAt + 1);
+      var blockSuffix = candidate.substring(candidate.indexOf('}'));
+      var blockContent = candidate.substring(firstBraceAt + 1, candidate.length - blockSuffix.length);
+
+      context.track(blockPrefix);
+      body.push(extractProperties(blockContent, selectors, context));
+      list.push(body);
+      context.track(blockSuffix);
+      context.track(i < l - 1 ? ';' : '');
+
+      continue;
+    }
+
     var values = split(candidate.substring(firstColonAt + 1), valueSeparator, true);
 
     if (values.length == 1 && values[0] === '') {
index 3061dea..449a3cb 100644 (file)
@@ -1,6 +1,7 @@
 var extractProperties = require('./extract-properties');
 var extractSelectors = require('./extract-selectors');
 var track = require('../source-maps/track');
+var split = require('../utils/split');
 
 var path = require('path');
 
@@ -65,10 +66,14 @@ function whatsNext(context) {
   }
 
   if (mode == 'body') {
-    closest = chunk.indexOf('}', context.cursor);
-    return closest > -1 ?
-      [closest, 'bodyEnd'] :
-      null;
+    if (chunk[context.cursor] == '}')
+      return [context.cursor, 'bodyEnd'];
+
+    if (chunk.indexOf('}', context.cursor) == -1)
+      return null;
+
+    closest = context.cursor + split(chunk.substring(context.cursor - 1), '}', true, '{', '}')[0].length - 2;
+    return [closest, 'bodyEnd'];
   }
 
   var nextSpecial = chunk.indexOf('@', context.cursor);
index b6ec0b5..598c921 100644 (file)
@@ -1635,7 +1635,7 @@ vows.describe('integration tests')
   )
   .addBatch(
     optimizerContext('IE 7 hacks', {
-      'keeps !ie hack 123': [
+      'keeps !ie hack': [
         'a{list-style-type:none;list-style-type:decimal !ie}',
         'a{list-style-type:none;list-style-type:decimal !ie}'
       ]
@@ -2571,6 +2571,22 @@ vows.describe('integration tests')
       'all values': [
         'a{--width:1px;--style:solid;--color:#000}.one{border:var(--width)var(--style)var(--color)}',
         'a{--width:1px;--style:solid;--color:#000}.one{border:var(--width)var(--style)var(--color)}'
+      ],
+      'Polymer mixins - simple optimizations': [
+        'a{ display:block; --my-toolbar: { color:#f00; width:96px }; color:blue}',
+        'a{display:block;--my-toolbar:{color:red;width:1in};color:#00f}'
+      ],
+      'Polymer mixins - override optimizations': [
+        'a{--my-toolbar: { margin:15px!important; margin:10px};}',
+        'a{--my-toolbar:{margin:15px!important};}'
+      ],
+      'Polymer mixins - shorthand optimizations': [
+        'a{--my-toolbar: { margin:10px; margin-top:10px };}',
+        'a{--my-toolbar:{margin:10px};}'
+      ],
+      'Polymer mixins - not fitting into a single chunk of 128 bytes': [
+        ':host{--live-head-theme: { line-height: 40px !important; vertical-align: middle; background: transparent; height: 40px; z-index: 999; }; }',
+        ':host{--live-head-theme:{line-height:40px!important;vertical-align:middle;background:0 0;height:40px;z-index:999};}'
       ]
     })
   )
index 67a05c9..8c699d2 100644 (file)
@@ -18,6 +18,9 @@ vows.describe(wrapForOptimizing)
       'has value': function (wrapped) {
         assert.deepEqual(wrapped[0].value, [['0'], ['0']]);
       },
+      'is not a block': function (wrapped) {
+        assert.isFalse(wrapped[0].block);
+      },
       'has no components': function (wrapped) {
         assert.lengthOf(wrapped[0].components, 0);
       },
@@ -76,6 +79,46 @@ vows.describe(wrapForOptimizing)
         assert.isTrue(wrapped[0].multiplex);
       }
     },
+    'variable': {
+      'topic': function () {
+        return wrapForOptimizing([[['--color'], ['red']]]);
+      },
+      'has one wrap': function (wrapped) {
+        assert.lengthOf(wrapped, 1);
+      },
+      'has name': function (wrapped) {
+        assert.deepEqual(wrapped[0].name, '--color');
+      },
+      'has value': function (wrapped) {
+        assert.deepEqual(wrapped[0].value, [['red']]);
+      },
+      'is not a block': function (wrapped) {
+        assert.isFalse(wrapped[0].block);
+      },
+      'is variable': function (wrapped) {
+        assert.isTrue(wrapped[0].variable);
+      }
+    },
+    'variable block': {
+      'topic': function () {
+        return wrapForOptimizing([[['--color'], [ [['color'], ['red']], [['text-color'], ['red']] ]]]);
+      },
+      'has one wrap': function (wrapped) {
+        assert.lengthOf(wrapped, 1);
+      },
+      'has name': function (wrapped) {
+        assert.deepEqual(wrapped[0].name, '--color');
+      },
+      'has value': function (wrapped) {
+        assert.deepEqual(wrapped[0].value, [[ [['color'], ['red']], [['text-color'], ['red']] ]]);
+      },
+      'is not a block': function (wrapped) {
+        assert.isTrue(wrapped[0].block);
+      },
+      'is variable': function (wrapped) {
+        assert.isTrue(wrapped[0].variable);
+      }
+    },
     'without value': {
       'topic': function () {
         return wrapForOptimizing([[['margin']]]);
index 09e7459..6d2fd88 100644 (file)
@@ -39,6 +39,18 @@ vows.describe(extractor)
         assert.deepEqual(tokens, [['color', 'red', 'color', [['color'], ['red']], 'color:red', [['#one span']], true]]);
       }
     },
+    'one property - variable': {
+      'topic': extractor(buildToken('#one span{--color:red}')),
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, []);
+      }
+    },
+    'one property - block variable': {
+      'topic': extractor(buildToken('#one span{--color:{color:red;display:block};}')),
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, []);
+      }
+    },
     'one property - complex selector': {
       'topic': extractor(buildToken('.one{color:red}')),
       'has no properties': function (tokens) {
index 97c2304..0fd62f3 100644 (file)
@@ -358,6 +358,19 @@ vows.describe('source-maps/analyzer')
             ]
           ]
         ]
+      ],
+      'variable blocks': [
+        '\ndiv {\n--test1:{\n color:red}; --test2:{ color: blue };}',
+        [
+          [
+            'selector',
+            [['div', [[2, 0, undefined]]]],
+            [
+              [['--test1', [[3, 0, undefined]]], [[['color', [[4, 1, undefined]]], ['red', [[4, 7, undefined]] ]]]],
+              [['--test2', [[4, 13, undefined]]], [[['color', [[4, 23, undefined]]], ['blue', [[4, 30, undefined]] ]]]]
+            ]
+          ]
+        ]
       ]
     })
   )
index ca44bc0..a43e6b9 100644 (file)
@@ -289,6 +289,43 @@ vows.describe(tokenize)
       ]
     })
   )
+  .addBatch(
+    tokenizerContext('Polymer mixins', {
+      'flat value': [
+        'a{--my-toolbar-color:red}',
+        [
+          ['selector', [['a']], [[['--my-toolbar-color'], ['red']]]]
+        ]
+      ],
+      'block value': [
+        'a{--my-toolbar:{color:red;width:100%}}',
+        [
+          [
+            'selector',
+            [['a']],
+            [[['--my-toolbar'], [
+              [['color'], ['red']],
+              [['width'], ['100%']]
+            ]]]
+          ]
+        ]
+      ],
+      'mixed block value': [
+        'a{display:block;--my-toolbar:{color:red;width:100%};color:blue}',
+        [
+          [
+            'selector',
+            [['a']],
+            [
+              [['display'], ['block']],
+              [['--my-toolbar'], [[['color'], ['red']], [['width'], ['100%']]]],
+              [['color'], ['blue']]
+            ]
+          ]
+        ]
+      ]
+    })
+  )
   .addBatch(
     tokenizerContext('multiple values', {
       'comma - no spaces': [