Adds more advanced tokenizing.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 21 Oct 2014 08:16:06 +0000 (09:16 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 8 Dec 2014 09:39:14 +0000 (09:39 +0000)
* We'll be able to hold more information about tokens now.

lib/properties/optimizer.js
lib/properties/processable.js
lib/properties/token.js
lib/selectors/optimizer.js
lib/selectors/optimizers/advanced.js
lib/selectors/optimizers/clean-up.js
lib/selectors/optimizers/simple.js
lib/selectors/tokenizer.js
test/selectors/optimizers/simple-test.js
test/selectors/tokenizer-test.js

index 8360992..7f61761 100644 (file)
@@ -3,6 +3,8 @@ var processableInfo = require('./processable');
 var overrideCompactor = require('./override-compactor');
 var shorthandCompactor = require('./shorthand-compactor');
 
+function valueMapper(object) { return object.value; }
+
 module.exports = function Optimizer(compatibility, aggressiveMerging, context) {
   var overridable = {
     'animation-delay': ['animation'],
@@ -121,13 +123,13 @@ module.exports = function Optimizer(compatibility, aggressiveMerging, context) {
     var keyValues = [];
 
     for (var i = 0, l = body.length; i < l; i++) {
-      var token = body[i];
+      var token = body[i].value;
 
       var firstColon = token.indexOf(':');
       var property = token.substring(0, firstColon);
       var value = token.substring(firstColon + 1);
       if (value === '') {
-        context.warnings.push('Empty property \'' + property + '\' inside \'' + selector + '\' selector. Ignoring.');
+        context.warnings.push('Empty property \'' + property + '\' inside \'' + selector.map(valueMapper).join(',') + '\' selector. Ignoring.');
         continue;
       }
 
@@ -236,12 +238,19 @@ module.exports = function Optimizer(compatibility, aggressiveMerging, context) {
 
   var rebuild = function(tokens) {
     var flat = [];
+    var eligibleForCompacting = false;
 
     for (var i = 0, l = tokens.length; i < l; i++) {
-      flat.push(tokens[i][0] + ':' + tokens[i][1]);
+      if (!eligibleForCompacting && processableInfo.implementedFor.test(tokens[i][0]))
+        eligibleForCompacting = true;
+
+      flat.push({ value: tokens[i][0] + ':' + tokens[i][1] });
     }
 
-    return flat;
+    return {
+      value: flat,
+      compactFurther: eligibleForCompacting
+    };
   };
 
   var compact = function (input) {
@@ -262,9 +271,9 @@ module.exports = function Optimizer(compatibility, aggressiveMerging, context) {
       var optimized = optimize(tokenized, allowAdjacent);
       var rebuilt = rebuild(optimized);
 
-      return compactProperties && processableInfo.implementedFor.test(rebuilt) ?
-        compact(rebuilt) :
-        rebuilt;
+      return compactProperties && rebuilt.compactFurther ?
+        compact(rebuilt.value) :
+        rebuilt.value;
     }
   };
 };
index bc6358c..913108a 100644 (file)
@@ -90,8 +90,8 @@ module.exports = (function () {
       return canOverride.sameFunctionOrValue(val1, val2);
     },
     border: function(val1, val2) {
-      var brokenUp1 = breakUp.border(Token.tokenizeOne(val1));
-      var brokenUp2 = breakUp.border(Token.tokenizeOne(val2));
+      var brokenUp1 = breakUp.border(Token.tokenizeOne({ value: val1 }));
+      var brokenUp2 = breakUp.border(Token.tokenizeOne({ value: val2 }));
 
       return canOverride.color(brokenUp1[2].value, brokenUp2[2].value);
     }
index ff4e514..bb4b8c7 100644 (file)
@@ -63,16 +63,16 @@ module.exports = (function() {
     // Parses one CSS property declaration into a token
     Token.tokenizeOne = function (fullProp) {
       // Find first colon
-      var colonPos = fullProp.indexOf(':');
+      var colonPos = fullProp.value.indexOf(':');
 
       if (colonPos < 0) {
         // This property doesn't have a colon, it's invalid. Let's keep it intact anyway.
-        return new Token('', fullProp);
+        return new Token('', fullProp.value);
       }
 
       // Parse parts of the property
-      var prop = fullProp.substr(0, colonPos).trim();
-      var value = fullProp.substr(colonPos + 1).trim();
+      var prop = fullProp.value.substr(0, colonPos).trim();
+      var value = fullProp.value.substr(colonPos + 1).trim();
       var isImportant = false;
       var importantPos = value.indexOf(important);
 
@@ -124,7 +124,7 @@ module.exports = (function() {
           continue;
         }
 
-        result.push(t.prop + ':' + t.value + (t.isImportant ? important : ''));
+        result.push({ value: t.prop + ':' + t.value + (t.isImportant ? important : '') });
       }
 
       return result;
index e33c87b..129c3a4 100644 (file)
@@ -9,28 +9,36 @@ function SelectorsOptimizer(options, context) {
   this.context = context || {};
 }
 
+function valueMapper (object) { return object.value; }
+
 function rebuild(tokens, keepBreaks, isFlatBlock) {
   var joinCharacter = isFlatBlock ? ';' : (keepBreaks ? lineBreak : '');
   var parts = [];
+  var body;
+  var selector;
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (typeof token === 'string') {
-      parts.push(token);
+    if (token.kind === 'text') {
+      parts.push(token.value);
       continue;
     }
 
     // TODO: broken due to joining/splitting
-    if (token.body.length === 0 || (token.body.length == 1 && token.body[0] === ''))
+    if (token.body && (token.body.length === 0 || (token.body.length == 1 && token.body[0].value === '')))
       continue;
 
-    if (token.block) {
-      var body = rebuild(token.body, keepBreaks, token.isFlatBlock);
+    if (token.kind == 'block') {
+      body = token.isFlatBlock ?
+        token.body.map(valueMapper).join(';') :
+        rebuild(token.body, keepBreaks, token.isFlatBlock);
       if (body.length > 0)
-        parts.push(token.block + '{' + body + '}');
+        parts.push(token.value + '{' + body + '}');
     } else {
-      parts.push(token.selector.join(',') + '{' + token.body.join(';') + '}');
+      selector = token.value.map(valueMapper).join(',');
+      body = token.body.map(valueMapper).join(';');
+      parts.push(selector + '{' + body + '}');
     }
   }
 
index cb3d351..142b1ed 100644 (file)
@@ -8,6 +8,8 @@ function AdvancedOptimizer(options, context) {
   this.propertyOptimizer = new PropertyOptimizer(this.options.compatibility, this.options.aggressiveMerging, context);
 }
 
+function valueMapper(object) { return object.value; }
+
 AdvancedOptimizer.prototype.isSpecial = function (selector) {
   return this.options.compatibility.selectors.special.test(selector);
 };
@@ -18,10 +20,10 @@ AdvancedOptimizer.prototype.removeDuplicates = function (tokens) {
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
-    if (typeof token == 'string' || token.block)
+    if (token.kind != 'selector')
       continue;
 
-    var id = token.body.join(';') + '@' + token.selector.join(',');
+    var id = token.body.map(valueMapper).join(';') + '@' + token.value.map(valueMapper).join(',');
     var alreadyMatched = matched[id];
 
     if (alreadyMatched) {
@@ -50,17 +52,17 @@ AdvancedOptimizer.prototype.mergeAdjacent = function (tokens) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (typeof token == 'string' || token.block)
+    if (token.kind != 'selector')
       continue;
 
     // TODO: broken due to joining/splitting
-    if (lastToken.selector && token.selector.join(',') == lastToken.selector.join(',')) {
+    if (lastToken.kind == 'selector' && token.value.map(valueMapper).join(',') == lastToken.value.map(valueMapper).join(',')) {
       var joinAt = [lastToken.body.length];
-      lastToken.body = this.propertyOptimizer.process(token.selector, lastToken.body.concat(token.body), joinAt, true);
+      lastToken.body = this.propertyOptimizer.process(token.value, lastToken.body.concat(token.body), joinAt, true);
       forRemoval.push(i);
       // TODO: broken due to joining/splitting
-    } else if (lastToken.body && token.body.join(';') == lastToken.body.join(';') && !this.isSpecial(token.selector.join(',')) && !this.isSpecial(lastToken.selector.join(','), this.options)) {
-      lastToken.selector = CleanUp.selectors(lastToken.selector.concat(token.selector));
+    } else if (lastToken.body && token.body.map(valueMapper).join(';') == lastToken.body.map(valueMapper).join(';') && !this.isSpecial(token.value.map(valueMapper).join(',')) && !this.isSpecial(lastToken.value.map(valueMapper).join(','), this.options)) {
+      lastToken.value = CleanUp.selectors(lastToken.value.concat(token.value));
       forRemoval.push(i);
     } else {
       lastToken = token;
@@ -81,13 +83,13 @@ AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) {
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
 
-    if (typeof token == 'string' || token.block)
+    if (token.kind != 'selector')
       continue;
 
-    var complexSelector = token.selector;
-    var selectors = complexSelector.length > 1 && !this.isSpecial(complexSelector.join(','), this.options) ?
-      [complexSelector.join(',')].concat(complexSelector) :
-      [complexSelector.join(',')];
+    var complexSelector = token.value;
+    var selectors = complexSelector.length > 1 && !this.isSpecial(complexSelector.map(valueMapper).join(','), this.options) ?
+      [complexSelector.map(valueMapper).join(',')].concat(complexSelector.map(valueMapper)) :
+      [complexSelector.map(valueMapper).join(',')];
 
     for (var j = 0, m = selectors.length; j < m; j++) {
       var selector = selectors[j];
@@ -100,7 +102,7 @@ AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) {
       // TODO: broken due to joining/splitting
       candidates[selector].push({
         where: i,
-        partial: selector != complexSelector.join(',')
+        partial: selector != complexSelector.map(valueMapper).join(',')
       });
     }
   }
@@ -170,7 +172,7 @@ AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, po
         },
         callback: function (token, newBody, processedCount, tokenIdx) {
           if (tokenIdx === 0)
-            reducedBodies.push(newBody.join(';'));
+            reducedBodies.push(newBody.map(valueMapper).join(';'));
         }
       });
 
@@ -178,7 +180,9 @@ AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, po
         continue allSelectors;
     }
 
-    intoToken.body = reducedBodies[0].split(';');
+    intoToken.body = reducedBodies[0].split(';').map(function (property) {
+      return { value: property };
+    });
     reduced = true;
   }
 
@@ -200,7 +204,7 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o
     var body = token.body;
 
     bodies = bodies.concat(body);
-    splitBodies.push(body);
+    splitBodies.push(body.map(valueMapper));
     processedTokens.push(where);
   }
 
@@ -217,7 +221,7 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o
   var tokenIdx = processedCount - 1;
 
   while (tokenIdx >= 0) {
-    if ((tokenIdx === 0 || splitBodies[tokenIdx].indexOf(optimizedProperties[propertyIdx]) > -1) && propertyIdx > -1) {
+     if ((tokenIdx === 0 || (optimizedProperties[propertyIdx] && splitBodies[tokenIdx].indexOf(optimizedProperties[propertyIdx].value) > -1)) && propertyIdx > -1) {
       propertyIdx--;
       continue;
     }
@@ -233,9 +237,9 @@ function optimizeProperties(tokens, propertyOptimizer) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (token.selector) {
-      token.body = propertyOptimizer.process(token.selector, token.body, false, true);
-    } else if (token.block) {
+    if (token.kind == 'selector') {
+      token.body = propertyOptimizer.process(token.value, token.body, false, true);
+    } else if (token.kind == 'block') {
       optimizeProperties(token.body, propertyOptimizer);
     }
   }
@@ -246,7 +250,7 @@ AdvancedOptimizer.prototype.optimize = function (tokens) {
 
   function _optimize(tokens) {
     tokens.forEach(function (token) {
-      if (token.block)
+      if (token.kind == 'block')
         _optimize(token.body);
     });
 
index d1a8771..dbe5fb0 100644 (file)
@@ -7,7 +7,7 @@ var CleanUp = {
     var plain = [];
 
     for (var i = 0, l = selectors.length; i < l; i++) {
-      var selector = selectors[i];
+      var selector = selectors[i].value;
       var reduced = selector.replace(/\s*([>\+\~])\s*/g, '$1');
 
       if (selector.indexOf('*') > -1) {
@@ -23,7 +23,9 @@ var CleanUp = {
         plain.push(reduced);
     }
 
-    return plain.sort();
+    return plain.sort().map(function (selector) {
+      return { value: selector };
+    });
   },
 
   block: function (block) {
index 8fd988e..28e3320 100644 (file)
@@ -30,14 +30,14 @@ function removeUnsupported(token, compatibility) {
     return;
 
   var supported = [];
-  for (var i = 0, l = token.selector.length; i < l; i++) {
-    var selector = token.selector[i];
+  for (var i = 0, l = token.value.length; i < l; i++) {
+    var selector = token.value[i];
 
-    if (selector.indexOf('*+html ') === -1 && selector.indexOf('*:first-child+html ') === -1)
+    if (selector.value.indexOf('*+html ') === -1 && selector.value.indexOf('*:first-child+html ') === -1)
       supported.push(selector);
   }
 
-  token.selector = supported;
+  token.value = supported;
 }
 
 var valueMinifiers = {
@@ -173,7 +173,7 @@ function reduce(body, options) {
   var reduced = [];
 
   for (var i = 0, l = body.length; i < l; i++) {
-    var token = body[i];
+    var token = body[i].value;
     var firstColon = token.indexOf(':');
     var property = token.substring(0, firstColon);
     var value = token.substring(firstColon + 1);
@@ -199,7 +199,7 @@ function reduce(body, options) {
     value = multipleZerosMinifier(property, value);
     value = colorMininifier(property, value, options.compatibility);
 
-    reduced.push(property + ':' + value + (important ? '!important' : ''));
+    reduced.push({ value: property + ':' + value + (important ? '!important' : '') });
   }
 
   return reduced;
@@ -216,32 +216,32 @@ SimpleOptimizer.prototype.optimize = function(tokens) {
       if (!token)
         break;
 
-      if (token.selector) {
-        token.selector = CleanUp.selectors(token.selector);
+      if (token.kind == 'selector') {
+        token.value = CleanUp.selectors(token.value);
 
         removeUnsupported(token, self.options.compatibility);
-        if (token.selector.length === 0) {
+        if (token.value.length === 0) {
           tokens.splice(i, 1);
           i--;
           continue;
         }
 
         token.body = reduce(token.body, self.options);
-      } else if (token.block) {
-        token.block = CleanUp.block(token.block);
+      } else if (token.kind == 'block') {
+        token.value = CleanUp.block(token.value);
         if (token.isFlatBlock)
           token.body = reduce(token.body, self.options);
         else
           _optimize(token.body);
-      } else if (typeof token === 'string') {
-        if (CHARSET_REGEXP.test(token)) {
-          if (hasCharset || token.indexOf(CHARSET_TOKEN) == -1) {
+      } else if (token.kind == 'text') {
+        if (CHARSET_REGEXP.test(token.value)) {
+          if (hasCharset || token.value.indexOf(CHARSET_TOKEN) == -1) {
             tokens.splice(i, 1);
             i++;
           } else {
             hasCharset = true;
             tokens.splice(i, 1);
-            tokens.unshift(token.replace(CHARSET_REGEXP, CHARSET_TOKEN));
+            tokens.unshift({ kind: 'text', value: token.value.replace(CHARSET_REGEXP, CHARSET_TOKEN) });
           }
         }
       }
index b44af56..8717d31 100644 (file)
@@ -45,7 +45,7 @@ function extractProperties(string) {
     .replace(TRAILING_SEMICOLON, '');
 
   return extracted.length > 0 ?
-    extracted.split(';') :
+    extracted.split(';').map(function (property) { return { value: property }; }) :
     [];
 }
 
@@ -56,7 +56,9 @@ function extractSelectors(string) {
     .replace(WHITESPACE_COMMA, ',')
     .trim();
 
-  return new Splitter(',').split(extracted);
+  return new Splitter(',').split(extracted).map(function (selector) {
+    return { value: selector };
+  });
 }
 
 function extractBlock(string) {
@@ -123,7 +125,7 @@ function tokenize(context) {
     if (!next) {
       var whatsLeft = context.chunk.substring(context.cursor);
       if (whatsLeft.length > 0) {
-        tokenized.push(whatsLeft);
+        tokenized.push({ kind: 'text', value: whatsLeft });
         context.cursor += whatsLeft.length;
       }
       break;
@@ -148,7 +150,7 @@ function tokenize(context) {
         nextEnd = chunk.indexOf(';', nextSpecial + 1);
 
         var single = extractBlock(chunk.substring(context.cursor, nextEnd + 1));
-        tokenized.push(single);
+        tokenized.push({ kind: 'text', value: single });
 
         context.cursor = nextEnd + 1;
       } else {
@@ -166,12 +168,12 @@ function tokenize(context) {
 
         context.mode = oldMode;
 
-        tokenized.push({ block: block, body: specialBody, isFlatBlock: isFlat });
+        tokenized.push({ kind: 'block', value: block, body: specialBody, isFlatBlock: isFlat });
       }
     } else if (what == 'escape') {
       nextEnd = chunk.indexOf('__', nextSpecial + 1);
       var escaped = chunk.substring(context.cursor, nextEnd + 2);
-      tokenized.push(escaped);
+      tokenized.push({ kind: 'text', value: escaped });
 
       context.cursor = nextEnd + 2;
     } else if (what == 'bodyStart') {
@@ -184,7 +186,7 @@ function tokenize(context) {
 
       context.mode = oldMode;
 
-      tokenized.push({ selector: selector, body: body });
+      tokenized.push({ kind: 'selector', value: selector, body: body });
     } else if (what == 'bodyEnd') {
       // extra closing brace at the top level can be safely ignored
       if (context.mode == 'top') {
index f12ed64..fceefdb 100644 (file)
@@ -15,7 +15,7 @@ function selectorContext(group, specs, options) {
       var tokens = new Tokenizer().toTokens(source);
       new SimpleOptimizer(options).optimize(tokens);
 
-      assert.deepEqual(tokens[0] ? tokens[0].selector : null, selectors);
+      assert.deepEqual(tokens[0] ? tokens[0].value : null, selectors);
     };
   }
 
@@ -38,8 +38,9 @@ function propertyContext(group, specs, options) {
     return function (source) {
       var tokens = new Tokenizer().toTokens(source);
       new SimpleOptimizer(options).optimize(tokens);
+      var value = tokens[0].body.map(function (property) { return property.value; });
 
-      assert.deepEqual(tokens[0].body, selectors);
+      assert.deepEqual(value, selectors);
     };
   }
 
@@ -58,15 +59,15 @@ vows.describe(SimpleOptimizer)
     selectorContext('default', {
       'optimized': [
         'a{}',
-        ['a']
+        [{ value: 'a' }]
       ],
       'whitespace': [
         ' div  > span{}',
-        ['div>span']
+        [{ value: 'div>span' }]
       ],
       'line breaks': [
         ' div  >\n\r\n span{}',
-        ['div>span']
+        [{ value: 'div>span' }]
       ],
       '+html': [
         '*+html .foo{display:inline}',
@@ -86,7 +87,7 @@ vows.describe(SimpleOptimizer)
       ],
       '+html - complex': [
         '*+html .foo,.bar{display:inline}',
-        ['.bar']
+        [{ value: '.bar' }]
       ]
     }, { compatibility: 'ie8' })
   )
@@ -94,11 +95,11 @@ vows.describe(SimpleOptimizer)
     selectorContext('ie7', {
       '+html': [
         '*+html .foo{display:inline}',
-        ['*+html .foo']
+        [{ value: '*+html .foo' }]
       ],
       '+html - complex': [
         '*+html .foo,.bar{display:inline}',
-        ['*+html .foo', '.bar']
+        [{ value: '*+html .foo' }, { value: '.bar' }]
       ]
     }, { compatibility: 'ie7' })
   )
index 5c08c7c..cdcacdc 100644 (file)
@@ -2,7 +2,7 @@ var vows = require('vows');
 var assert = require('assert');
 var Tokenizer = require('../../lib/selectors/tokenizer');
 
-function tokenizerContext(config) {
+function tokenizerContext(name, specs) {
   var ctx = {};
 
   function tokenized(target) {
@@ -12,10 +12,10 @@ function tokenizerContext(config) {
     };
   }
 
-  for (var test in config) {
+  for (var test in specs) {
     ctx[test] = {
-      topic: config[test][0],
-      tokenized: tokenized(config[test][1])
+      topic: specs[test][0],
+      tokenized: tokenized(specs[test][1])
     };
   }
 
@@ -24,81 +24,179 @@ function tokenizerContext(config) {
 
 vows.describe(Tokenizer)
   .addBatch(
-    tokenizerContext({
+    tokenizerContext('basic', {
       'no content': [
         '',
         []
       ],
       'an escaped content': [
         '__ESCAPED_COMMENT_CLEAN_CSS0__',
-        ['__ESCAPED_COMMENT_CLEAN_CSS0__']
+        [{
+          kind: 'text',
+          value: '__ESCAPED_COMMENT_CLEAN_CSS0__'
+        }]
       ],
       'an empty selector': [
         'a{}',
-        [{ selector: ['a'], body: [] }]
+        [{
+          kind: 'selector',
+          value: [{ value: 'a' }],
+          body: []
+        }]
       ],
       'an empty selector with whitespace': [
         'a{ \n  }',
-        [{ selector: ['a'], body: [] }]
+        [{
+          kind: 'selector',
+          value: [{ value: 'a' }],
+          body: []
+        }]
       ],
       'a selector': [
         'a{color:red}',
-        [{ selector: ['a'], body: ['color:red'] }]
+        [{
+          kind: 'selector',
+          value: [{ value: 'a' }],
+          body: [{ value: 'color:red' }]
+        }]
       ],
       'a selector with whitespace': [
         'a {color:red;\n\ndisplay :  block }',
-        [{ selector: ['a'], body: ['color:red', 'display:block'] }]
+        [{
+          kind: 'selector',
+          value: [{ value: 'a' }],
+          body: [
+            { value: 'color:red' },
+            { value: 'display:block'
+          }]
+        }]
       ],
       'a selector with whitespace in functions': [
         'a{color:rgba( 255, 255, 0, 0.5  )}',
-        [{ selector: ['a'], body: ['color:rgba(255,255,0,0.5)'] }]
+        [{
+          kind: 'selector',
+          value: [{ value: 'a' }],
+          body: [{ value: 'color:rgba(255,255,0,0.5)' }]
+        }]
       ],
       'a selector with empty properties': [
         'a{color:red; ; ; ;}',
-        [{ selector: ['a'], body: ['color:red'] }]
+        [{
+          kind: 'selector',
+          value: [{ value: 'a' }],
+          body: [{ value: 'color:red' }]
+        }]
       ],
       'a selector with quoted attribute': [
         'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]{color:red}',
-        [{ selector: ['a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]'], body: ['color:red'] }]
+        [{
+          kind: 'selector',
+          value: [{ value: 'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]' }],
+          body: [{ value: 'color:red' }]
+        }]
       ],
       'a double selector': [
         'a,\n\ndiv.class > p {color:red}',
-        [{ selector: ['a', 'div.class > p'], body: ['color:red'] }]
+        [{
+          kind: 'selector',
+          value: [
+            { value: 'a' },
+            { value: 'div.class > p' }
+          ],
+          body: [{ value: 'color:red' }]
+        }]
       ],
       'two selectors': [
         'a{color:red}div{color:blue}',
         [
-          { selector: ['a'], body: ['color:red'] },
-          { selector: ['div'], body: ['color:blue'] }
+          {
+            kind: 'selector',
+            value: [{ value: 'a' }],
+            body: [{ value: 'color:red' }]
+          },
+          {
+            kind: 'selector',
+            value: [{ value: 'div' }],
+            body: [{ value: 'color:blue' }]
+          }
         ]
       ],
       'media query': [
         '@media (min-width:980px){}',
-        [{ block: '@media (min-width:980px)', body: [], isFlatBlock: false }]
+        [{
+          kind: 'block',
+          value: '@media (min-width:980px)',
+          body: [],
+          isFlatBlock: false
+        }]
       ],
       'media query with selectors': [
         '@media (min-width:980px){a{color:red}}',
-        [{ block: '@media (min-width:980px)', body: [{ selector: ['a'], body: ['color:red'] }], isFlatBlock: false }]
+        [{
+          kind: 'block',
+          value: '@media (min-width:980px)',
+          body: [{
+            kind: 'selector',
+            value: [{ value: 'a' }],
+            body: [{ value: 'color:red' }]
+          }],
+          isFlatBlock: false
+        }]
       ],
       'media query spanning more than one chunk': [
         '@media only screen and (max-width:1319px) and (min--moz-device-pixel-ratio:1.5),only screen and (max-width:1319px) and (-moz-min-device-pixel-ratio:1.5){a{color:#000}}',
-        [{ block: '@media only screen and (max-width:1319px) and (min--moz-device-pixel-ratio:1.5),only screen and (max-width:1319px) and (-moz-min-device-pixel-ratio:1.5)', body: [{ selector: ['a'], body: ['color:#000'] }], isFlatBlock: false }]
+        [{
+          kind: 'block',
+          value: '@media only screen and (max-width:1319px) and (min--moz-device-pixel-ratio:1.5),only screen and (max-width:1319px) and (-moz-min-device-pixel-ratio:1.5)',
+          body: [{
+            kind: 'selector',
+            value: [{ value: 'a' }],
+            body: [{ value: 'color:#000' }]
+          }],
+          isFlatBlock: false
+        }]
       ],
       'font-face': [
         '@font-face{font-family: fontName;font-size:12px}',
-        [{ block: '@font-face', body: ['font-family:fontName', 'font-size:12px'], isFlatBlock: true }]
+        [{
+          kind: 'block',
+          value: '@font-face',
+          body: [
+            { value: 'font-family:fontName' },
+            { value: 'font-size:12px' }
+          ],
+          isFlatBlock: true
+        }]
       ],
       'charset': [
         '@charset \'utf-8\';a{color:red}',
-        ['@charset \'utf-8\';', { selector: ['a'], body: ['color:red'] }]
+        [
+          {
+            kind: 'text',
+            value: '@charset \'utf-8\';'
+          },
+          {
+            kind: 'selector',
+            value: [{ value: 'a' }],
+            body: [{ value: 'color:red' }]
+          }
+        ]
       ],
       'charset after a line break': [
         '\n@charset \n\'utf-8\';',
-        ['@charset \'utf-8\';']
+        [{
+          kind: 'text',
+          value: '@charset \'utf-8\';'
+        }]
       ],
       'keyframes with quoted attribute': [
         '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__{}',
-        [{ block: '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__', body: [], isFlatBlock: false }]
+        [{
+          kind: 'block',
+          value: '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__',
+          body: [],
+          isFlatBlock: false
+        }]
       ]
     })
   )