Reworks tokenizer to operate on arrays.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Wed, 18 Mar 2015 20:37:01 +0000 (20:37 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 22 Mar 2015 15:57:16 +0000 (15:57 +0000)
* Uses arrays instead of hashes.
* Gets rid of cached metadata in favor to live-calculated values.
* Adds `removeEmpty` step in advanced optimizer to improve empty
  elements removal.
* Cleans up simple optimizer iteration loop.
* Cleans up source map tracking.

22 files changed:
lib/properties/extractor.js
lib/properties/optimizer.js
lib/properties/processable.js
lib/properties/reorderable.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/source-map-stringifier.js
lib/selectors/stringifier.js
lib/selectors/tokenizer.js
lib/utils/extractors.js
lib/utils/input-source-map-tracker.js
lib/utils/source-maps.js
lib/utils/stringify-tokens.js [new file with mode: 0644]
test/properties/extractor-test.js
test/properties/reorderable-test.js
test/selectors/optimizers/simple-test.js
test/selectors/tokenizer-source-maps-test.js
test/selectors/tokenizer-test.js
test/source-map-test.js

index 69156c3..ac131ac 100644 (file)
@@ -5,10 +5,11 @@
 function extract(token) {
   var properties = [];
 
-  if (token.kind == 'selector') {
-    var inSimpleSelector = !/[\.\+#>~\s]/.test(token.metadata.selector);
-    for (var i = 0, l = token.metadata.bodiesList.length; i < l; i++) {
-      var property = token.metadata.bodiesList[i];
+  if (token[0] == 'selector') {
+    // TODO: stringifySelector
+    var inSimpleSelector = !/[\.\+#>~\s]/.test(token[1].join(','));
+    for (var i = 0, l = token[2].length; i < l; i++) {
+      var property = token[2][i][0];
       if (property.indexOf('__ESCAPED') === 0)
         continue;
 
@@ -23,14 +24,14 @@ function extract(token) {
         name,
         property.substring(splitAt + 1),
         nameRoot,
-        property,
-        token.metadata.selectorsList,
+        token[2][i],
+        token[1],
         inSimpleSelector
       ]);
     }
-  } else if (token.kind == 'block') {
-    for (var j = 0, k = token.body.length; j < k; j++) {
-      properties = properties.concat(extract(token.body[j]));
+  } else if (token[0] == 'block') {
+    for (var j = 0, k = token[2].length; j < k; j++) {
+      properties = properties.concat(extract(token[2][j]));
     }
   }
 
index 4ffe916..ce891f9 100644 (file)
@@ -3,8 +3,6 @@ var processableInfo = require('./processable');
 var overrideCompactor = require('./override-compactor');
 var shorthandCompactor = require('./shorthand-compactor');
 
-function valueMapper(object) { return object.value; }
-
 module.exports = function Optimizer(options, context) {
   var overridable = {
     'animation-delay': ['animation'],
@@ -111,46 +109,46 @@ module.exports = function Optimizer(options, context) {
     }
   }
 
-  var tokenize = function(body, selector) {
-    var keyValues = [];
+  var tokenize = function(properties, selector) {
+    var tokenized = [];
 
-    for (var i = 0, l = body.length; i < l; i++) {
-      var token = body[i];
-      var firstColon = token.value.indexOf(':');
-      var property = token.value.substring(0, firstColon);
-      var value = token.value.substring(firstColon + 1);
+    for (var i = 0, l = properties.length; i < l; i++) {
+      var property = properties[i];
+      var firstColon = property[0].indexOf(':');
+      var name = property[0].substring(0, firstColon);
+      var value = property[0].substring(firstColon + 1);
       if (value === '') {
-        context.warnings.push('Empty property \'' + property + '\' inside \'' + selector.map(valueMapper).join(',') + '\' selector. Ignoring.');
+        context.warnings.push('Empty property \'' + name + '\' inside \'' + selector.join(',') + '\' selector. Ignoring.');
         continue;
       }
 
-      keyValues.push([
-        property,
+      tokenized.push([
+        name,
         value,
-        token.value.indexOf('!important') > -1,
-        token.value.indexOf(IE_BACKSLASH_HACK, firstColon + 1) === token.value.length - IE_BACKSLASH_HACK.length,
-        token.metadata
+        value.indexOf('!important') > -1,
+        property[0].indexOf(IE_BACKSLASH_HACK, firstColon + 1) === property[0].length - IE_BACKSLASH_HACK.length,
+        property.slice(1)
       ]);
     }
 
-    return keyValues;
+    return tokenized;
   };
 
-  var optimize = function(tokens, allowAdjacent) {
+  var optimize = function(properties, allowAdjacent) {
     var merged = [];
-    var properties = [];
-    var lastProperty = null;
+    var names = [];
+    var lastName = null;
     var rescanTrigger = {};
 
     var removeOverridenBy = function(property, isImportant) {
       var overrided = overrides[property];
       for (var i = 0, l = overrided.length; i < l; i++) {
-        for (var j = 0; j < properties.length; j++) {
-          if (properties[j] != overrided[i] || (merged[j][2] && !isImportant))
+        for (var j = 0; j < names.length; j++) {
+          if (names[j] != overrided[i] || (merged[j][2] && !isImportant))
             continue;
 
           merged.splice(j, 1);
-          properties.splice(j, 1);
+          names.splice(j, 1);
           j -= 1;
         }
       }
@@ -163,16 +161,16 @@ module.exports = function Optimizer(options, context) {
       return allowAdjacent.indexOf(position) > -1;
     };
 
-    tokensLoop:
-    for (var i = 0, l = tokens.length; i < l; i++) {
-      var token = tokens[i];
-      var property = token[0];
-      var value = token[1];
-      var isImportant = token[2];
-      var isIEHack = token[3];
-      var _property = (property == '-ms-filter' || property == 'filter') ?
-        (lastProperty == 'background' || lastProperty == 'background-image' ? lastProperty : property) :
-        property;
+    propertiesLoop:
+    for (var i = 0, l = properties.length; i < l; i++) {
+      var property = properties[i];
+      var name = property[0];
+      var value = property[1];
+      var isImportant = property[2];
+      var isIEHack = property[3];
+      var _name = (name == '-ms-filter' || name == 'filter') ?
+        (lastName == 'background' || lastName == 'background-image' ? lastName : name) :
+        name;
       var toOverridePosition = 0;
 
       if (isIEHack && !compatibility.properties.ieSuffixHack)
@@ -183,9 +181,9 @@ module.exports = function Optimizer(options, context) {
       // e.g. a{display:inline-block;display:-moz-inline-box}
       // however if `mergeablePosition` yields true then the rule does not apply
       // (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
-      if (aggressiveMerging && property !== '' && _property != lastProperty || mergeablePosition(i)) {
+      if (aggressiveMerging && name !== '' && _name != lastName || mergeablePosition(i)) {
         while (true) {
-          toOverridePosition = properties.indexOf(_property, toOverridePosition);
+          toOverridePosition = names.indexOf(_name, toOverridePosition);
           if (toOverridePosition == -1)
             break;
 
@@ -194,61 +192,60 @@ module.exports = function Optimizer(options, context) {
           var wasIEHack = lastToken[3];
 
           if (wasImportant && !isImportant)
-            continue tokensLoop;
+            continue propertiesLoop;
 
           if (compatibility.properties.ieSuffixHack && !wasIEHack && isIEHack)
             break;
 
-          var _info = processable[_property];
-          if (!isIEHack && !wasIEHack && _info && _info.canOverride && !_info.canOverride(tokens[toOverridePosition][1], value))
+          var _info = processable[_name];
+          if (!isIEHack && !wasIEHack && _info && _info.canOverride && !_info.canOverride(properties[toOverridePosition][1], value))
             break;
 
           merged.splice(toOverridePosition, 1);
-          properties.splice(toOverridePosition, 1);
+          names.splice(toOverridePosition, 1);
         }
       }
 
-      merged.push(token);
-      properties.push(_property);
+      merged.push(property);
+      names.push(_name);
 
       // certain properties (see values of `overridable`) should trigger removal of
       // more granular properties (see keys of `overridable`)
-      if (rescanTrigger[_property])
-        removeOverridenBy(_property, isImportant);
+      if (rescanTrigger[_name])
+        removeOverridenBy(_name, isImportant);
 
       // add rescan triggers - if certain property appears later in the list a rescan needs
       // to be triggered, e.g 'border-top' triggers a rescan after 'border-top-width' and
       // 'border-top-color' as they can be removed
-      for (var j = 0, list = overridable[_property] || [], m = list.length; j < m; j++)
+      for (var j = 0, list = overridable[_name] || [], m = list.length; j < m; j++)
         rescanTrigger[list[j]] = true;
 
-      lastProperty = _property;
+      lastName = _name;
     }
 
     return merged;
   };
 
-  var rebuild = function(tokens) {
-    var tokenized = [];
-    var list = [];
+  var rebuild = function(properties) {
+    var rebuilt = [];
     var eligibleForCompacting = false;
 
-    for (var i = 0, l = tokens.length; i < l; i++) {
-      if (!eligibleForCompacting && processableInfo.implementedFor.test(tokens[i][0]))
+    for (var i = 0, l = properties.length; i < l; i++) {
+      if (!eligibleForCompacting && processableInfo.implementedFor.test(properties[i][0]))
         eligibleForCompacting = true;
 
       // FIXME: the check should be gone with #407
-      var property = !tokens[i][0] && tokens[i][1].indexOf('__ESCAPED_') === 0 ?
-        tokens[i][1] :
-        tokens[i][0] + ':' + tokens[i][1];
-      tokenized.push({ value: property, metadata: tokens[i][4] });
-      list.push(property);
+      var property = !properties[i][0] && properties[i][1].indexOf('__ESCAPED_') === 0 ?
+        properties[i][1] :
+        properties[i][0] + ':' + properties[i][1];
+      var metadata = properties[i].pop();
+
+      rebuilt.push([property].concat(metadata));
     }
 
     return {
       compactFurther: eligibleForCompacting,
-      list: list,
-      tokenized: tokenized
+      list: rebuilt
     };
   };
 
@@ -265,14 +262,14 @@ module.exports = function Optimizer(options, context) {
   };
 
   return {
-    process: function(selector, body, allowAdjacent, compactProperties) {
-      var tokenized = tokenize(body, selector);
+    process: function(selector, properties, allowAdjacent, compactProperties) {
+      var tokenized = tokenize(properties, selector);
       var optimized = optimize(tokenized, allowAdjacent);
       var rebuilt = rebuild(optimized);
 
       return shorthandCompacting && compactProperties && rebuilt.compactFurther ?
-        compact(rebuilt.tokenized) :
-        rebuilt;
+        compact(rebuilt.list) :
+        rebuilt.list;
     }
   };
 };
index ea30c79..60dc250 100644 (file)
@@ -90,8 +90,8 @@ module.exports = (function () {
       return canOverride.sameFunctionOrValue(val1, val2);
     },
     border: function(val1, val2) {
-      var brokenUp1 = breakUp.border(Token.tokenizeOne({ value: val1 }));
-      var brokenUp2 = breakUp.border(Token.tokenizeOne({ value: val2 }));
+      var brokenUp1 = breakUp.border(Token.tokenizeOne([val1]));
+      var brokenUp2 = breakUp.border(Token.tokenizeOne([val2]));
 
       return canOverride.color(brokenUp1[2].value, brokenUp2[2].value);
     }
index 7b53cb9..902d690 100644 (file)
@@ -43,8 +43,10 @@ function canReorderSingle(left, right) {
 
 function selectorsDoNotOverlap(s1, s2) {
   for (var i = 0, l = s1.length; i < l; i++) {
-    if (s2.indexOf(s1[i]) > -1)
-      return false;
+    for (var j = 0, m = s2.length; j < m; j++) {
+      if (s1[i][0] == s2[j][0])
+        return false;
+    }
   }
 
   return true;
index 13c5b7b..2d030e3 100644 (file)
@@ -64,16 +64,16 @@ module.exports = (function() {
     // Parses one CSS property declaration into a token
     Token.tokenizeOne = function (fullProp) {
       // Find first colon
-      var colonPos = fullProp.value.indexOf(':');
+      var colonPos = fullProp[0].indexOf(':');
 
       if (colonPos < 0) {
         // This property doesn't have a colon, it's invalid. Let's keep it intact anyway.
-        return new Token('', fullProp.value);
+        return new Token('', fullProp[0]);
       }
 
       // Parse parts of the property
-      var prop = fullProp.value.substr(0, colonPos).trim();
-      var value = fullProp.value.substr(colonPos + 1).trim();
+      var prop = fullProp[0].substr(0, colonPos).trim();
+      var value = fullProp[0].substr(colonPos + 1).trim();
       var isImportant = false;
       var importantPos = value.indexOf(important);
 
@@ -113,7 +113,6 @@ module.exports = (function() {
         tokens = [tokens];
       }
 
-      var tokenized = [];
       var list = [];
 
       // This step takes care of putting together the components of shorthands
@@ -136,14 +135,10 @@ module.exports = (function() {
         // FIXME: to be fixed with #429
         property = property.replace(/\) /g, ')');
 
-        tokenized.push({ value: property, metadata: t.metadata || {} });
-        list.push(property);
+        list.push([property]);
       }
 
-      return {
-        list: list,
-        tokenized: tokenized
-      };
+      return list;
     };
 
     // Gets the final (detokenized) length of the given tokens
index 051d5e9..b2a46fa 100644 (file)
@@ -8,7 +8,7 @@ function SelectorsOptimizer(options, context) {
 }
 
 SelectorsOptimizer.prototype.process = function (data, stringifier) {
-  var tokens = new Tokenizer(this.context, this.options.advanced, this.options.sourceMap).toTokens(data);
+  var tokens = new Tokenizer(this.context, this.options.sourceMap).toTokens(data);
 
   new SimpleOptimizer(this.options).optimize(tokens);
   if (this.options.advanced)
index 744b3c3..af9133e 100644 (file)
@@ -4,25 +4,14 @@ var CleanUp = require('./clean-up');
 var extractProperties = require('../../properties/extractor');
 var canReorder = require('../../properties/reorderable').canReorder;
 var canReorderSingle = require('../../properties/reorderable').canReorderSingle;
+var stringifyBody = require('../../utils/stringify-tokens').body;
+var stringifySelector = require('../../utils/stringify-tokens').selector;
 
 function AdvancedOptimizer(options, context) {
   this.options = options;
-  this.minificationsMade = [];
   this.propertyOptimizer = new PropertyOptimizer(this.options, context);
 }
 
-function changeBodyOf(token, newBody) {
-  token.body = newBody.tokenized;
-  token.metadata.body = newBody.list.join(';');
-  token.metadata.bodiesList = newBody.list;
-}
-
-function changeSelectorOf(token, newSelectors) {
-  token.value = newSelectors.tokenized;
-  token.metadata.selector = newSelectors.list.join(',');
-  token.metadata.selectorsList = newSelectors.list;
-}
-
 function unsafeSelector(value) {
   return /\.|\*| :/.test(value);
 }
@@ -41,10 +30,10 @@ AdvancedOptimizer.prototype.removeDuplicates = function (tokens) {
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
-    if (token.kind != 'selector')
+    if (token[0] != 'selector')
       continue;
 
-    var id = token.metadata.body + '@' + token.metadata.selector;
+    var id = stringifyBody(token[2]) + '@' + stringifySelector(token[1]);
     var alreadyMatched = matched[id];
 
     if (alreadyMatched) {
@@ -62,36 +51,28 @@ AdvancedOptimizer.prototype.removeDuplicates = function (tokens) {
   for (var j = 0, n = forRemoval.length; j < n; j++) {
     tokens.splice(forRemoval[j] - j, 1);
   }
-
-  this.minificationsMade.unshift(forRemoval.length > 0);
 };
 
 AdvancedOptimizer.prototype.mergeAdjacent = function (tokens) {
   var forRemoval = [];
-  var lastToken = { selector: null, body: null };
+  var lastToken = [null, [], []];
   var adjacentSpace = this.options.compatibility.selectors.adjacentSpace;
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (token.kind != 'selector') {
-      lastToken = { selector: null, body: null };
+    if (token[0] != 'selector') {
+      lastToken = [null, [], []];
       continue;
     }
 
-    if (lastToken.kind == 'selector' && token.metadata.selector == lastToken.metadata.selector) {
-      var joinAt = [lastToken.body.length];
-      changeBodyOf(
-        lastToken,
-        this.propertyOptimizer.process(token.value, lastToken.body.concat(token.body), joinAt, true)
-      );
+    if (lastToken[0] == 'selector' && stringifySelector(token[1]) == stringifySelector(lastToken[1])) {
+      var joinAt = [lastToken[2].length];
+      lastToken[2] = this.propertyOptimizer.process(token[1], lastToken[2].concat(token[2]), joinAt, true);
       forRemoval.push(i);
-    } else if (lastToken.body && token.metadata.body == lastToken.metadata.body &&
-        !this.isSpecial(token.metadata.selector) && !this.isSpecial(lastToken.metadata.selector)) {
-      changeSelectorOf(
-        lastToken,
-        CleanUp.selectors(lastToken.value.concat(token.value), false, adjacentSpace)
-      );
+    } else if (lastToken[0] == 'selector' && stringifyBody(token[2]) == stringifyBody(lastToken[2]) &&
+        !this.isSpecial(stringifySelector(token[1])) && !this.isSpecial(stringifySelector(lastToken[1]))) {
+      lastToken[1] = CleanUp.selectors(lastToken[1].concat(token[1]), false, adjacentSpace);
       forRemoval.push(i);
     } else {
       lastToken = token;
@@ -101,8 +82,6 @@ AdvancedOptimizer.prototype.mergeAdjacent = function (tokens) {
   for (var j = 0, m = forRemoval.length; j < m; j++) {
     tokens.splice(forRemoval[j] - j, 1);
   }
-
-  this.minificationsMade.unshift(forRemoval.length > 0);
 };
 
 AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) {
@@ -112,13 +91,13 @@ AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) {
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
 
-    if (token.kind != 'selector')
+    if (token[0] != 'selector')
       continue;
 
-    var isComplexAndNotSpecial = token.value.length > 1 && !this.isSpecial(token.metadata.selector);
+    var isComplexAndNotSpecial = token[1].length > 1 && !this.isSpecial(stringifySelector(token[1]));
     var selectors = isComplexAndNotSpecial ?
-      [token.metadata.selector].concat(token.metadata.selectorsList) :
-      [token.metadata.selector];
+      [stringifySelector(token[1])].concat(token[1]) :
+      [stringifySelector(token[1])];
 
     for (var j = 0, m = selectors.length; j < m; j++) {
       var selector = selectors[j];
@@ -130,31 +109,25 @@ AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) {
 
       candidates[selector].push({
         where: i,
-        list: token.metadata.selectorsList,
+        list: token[1],
         isPartial: isComplexAndNotSpecial && j > 0,
         isComplex: isComplexAndNotSpecial && j === 0
       });
     }
   }
 
-  var reducedInSimple = this.reduceSimpleNonAdjacentCases(tokens, repeated, candidates);
-  var reducedInComplex = this.reduceComplexNonAdjacentCases(tokens, candidates);
-
-  this.minificationsMade.unshift(reducedInSimple || reducedInComplex);
+  this.reduceSimpleNonAdjacentCases(tokens, repeated, candidates);
+  this.reduceComplexNonAdjacentCases(tokens, candidates);
 };
 
 AdvancedOptimizer.prototype.reduceSimpleNonAdjacentCases = function (tokens, repeated, candidates) {
-  var reduced = false;
-
   function filterOut(idx, bodies) {
     return data[idx].isPartial && bodies.length === 0;
   }
 
   function reduceBody(token, newBody, processedCount, tokenIdx) {
-    if (!data[processedCount - tokenIdx - 1].isPartial) {
-      changeBodyOf(token, newBody);
-      reduced = true;
-    }
+    if (!data[processedCount - tokenIdx - 1].isPartial)
+      token[2] = newBody;
   }
 
   for (var i = 0, l = repeated.length; i < l; i++) {
@@ -166,12 +139,9 @@ AdvancedOptimizer.prototype.reduceSimpleNonAdjacentCases = function (tokens, rep
       callback: reduceBody
     });
   }
-
-  return reduced;
 };
 
 AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, candidates) {
-  var reduced = false;
   var localContext = {};
 
   function filterOut(idx) {
@@ -214,15 +184,12 @@ AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, ca
         callback: collectReducedBodies
       });
 
-      if (reducedBodies[reducedBodies.length - 1].list.join(';') != reducedBodies[0].list.join(';'))
+      if (stringifyBody(reducedBodies[reducedBodies.length - 1]) != stringifyBody(reducedBodies[0]))
         continue allSelectors;
     }
 
-    intoToken.body = reducedBodies[0].tokenized;
-    reduced = true;
+    intoToken[2] = reducedBodies[0];
   }
-
-  return reduced;
 };
 
 AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, options) {
@@ -238,8 +205,8 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o
     var where = data[j].where;
     var token = tokens[where];
 
-    bodies = bodies.concat(token.body);
-    bodiesAsList.push(token.metadata.bodiesList);
+    bodies = bodies.concat(token[2]);
+    bodiesAsList.push(token[2]);
     processedTokens.push(where);
   }
 
@@ -251,19 +218,16 @@ AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, o
   var optimizedBody = this.propertyOptimizer.process(selector, bodies, joinsAt, false);
 
   var processedCount = processedTokens.length;
-  var propertyIdx = optimizedBody.tokenized.length - 1;
+  var propertyIdx = optimizedBody.length - 1;
   var tokenIdx = processedCount - 1;
 
   while (tokenIdx >= 0) {
-     if ((tokenIdx === 0 || (optimizedBody.tokenized[propertyIdx] && bodiesAsList[tokenIdx].indexOf(optimizedBody.tokenized[propertyIdx].value) > -1)) && propertyIdx > -1) {
+     if ((tokenIdx === 0 || (optimizedBody[propertyIdx] && stringifyBody(bodiesAsList[tokenIdx]).indexOf(optimizedBody[propertyIdx]) > -1)) && propertyIdx > -1) {
       propertyIdx--;
       continue;
     }
 
-    var newBody = {
-      list: optimizedBody.list.splice(propertyIdx + 1),
-      tokenized: optimizedBody.tokenized.splice(propertyIdx + 1)
-    };
+    var newBody = optimizedBody.splice(propertyIdx + 1);
     options.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
 
     tokenIdx--;
@@ -276,12 +240,12 @@ AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) {
   var i;
 
   for (i = tokens.length - 1; i >= 0; i--) {
-    if (tokens[i].kind != 'selector')
+    if (tokens[i][0] != 'selector')
       continue;
-    if (tokens[i].body.length === 0)
+    if (tokens[i][2].length === 0)
       continue;
 
-    var selector = tokens[i].metadata.selector;
+    var selector = stringifySelector(tokens[i][1]);
     allSelectors[selector] = [i].concat(allSelectors[selector] || []);
 
     if (allSelectors[selector].length == 2)
@@ -323,11 +287,10 @@ AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) {
             continue directionIterator;
         }
 
-        var joinAt = topToBottom ? [target.body.length] : [moved.body.length];
-        var joinedBodies = topToBottom ? moved.body.concat(target.body) : target.body.concat(moved.body);
-        var newBody = this.propertyOptimizer.process(target.value, joinedBodies, joinAt, true);
-        changeBodyOf(target, newBody);
-        changeBodyOf(moved, { tokenized: [], list: [] });
+        var joinAt = topToBottom ? [target[2].length] : [moved[2].length];
+        var joinedBodies = topToBottom ? moved[2].concat(target[2]) : target[2].concat(moved[2]);
+        target[2] = this.propertyOptimizer.process(target[1], joinedBodies, joinAt, true);
+        moved[2] = [];
       }
     }
   }
@@ -339,24 +302,21 @@ AdvancedOptimizer.prototype.mergeNonAdjacentByBody = function (tokens) {
 
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
-    if (token.kind != 'selector')
+    if (token[0] != 'selector')
       continue;
 
-    if (token.body.length > 0 && unsafeSelector(token.metadata.selector))
+    if (token[2].length > 0 && unsafeSelector(stringifySelector(token[1])))
       candidates = {};
 
-    var oldToken = candidates[token.metadata.body];
-    if (oldToken && !this.isSpecial(token.metadata.selector) && !this.isSpecial(oldToken.metadata.selector)) {
-      changeSelectorOf(
-        token,
-        CleanUp.selectors(oldToken.value.concat(token.value), false, adjacentSpace)
-      );
+    var oldToken = candidates[stringifyBody(token[2])];
+    if (oldToken && !this.isSpecial(stringifySelector(token[1])) && !this.isSpecial(stringifySelector(oldToken[1]))) {
+      token[1] = CleanUp.selectors(oldToken[1].concat(token[1]), false, adjacentSpace);
 
-      changeBodyOf(oldToken, { tokenized: [], list: [] });
-      candidates[token.metadata.body] = null;
+      oldToken[2] = [];
+      candidates[stringifyBody(token[2])] = null;
     }
 
-    candidates[token.metadata.body] = token;
+    candidates[stringifyBody(token[2])] = token;
   }
 };
 
@@ -411,7 +371,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
   function cacheId(cachedTokens) {
     var id = [];
     for (var i = 0, l = cachedTokens.length; i < l; i++) {
-      id.push(cachedTokens[i].metadata.selector);
+      id.push(stringifySelector(cachedTokens[i][1]));
     }
     return id.join(ID_JOIN_CHARACTER);
   }
@@ -421,11 +381,11 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
     var mergeableTokens = [];
 
     for (var i = sourceTokens.length - 1; i >= 0; i--) {
-      if (self.isSpecial(sourceTokens[i].metadata.selector))
+      if (self.isSpecial(stringifySelector(sourceTokens[i][1])))
         continue;
 
       mergeableTokens.unshift(sourceTokens[i]);
-      if (sourceTokens[i].body.length > 0 && uniqueTokensWithBody.indexOf(sourceTokens[i]) == -1)
+      if (sourceTokens[i][2].length > 0 && uniqueTokensWithBody.indexOf(sourceTokens[i]) == -1)
         uniqueTokensWithBody.push(sourceTokens[i]);
     }
 
@@ -437,7 +397,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
   function shortenIfPossible(position, movedProperty) {
     var name = movedProperty[0];
     var value = movedProperty[1];
-    var key = movedProperty[3];
+    var key = movedProperty[3][0];
     var valueSize = name.length + value.length + 1;
     var allSelectors = [];
     var qualifiedTokens = [];
@@ -452,7 +412,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
       return sendToMultiPropertyMoveCache(position, movedProperty, allFits);
 
     for (var i = bestFit[0].length - 1; i >=0; i--) {
-      allSelectors = bestFit[0][i].value.concat(allSelectors);
+      allSelectors = bestFit[0][i][1].concat(allSelectors);
       qualifiedTokens.unshift(bestFit[0][i]);
     }
 
@@ -485,55 +445,41 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
   function sizeDifference(tokensVariant, propertySize, propertiesCount) {
     var allSelectorsSize = 0;
     for (var i = tokensVariant.length - 1; i >= 0; i--) {
-      allSelectorsSize += tokensVariant[i].body.length > propertiesCount ? tokensVariant[i].metadata.selector.length : -1;
+      allSelectorsSize += tokensVariant[i][2].length > propertiesCount ? stringifySelector(tokensVariant[i][1]).length : -1;
     }
     return allSelectorsSize - (tokensVariant.length - 1) * propertySize + 1;
   }
 
   function dropAsNewTokenAt(position, properties, allSelectors, mergeableTokens) {
-    var bodyMetadata = {};
     var i, j, k, m;
+    var allProperties = [];
 
     for (i = mergeableTokens.length - 1; i >= 0; i--) {
       var mergeableToken = mergeableTokens[i];
 
-      for (j = mergeableToken.body.length - 1; j >= 0; j--) {
+      for (j = mergeableToken[2].length - 1; j >= 0; j--) {
 
         for (k = 0, m = properties.length; k < m; k++) {
           var property = properties[k];
 
-          if (mergeableToken.body[j].value === property[3]) {
-            bodyMetadata[property[3]] = mergeableToken.body[j].metadata;
-
-            mergeableToken.body.splice(j, 1);
-            mergeableToken.metadata.bodiesList.splice(j, 1);
-            mergeableToken.metadata.body = mergeableToken.metadata.bodiesList.join(';');
+          if (mergeableToken[2][j][0] === property[3][0]) {
+            mergeableToken[2].splice(j, 1);
             break;
           }
         }
       }
     }
 
-    var newToken = { kind: 'selector', metadata: {} };
-    var allBodies = { tokenized: [], list: [] };
-
     for (i = properties.length - 1; i >= 0; i--) {
-      allBodies.tokenized.push({ value: properties[i][3] });
-      allBodies.list.push(properties[i][3]);
-    }
-
-    changeSelectorOf(newToken, allSelectors);
-    changeBodyOf(newToken, allBodies);
-
-    for (i = properties.length - 1; i >= 0; i--) {
-      newToken.body[i].metadata = bodyMetadata[properties[i][3]];
+      allProperties.push(properties[i][3]);
     }
 
+    var newToken = ['selector', allSelectors, allProperties];
     tokens.splice(position, 0, newToken);
   }
 
   function dropPropertiesAt(position, movedProperty) {
-    var key = movedProperty[3];
+    var key = movedProperty[3][0];
 
     if (movableTokens[key] && movableTokens[key].length > 1)
       shortenIfPossible(position, movedProperty);
@@ -546,7 +492,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
 
     for (var i = propertiesAndMergableTokens.length - 1; i >= 0; i--) {
       property = propertiesAndMergableTokens[i][0];
-      var fullValue = property[3];
+      var fullValue = property[3][0];
       valueSize += fullValue.length + (i > 0 ? 1 : 0);
 
       properties.push(property);
@@ -560,7 +506,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
     var allSelectors = [];
     var qualifiedTokens = [];
     for (i = bestFit[0].length - 1; i >= 0; i--) {
-      allSelectors = bestFit[0][i].value.concat(allSelectors);
+      allSelectors = bestFit[0][i][1].concat(allSelectors);
       qualifiedTokens.unshift(bestFit[0][i]);
     }
 
@@ -585,9 +531,9 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
     var isSelector;
     var j, k, m;
 
-    if (token.kind == 'selector') {
+    if (token[0] == 'selector') {
       isSelector = true;
-    } else if (token.kind == 'block' && !token.isFlatBlock) {
+    } else if (token[0] == 'block') {
       isSelector = false;
     } else {
       continue;
@@ -619,7 +565,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
         if (movedToBeDropped.indexOf(k) == -1 && !canReorderSingle(property, movedProperty)) {
           dropPropertiesAt(i + 1, movedProperty);
           movedToBeDropped.push(k);
-          delete movableTokens[movedProperty[3]];
+          delete movableTokens[movedProperty[3][0]];
         }
 
         if (!movedSameProperty)
@@ -629,7 +575,7 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
       if (!isSelector || unmovableInCurrentToken.indexOf(j) > -1)
         continue;
 
-      var key = property[3];
+      var key = property[3][0];
       movableTokens[key] = movableTokens[key] || [];
       movableTokens[key].push(token);
 
@@ -643,10 +589,10 @@ AdvancedOptimizer.prototype.restructure = function (tokens) {
     }
   }
 
-  var position = tokens[0] && tokens[0].kind == 'at-rule' && tokens[0].value.indexOf('@charset') === 0 ? 1 : 0;
+  var position = tokens[0] && tokens[0][0] == 'at-rule' && tokens[0][1][0].indexOf('@charset') === 0 ? 1 : 0;
   for (; position < tokens.length - 1; position++) {
-    var isImportRule = tokens[position].kind === 'at-rule' && tokens[position].value.indexOf('@import') === 0;
-    var isEscapedCommentSpecial = tokens[position].kind === 'text' && tokens[position].value.indexOf('__ESCAPED_COMMENT_SPECIAL') === 0;
+    var isImportRule = tokens[position][0] === 'at-rule' && tokens[position][1][0].indexOf('@import') === 0;
+    var isEscapedCommentSpecial = tokens[position][0] === 'text' && tokens[position][1][0].indexOf('__ESCAPED_COMMENT_SPECIAL') === 0;
     if (!(isImportRule || isEscapedCommentSpecial))
       break;
   }
@@ -662,13 +608,13 @@ AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) {
 
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
-    if (token.kind != 'block' || token.isFlatBlock === true)
+    if (token[0] != 'block')
       continue;
 
-    var candidate = candidates[token.value];
+    var candidate = candidates[token[1][0]];
     if (!candidate) {
       candidate = [];
-      candidates[token.value] = candidate;
+      candidates[token[1][0]] = candidate;
     }
 
     candidate.push(i);
@@ -691,8 +637,8 @@ AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) {
           continue positionLoop;
       }
 
-      target.body = source.body.concat(target.body);
-      source.body = [];
+      target[2] = source[2].concat(target[2]);
+      source[2] = [];
 
       reduced.push(target);
     }
@@ -701,17 +647,30 @@ AdvancedOptimizer.prototype.mergeMediaQueries = function (tokens) {
   return reduced;
 };
 
+AdvancedOptimizer.prototype.removeEmpty = function (tokens) {
+  for (var i = 0, l = tokens.length; i < l; i++) {
+    var token = tokens[i];
+
+    if (token[0] == 'selector' && (token[1].length === 0 || token[2].length === 0)) {
+      tokens.splice(i, 1);
+      i--;
+      l--;
+    } else if (token[1] == 'block') {
+      this.removeEmpty(token[2]);
+    }
+  }
+};
+
 function optimizeProperties(tokens, propertyOptimizer) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (token.kind == 'selector') {
-      changeBodyOf(
-        token,
-        propertyOptimizer.process(token.value, token.body, false, true)
-      );
-    } else if (token.kind == 'block') {
-      optimizeProperties(token.body, propertyOptimizer);
+    switch (token[0]) {
+      case 'selector':
+        token[2] = propertyOptimizer.process(token[1], token[2], false, true);
+        break;
+      case 'block':
+        optimizeProperties(token[2], propertyOptimizer);
     }
   }
 }
@@ -721,9 +680,9 @@ AdvancedOptimizer.prototype.optimize = function (tokens) {
 
   function _optimize(tokens, withRestructuring) {
     tokens.forEach(function (token) {
-      if (token.kind == 'block') {
-        var isKeyframes = /@(-moz-|-o-|-webkit-)?keyframes/.test(token.value);
-        _optimize(token.body, !isKeyframes);
+      if (token[0] == 'block') {
+        var isKeyframes = /@(-moz-|-o-|-webkit-)?keyframes/.test(token[1][0]);
+        _optimize(token[2], !isKeyframes);
       }
     });
 
@@ -744,9 +703,11 @@ AdvancedOptimizer.prototype.optimize = function (tokens) {
     if (self.options.mediaMerging) {
       var reduced = self.mergeMediaQueries(tokens);
       for (var i = reduced.length - 1; i >= 0; i--) {
-        _optimize(reduced[i].body);
+        _optimize(reduced[i][2]);
       }
     }
+
+    self.removeEmpty(tokens);
   }
 
   _optimize(tokens, true);
index 878f4b9..ac36a2d 100644 (file)
@@ -3,17 +3,17 @@ function removeWhitespace(match, value) {
 }
 
 function selectorSorter(s1, s2) {
-  return s1.value > s2.value ? 1 : -1;
+  return s1[0] > s2[0] ? 1 : -1;
 }
 
 var CleanUp = {
   selectors: function (selectors, removeUnsupported, adjacentSpace) {
-    var plain = [];
-    var tokenized = [];
+    var list = [];
+    var repeated = [];
 
     for (var i = 0, l = selectors.length; i < l; i++) {
       var selector = selectors[i];
-      var reduced = selector.value
+      var reduced = selector[0]
         .replace(/\s+/g, ' ')
         .replace(/ ?, ?/g, ',')
         .replace(/\s*([>\+\~])\s*/g, '$1')
@@ -34,47 +34,41 @@ var CleanUp = {
       if (reduced.indexOf('[') > -1)
         reduced = reduced.replace(/\[([^\]]+)\]/g, removeWhitespace);
 
-      if (plain.indexOf(reduced) == -1) {
-        plain.push(reduced);
-        selector.value = reduced;
-        tokenized.push(selector);
+      if (repeated.indexOf(reduced) == -1) {
+        selector[0] = reduced;
+        repeated.push(reduced);
+        list.push(selector);
       }
     }
 
-    return {
-      list: plain.sort(),
-      tokenized: tokenized.sort(selectorSorter)
-    };
+    return list.sort(selectorSorter);
   },
 
   selectorDuplicates: function (selectors) {
-    var plain = [];
-    var tokenized = [];
+    var list = [];
+    var repeated = [];
 
     for (var i = 0, l = selectors.length; i < l; i++) {
       var selector = selectors[i];
 
-      if (plain.indexOf(selector.value) == -1) {
-        plain.push(selector.value);
-        tokenized.push(selector);
+      if (repeated.indexOf(selector[0]) == -1) {
+        repeated.push(selector[0]);
+        list.push(selector);
       }
     }
 
-    return {
-      list: plain.sort(),
-      tokenized: tokenized.sort(selectorSorter)
-    };
+    return list.sort(selectorSorter);
   },
 
-  block: function (block) {
-    return block
+  block: function (values) {
+    values[0] = values[0]
       .replace(/\s+/g, ' ')
       .replace(/(,|:|\() /g, '$1')
       .replace(/ ?\) ?/g, ')');
   },
 
-  atRule: function (block) {
-    return block
+  atRule: function (values) {
+    values[0] = values[0]
       .replace(/\s+/g, ' ')
       .trim();
   }
index 028dbc6..45bf760 100644 (file)
@@ -25,8 +25,6 @@ function SimpleOptimizer(options) {
     options.roundingPrecision;
   options.precision.multiplier = Math.pow(10, options.precision.value);
   options.precision.regexp = new RegExp('(\\d*\\.\\d{' + (options.precision.value + 1) + ',})px', 'g');
-
-  options.updateMetadata = this.options.advanced;
 }
 
 var valueMinifiers = {
@@ -127,17 +125,17 @@ function unitMinifier(_, value, unitsRegexp) {
   return value.replace(unitsRegexp, '$1' + '0');
 }
 
-function multipleZerosMinifier(property, value) {
+function multipleZerosMinifier(name, value) {
   if (value.indexOf('0 0 0 0') == -1)
     return value;
 
-  if (property.indexOf('box-shadow') > -1)
+  if (name.indexOf('box-shadow') > -1)
     return value == '0 0 0 0' ? '0 0' : value;
 
   return value.replace(/^0 0 0 0$/, '0');
 }
 
-function colorMininifier(property, value, compatibility) {
+function colorMininifier(_, value, compatibility) {
   if (value.indexOf('#') === -1 && value.indexOf('rgb') == -1 && value.indexOf('hsl') == -1)
     return HexNameShortener.shorten(value);
 
@@ -179,126 +177,127 @@ function colorMininifier(property, value, compatibility) {
   return HexNameShortener.shorten(value);
 }
 
-function spaceMinifier(property, value) {
-  if (property == 'filter' || value.indexOf(') ') == -1 || processable.implementedFor.test(property))
+function spaceMinifier(name, value) {
+  if (name == 'filter' || value.indexOf(') ') == -1 || processable.implementedFor.test(name))
     return value;
 
   return value.replace(/\) ((?![\+\-] )|$)/g, ')$1');
 }
 
-function reduce(body, options) {
-  var reduced = [];
-  var properties = [];
-  var newProperty;
+function optimizeBody(properties, options) {
+  var property, firstColon, name, value, important;
 
-  for (var i = 0, l = body.length; i < l; i++) {
-    var token = body[i];
+  for (var i = 0, l = properties.length; i < l; i++) {
+    property = properties[i];
 
     // FIXME: the check should be gone with #407
-    if (token.value.indexOf('__ESCAPED_') === 0) {
-      reduced.push(token);
-      properties.push(token.value);
+    if (property[0].indexOf('__ESCAPED_') === 0)
       continue;
-    }
 
-    var firstColon = token.value.indexOf(':');
-    var property = token.value.substring(0, firstColon);
-    var value = token.value.substring(firstColon + 1);
-    var important = false;
+    firstColon = property[0].indexOf(':');
+    name = property[0].substring(0, firstColon);
+    value = property[0].substring(firstColon + 1);
+    important = false;
 
-    if (!options.compatibility.properties.iePrefixHack && (property[0] == '_' || property[0] == '*'))
+    if ((!options.compatibility.properties.iePrefixHack && (name[0] == '_' || name[0] == '*')) ||
+        (name.indexOf('padding') === 0 && isNegative(value))) {
+      properties.splice(i, 1);
+      i--;
+      l--;
       continue;
+    }
 
     if (value.indexOf('!important') > 0 || value.indexOf('! important') > 0) {
       value = value.substring(0, value.indexOf('!')).trim();
       important = true;
     }
 
-    if (property.indexOf('padding') === 0 && isNegative(value))
-      continue;
-
-    if (property.indexOf('border') === 0 && property.indexOf('radius') > 0)
+    if (name.indexOf('border') === 0 && name.indexOf('radius') > 0)
       value = valueMinifiers['border-*-radius'](value);
 
-    if (valueMinifiers[property])
-      value = valueMinifiers[property](value);
+    if (valueMinifiers[name])
+      value = valueMinifiers[name](value);
 
-    value = precisionMinifier(property, value, options.precision);
-    value = zeroMinifier(property, value);
+    value = precisionMinifier(name, value, options.precision);
+    value = zeroMinifier(name, value);
     if (options.compatibility.properties.zeroUnits) {
-      value = zeroDegMinifier(property, value);
-      value = unitMinifier(property, value, options.unitsRegexp);
+      value = zeroDegMinifier(name, value);
+      value = unitMinifier(name, value, options.unitsRegexp);
     }
-    value = multipleZerosMinifier(property, value);
-    value = colorMininifier(property, value, options.compatibility);
+    value = multipleZerosMinifier(name, value);
+    value = colorMininifier(name, value, options.compatibility);
 
     if (!options.compatibility.properties.spaceAfterClosingBrace)
-      value = spaceMinifier(property, value);
+      value = spaceMinifier(name, value);
 
-    newProperty = property + ':' + value + (important ? '!important' : '');
-    reduced.push({ value: newProperty, metadata: token.metadata });
-    properties.push(newProperty);
+    property[0] = name + ':' + value + (important ? '!important' : '');
   }
-
-  return {
-    tokenized: reduced,
-    list: properties
-  };
 }
 
 SimpleOptimizer.prototype.optimize = function(tokens) {
   var self = this;
   var hasCharset = false;
   var options = this.options;
+  var ie7Hack = options.compatibility.selectors.ie7Hack;
+  var adjacentSpace = options.compatibility.selectors.adjacentSpace;
+  var token;
 
-  function _optimize(tokens) {
+  function _cleanupCharsets(tokens) {
     for (var i = 0, l = tokens.length; i < l; i++) {
-      var token = tokens[i];
-      // FIXME: why it's so?
-      if (!token)
-        break;
+      token = tokens[i];
 
-      if (token.kind == 'selector') {
-        var newSelectors = CleanUp.selectors(token.value, !options.compatibility.selectors.ie7Hack, options.compatibility.selectors.adjacentSpace);
-        token.value = newSelectors.tokenized;
+      if (token[0] != 'at-rule')
+        continue;
 
-        if (token.value.length === 0) {
+      if (CHARSET_REGEXP.test(token[1][0])) {
+        if (hasCharset || token[1][0].indexOf(CHARSET_TOKEN) == -1) {
           tokens.splice(i, 1);
           i--;
-          continue;
-        }
-        var newBody = reduce(token.body, self.options);
-        token.body = newBody.tokenized;
-
-        if (options.updateMetadata) {
-          token.metadata.body = newBody.list.join(';');
-          token.metadata.bodiesList = newBody.list;
-          token.metadata.selector = newSelectors.list.join(',');
-          token.metadata.selectorsList = newSelectors.list;
-        }
-      } else if (token.kind == 'block') {
-        token.value = CleanUp.block(token.value);
-        if (token.isFlatBlock)
-          token.body = reduce(token.body, self.options).tokenized;
-        else
-          _optimize(token.body);
-      } else if (token.kind == 'at-rule') {
-        token.value = CleanUp.atRule(token.value);
-
-        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({ kind: 'at-rule', value: token.value.replace(CHARSET_REGEXP, CHARSET_TOKEN) });
-          }
+          l--;
+        } else {
+          hasCharset = true;
+          tokens.splice(i, 1);
+          tokens.unshift(['at-rule', [token[1][0].replace(CHARSET_REGEXP, CHARSET_TOKEN)]]);
         }
       }
     }
   }
 
+  function _optimize(tokens) {
+    var mayHaveCharset = false;
+
+    for (var i = 0, l = tokens.length; i < l; i++) {
+      token = tokens[i];
+
+      switch (token[0]) {
+        case 'selector':
+          token[1] = CleanUp.selectors(token[1], !ie7Hack, adjacentSpace);
+          optimizeBody(token[2], self.options);
+          break;
+        case 'block':
+          CleanUp.block(token[1]);
+          _optimize(token[2]);
+          break;
+        case 'flat-block':
+          CleanUp.block(token[1]);
+          optimizeBody(token[2], self.options);
+          break;
+        case 'at-rule':
+          CleanUp.atRule(token[1]);
+          mayHaveCharset = true;
+      }
+
+      if (token[1].length === 0 || (token[2] && token[2].length === 0)) {
+        tokens.splice(i, 1);
+        i--;
+        l--;
+      }
+    }
+
+    if (mayHaveCharset)
+      _cleanupCharsets(tokens);
+  }
+
   _optimize(tokens);
 };
 
index d09b2b0..f744fda 100644 (file)
@@ -1,7 +1,7 @@
 var SourceMapGenerator = require('source-map').SourceMapGenerator;
-var SourceMap = require('../utils/source-maps');
 
 var lineBreak = require('os').EOL;
+var unknownSource = '$stdin';
 
 function Rebuilder(options, restoreCallback, inputMapTracker) {
   this.column = 0;
@@ -14,32 +14,34 @@ function Rebuilder(options, restoreCallback, inputMapTracker) {
   this.outputMap = new SourceMapGenerator();
 }
 
-Rebuilder.prototype.rebuildValue = function (list, separator) {
+Rebuilder.prototype.rebuildValue = function (elements, separator) {
   var escaped = 0;
 
-  for (var i = 0, l = list.length; i < l; i++) {
-    var el = list[i];
+  for (var i = 0, l = elements.length; i < l; i++) {
+    var element = elements[i];
 
-    if (el.value.indexOf('__ESCAPED_') === 0) {
-      this.store(el);
+    if (element[0].indexOf('__ESCAPED_') === 0) {
+      this.store(element[0]);
       escaped++;
 
       if (i === l - 1 && escaped > 0)
         this.output.splice(this.output.length - escaped - 1, 1);
     } else {
-      this.store(el);
+      this.store(element);
       this.store(i < l - 1 ? separator : '');
       escaped = 0;
     }
   }
 };
 
-Rebuilder.prototype.store = function (token) {
-  var value = typeof token == 'string' ?
-    token :
-    token.value.indexOf('_') > -1 ? this.restore(token.value) : token.value;
+Rebuilder.prototype.store = function (element) {
+  var fromString = typeof element == 'string';
+  var value = fromString ? element : element[0];
 
-  this.track(value, token.metadata);
+  if (value.indexOf('_') > -1)
+    value = this.restore(value);
+
+  this.track(value, fromString ? null : element);
   this.output.push(value);
 };
 
@@ -49,47 +51,46 @@ Rebuilder.prototype.rebuildList = function (tokens, isFlatBlock) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (token.kind === 'text' || token.kind == 'at-rule') {
-      this.store(token);
-      continue;
-    }
-
-    // FIXME: broken due to joining/splitting
-    if (token.body && (token.body.length === 0 || (token.body.length == 1 && token.body[0].value === '')))
-      continue;
-
-    if (token.kind == 'block') {
-      if (token.body.length > 0) {
-        this.rebuildValue([{ value: token.value, metadata: token.metadata }], '');
+    switch (token[0]) {
+      case 'at-rule':
+      case 'text':
+        this.store(token[1][0]);
+        break;
+      case 'block':
+        this.rebuildValue([token[1]], '');
         this.store('{');
-        if (token.isFlatBlock)
-          this.rebuildValue(token.body, ';');
-        else
-          this.rebuildList(token.body, false);
+        this.rebuildList(token[2], false);
         this.store('}');
-      }
-    } else {
-      this.rebuildValue(token.value, ',');
-      this.store('{');
-      this.rebuildValue(token.body, ';');
-      this.store('}');
+        this.store(joinCharacter);
+        break;
+      case 'flat-block':
+        this.rebuildValue([token[1]], '');
+        this.store('{');
+        this.rebuildValue(token[2], ';');
+        this.store('}');
+        this.store(joinCharacter);
+        break;
+      default:
+        this.rebuildValue(token[1], ',');
+        this.store('{');
+        this.rebuildValue(token[2], ';');
+        this.store('}');
+        this.store(joinCharacter);
     }
-
-    this.store(joinCharacter);
   }
 };
 
-Rebuilder.prototype.track = function (value, metadata) {
-  if (metadata)
-    this.trackMetadata(metadata);
+Rebuilder.prototype.track = function (value, element) {
+  if (element)
+    this.trackMetadata(element);
 
   var parts = value.split('\n');
   this.line += parts.length - 1;
   this.column = parts.length > 1 ? 0 : (this.column + parts.pop().length);
 };
 
-Rebuilder.prototype.trackMetadata = function (metadata) {
-  var source = metadata.source || SourceMap.unknownSource;
+Rebuilder.prototype.trackMetadata = function (element) {
+  var source = element[3] || unknownSource;
 
   this.outputMap.addMapping({
     generated: {
@@ -97,11 +98,14 @@ Rebuilder.prototype.trackMetadata = function (metadata) {
       column: this.column
     },
     source: source,
-    original: metadata.original
+    original: {
+      line: element[1],
+      column: element[2]
+    }
   });
 
-  if (metadata.sourcesContent)
-    this.outputMap.setSourceContent(source, metadata.sourcesContent[metadata.source]);
+  if (element[4])
+    this.outputMap.setSourceContent(source, element[4][element[3]]);
 };
 
 function SourceMapStringifier(options, restoreCallback, inputMapTracker) {
index 1d9bb46..430fdaf 100644 (file)
@@ -5,21 +5,22 @@ function Stringifier(options, restoreCallback) {
   this.restoreCallback = restoreCallback;
 }
 
-function valueRebuilder(list, separator) {
+function valueRebuilder(elements, separator) {
   var merged = '';
+  var element;
 
-  for (var i = 0, l = list.length; i < l; i++) {
-    var el = list[i];
+  for (var i = 0, l = elements.length; i < l; i++) {
+    element = elements[i];
 
-    if (el.value.indexOf('__ESCAPED_') === 0) {
-      merged += el.value;
+    if (element[0].indexOf('__ESCAPED_') === 0) {
+      merged += element[0];
 
       if (i === l - 1) {
         var lastSemicolonAt = merged.lastIndexOf(';');
         merged = merged.substring(0, lastSemicolonAt) + merged.substring(lastSemicolonAt + 1);
       }
     } else {
-      merged += list[i].value + (i < l - 1 ? separator : '');
+      merged += element[0] + (i < l - 1 ? separator : '');
     }
   }
 
@@ -35,25 +36,25 @@ function rebuild(tokens, keepBreaks, isFlatBlock) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (token.kind === 'text' || token.kind == 'at-rule') {
-      parts.push(token.value);
-      continue;
-    }
-
-    // FIXME: broken due to joining/splitting
-    if (token.body && (token.body.length === 0 || (token.body.length == 1 && token.body[0].value === '')))
-      continue;
-
-    if (token.kind == 'block') {
-      body = token.isFlatBlock ?
-        valueRebuilder(token.body, ';') :
-        rebuild(token.body, keepBreaks, token.isFlatBlock);
-      if (body.length > 0)
-        parts.push(token.value + '{' + body + '}');
-    } else {
-      selector = valueRebuilder(token.value, ',');
-      body = valueRebuilder(token.body, ';');
-      parts.push(selector + '{' + body + '}');
+    switch (token[0]) {
+      case 'at-rule':
+      case 'text':
+        parts.push(token[1][0]);
+        break;
+      case 'block':
+        body = rebuild(token[2], keepBreaks, false);
+        if (body.length > 0)
+          parts.push(token[1][0] + '{' + body + '}');
+        break;
+      case 'flat-block':
+        body = valueRebuilder(token[2], ';');
+        if (body.length > 0)
+          parts.push(token[1][0] + '{' + body + '}');
+        break;
+      default:
+        selector = valueRebuilder(token[1], ',');
+        body = valueRebuilder(token[2], ';');
+        parts.push(selector + '{' + body + '}');
     }
   }
 
index 6fbf4e6..5fa2c5a 100644 (file)
@@ -1,15 +1,14 @@
 var Chunker = require('../utils/chunker');
 var Extract = require('../utils/extractors');
-var SourceMaps = require('../utils/source-maps');
+var track = require('../utils/source-maps');
 
 var path = require('path');
 
 var flatBlock = /(^@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport|counter\-style)|\\@.+?)/;
 
-function Tokenizer(minifyContext, addMetadata, addSourceMap) {
+function Tokenizer(minifyContext, sourceMaps) {
   this.minifyContext = minifyContext;
-  this.addMetadata = addMetadata;
-  this.addSourceMap = addSourceMap;
+  this.sourceMaps = sourceMaps;
 }
 
 Tokenizer.prototype.toTokens = function (data) {
@@ -25,8 +24,10 @@ Tokenizer.prototype.toTokens = function (data) {
     chunker: chunker,
     chunk: chunker.next(),
     outer: this.minifyContext,
-    addMetadata: this.addMetadata,
-    addSourceMap: this.addSourceMap,
+    track: this.sourceMaps ?
+      function (data, snapshotMetadata, fallbacks) { return track(data, context, snapshotMetadata, fallbacks); } :
+      function () { return []; },
+    sourceMaps: this.sourceMaps,
     state: [],
     line: 1,
     column: 0,
@@ -102,7 +103,6 @@ function tokenize(context) {
   var tokenized = [];
   var newToken;
   var value;
-  var addSourceMap = context.addSourceMap;
 
   while (true) {
     var next = whatsNext(context);
@@ -112,7 +112,7 @@ function tokenize(context) {
         if (context.mode == 'body') {
           context.outer.warnings.push('Missing \'}\' after \'' + whatsLeft + '\'. Ignoring.');
         } else {
-          tokenized.push({ kind: 'text', value: whatsLeft });
+          tokenized.push(['text', [whatsLeft]]);
         }
         context.cursor += whatsLeft.length;
       }
@@ -132,9 +132,7 @@ function tokenize(context) {
 
       if (leadingWhitespace) {
         context.cursor += leadingWhitespace[0].length;
-
-        if (addSourceMap)
-          SourceMaps.track(leadingWhitespace[0], context);
+        context.track(leadingWhitespace[0]);
       }
     }
 
@@ -148,14 +146,14 @@ function tokenize(context) {
         context.cursor = chunk.length;
       } else if (isSingle) {
         nextEnd = chunk.indexOf(';', nextSpecial + 1);
-
         value = chunk.substring(context.cursor, nextEnd + 1);
-        newToken = { kind: 'at-rule', value: value };
-        tokenized.push(newToken);
 
-        if (addSourceMap)
-          newToken.metadata = SourceMaps.saveAndTrack(value, context, true);
+        tokenized.push([
+          'at-rule',
+          [value].concat(context.track(value, true))
+        ]);
 
+        context.track(';');
         context.cursor = nextEnd + 1;
       } else {
         nextEnd = chunk.indexOf('{', nextSpecial + 1);
@@ -167,19 +165,19 @@ function tokenize(context) {
         context.cursor = nextEnd + 1;
         context.mode = isFlat ? 'body' : 'block';
 
-        newToken = { kind: 'block', value: trimmedValue, isFlatBlock: isFlat };
+        newToken = [
+          isFlat ? 'flat-block' : 'block'
+        ];
 
-        if (addSourceMap)
-          newToken.metadata = SourceMaps.saveAndTrack(value, context, true);
+        newToken.push([trimmedValue].concat(context.track(value, true)));
+        context.track('{');
+        newToken.push(tokenize(context));
 
-        newToken.body = tokenize(context);
-        if (typeof newToken.body == 'string')
-          newToken.body = Extract.properties(newToken.body, context).tokenized;
+        if (typeof newToken[2] == 'string')
+          newToken[2] = Extract.properties(newToken[2], context);
 
         context.mode = oldMode;
-
-        if (addSourceMap)
-          SourceMaps.suffix(context);
+        context.track('}');
 
         tokenized.push(newToken);
       }
@@ -190,9 +188,7 @@ function tokenize(context) {
       var isEndSourceMarker = !!context.outer.sourceTracker.nextEnd(escaped);
 
       if (isStartSourceMarker) {
-        if (addSourceMap)
-          SourceMaps.track(escaped, context);
-
+        context.track(escaped);
         context.state.push({
           source: context.source,
           line: context.line,
@@ -206,46 +202,32 @@ function tokenize(context) {
         context.source = oldState.source;
         context.line = oldState.line;
         context.column = oldState.column;
-
-        if (addSourceMap)
-          SourceMaps.track(escaped, context);
+        context.track(escaped);
       } else {
         if (escaped.indexOf('__ESCAPED_COMMENT_SPECIAL') === 0)
-          tokenized.push({ kind: 'text', value: escaped });
+          tokenized.push(['text', [escaped]]);
 
-        if (addSourceMap)
-          SourceMaps.track(escaped, context);
+        context.track(escaped);
       }
 
       context.cursor = nextEnd + 2;
     } else if (what == 'bodyStart') {
-      var selectorData = Extract.selectors(chunk.substring(context.cursor, nextSpecial), context);
+      var selectors = Extract.selectors(chunk.substring(context.cursor, nextSpecial), context);
 
       oldMode = context.mode;
       context.cursor = nextSpecial + 1;
       context.mode = 'body';
 
-      var bodyData = Extract.properties(tokenize(context), context);
-
-      if (addSourceMap)
-        SourceMaps.suffix(context);
+      var body = Extract.properties(tokenize(context), context);
 
+      context.track('{');
       context.mode = oldMode;
 
-      newToken = {
-        kind: 'selector',
-        value: selectorData.tokenized,
-        body: bodyData.tokenized
-      };
-      if (context.addMetadata) {
-        newToken.metadata = {
-          body: bodyData.list.join(','),
-          bodiesList: bodyData.list,
-          selector: selectorData.list.join(','),
-          selectorsList: selectorData.list
-        };
-      }
-      tokenized.push(newToken);
+      tokenized.push([
+        'selector',
+        selectors,
+        body
+      ]);
     } else if (what == 'bodyEnd') {
       // extra closing brace at the top level can be safely ignored
       if (context.mode == 'top') {
@@ -259,8 +241,8 @@ function tokenize(context) {
         continue;
       }
 
-      if (context.mode == 'block' && context.addSourceMap)
-        SourceMaps.track(chunk.substring(context.cursor, nextSpecial), context);
+      if (context.mode == 'block')
+        context.track(chunk.substring(context.cursor, nextSpecial));
       if (context.mode != 'block')
         tokenized = chunk.substring(context.cursor, nextSpecial);
 
index 824f179..8f9867b 100644 (file)
@@ -1,9 +1,7 @@
 var Splitter = require('./splitter');
-var SourceMaps = require('../utils/source-maps');
 
 var Extractors = {
   properties: function (string, context) {
-    var tokenized = [];
     var list = [];
     var buffer = [];
     var all = [];
@@ -18,11 +16,10 @@ var Extractors = {
     var secondToLast;
     var wasCloseParenthesis;
     var isEscape;
-    var token;
-    var addSourceMap = context.addSourceMap;
+    var metadata;
 
     if (string.replace && string.indexOf(')') > 0)
-      string = string.replace(/\)([^\s_;:,\)])/g, context.addSourceMap ? ') __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__$1' : ') $1');
+      string = string.replace(/\)([^\s_;:,\)])/g, context.sourceMaps ? ') __ESCAPED_COMMENT_CLEAN_CSS(0,-1)__$1' : ') $1');
 
     for (var i = 0, l = string.length; i < l; i++) {
       current = string[i];
@@ -39,8 +36,7 @@ var Extractors = {
           i = endOfEscape - 1;
 
           if (comment.indexOf('__ESCAPED_COMMENT_SPECIAL') === -1) {
-            if (addSourceMap)
-              SourceMaps.track(comment, context, true);
+            context.track(comment);
             continue;
           }
           else {
@@ -55,12 +51,11 @@ var Extractors = {
         if (buffer.length > 0) {
           property = buffer.join('');
           if (property.indexOf('{') === -1) {
-            token = { value: property };
-            tokenized.push(token);
-            list.push(property);
+            metadata = context.track(all.join(''), true);
+            list.push([property].concat(metadata));
 
-            if (addSourceMap)
-              token.metadata = SourceMaps.saveAndTrack(all.join(''), context, !isEscape);
+            if (!isEscape)
+              context.track(';');
           }
         }
         buffer = [];
@@ -97,44 +92,28 @@ var Extractors = {
     if (buffer.length > 0) {
       property = buffer.join('');
       if (property.indexOf('{') === -1) {
-        token = { value: property };
-        tokenized.push(token);
-        list.push(property);
-
-        if (addSourceMap)
-          token.metadata = SourceMaps.saveAndTrack(all.join(''), context, false);
+        metadata = context.track(all.join(''), true);
+        list.push([property].concat(metadata));
       }
     } else if (all.indexOf('\n') > -1) {
-      SourceMaps.track(all.join(''), context);
+      context.track(all.join(''));
     }
 
-    return {
-      list: list,
-      tokenized: tokenized
-    };
+    return list;
   },
 
   selectors: function (string, context) {
-    var tokenized = [];
     var list = [];
+    var metadata;
     var selectors = new Splitter(',').split(string);
-    var addSourceMap = context.addSourceMap;
-    for (var i = 0, l = selectors.length; i < l; i++) {
-      var selector = selectors[i];
-
-      list.push(selector);
 
-      var token = { value: selector };
-      tokenized.push(token);
-
-      if (addSourceMap)
-        token.metadata = SourceMaps.saveAndTrack(selector, context, true, i);
+    for (var i = 0, l = selectors.length; i < l; i++) {
+      metadata = context.track(selectors[i], true, i);
+      context.track(',');
+      list.push([selectors[i]].concat(metadata));
     }
 
-    return {
-      list: list,
-      tokenized: tokenized
-    };
+    return list;
   }
 };
 
index 3644399..1ffc70b 100644 (file)
@@ -127,12 +127,12 @@ function fetch(self, path, onSuccess, onFailure) {
     .setTimeout(self.timeout);
 }
 
-function originalPositionIn(trackedSource, sourceInfo, token, allowNFallbacks) {
+function originalPositionIn(trackedSource, line, column, token, allowNFallbacks) {
   var originalPosition;
   var maxRange = token.length;
   var position = {
-    line: sourceInfo.line,
-    column: sourceInfo.column + maxRange
+    line: line,
+    column: column + maxRange
   };
 
   while (maxRange-- > 0) {
@@ -143,8 +143,8 @@ function originalPositionIn(trackedSource, sourceInfo, token, allowNFallbacks) {
       break;
   }
 
-  if (originalPosition.line === null && sourceInfo.line > 1 && allowNFallbacks > 0)
-    return originalPositionIn(trackedSource, { line: sourceInfo.line - 1, column: sourceInfo.column }, token, allowNFallbacks - 1);
+  if (originalPosition.line === null && line > 1 && allowNFallbacks > 0)
+    return originalPositionIn(trackedSource, line - 1, column, token, allowNFallbacks - 1);
 
   if (trackedSource.path) {
     originalPosition.source = REMOTE_RESOURCE.test(trackedSource.path) ?
@@ -237,7 +237,7 @@ InputSourceMapStore.prototype.isTracking = function (source) {
 };
 
 InputSourceMapStore.prototype.originalPositionFor = function (sourceInfo, token, allowNFallbacks) {
-  return originalPositionIn(this.maps[sourceInfo.source], sourceInfo.original, token, allowNFallbacks);
+  return originalPositionIn(this.maps[sourceInfo.source], sourceInfo.line, sourceInfo.column, token, allowNFallbacks);
 };
 
 InputSourceMapStore.prototype.sourcesContentFor = function (contextSource) {
index bb2af88..f9a7d31 100644 (file)
@@ -1,14 +1,30 @@
-function trimLeft(value, context) {
-  var withoutContent;
-  var total;
+var escapePrefix = '__ESCAPED_';
+
+function trackPrefix(value, context, interestingContent) {
+  if (!interestingContent && value.indexOf('\n') == -1) {
+    if (value.indexOf(escapePrefix) === 0) {
+      return value;
+    } else {
+      context.column += value.length;
+      return;
+    }
+  }
+
+  var withoutContent = 0;
   var split = value.split('\n');
+  var total = split.length;
   var shift = 0;
-  for (withoutContent = 0, total = split.length; withoutContent < total; withoutContent++) {
+
+  while (true) {
+    if (withoutContent == total - 1)
+      break;
+
     var part = split[withoutContent];
     if (/\S/.test(part))
       break;
 
     shift += part.length + 1;
+    withoutContent++;
   }
 
   context.line += withoutContent;
@@ -27,82 +43,77 @@ function sourceFor(originalMetadata, contextMetadata, context) {
   return source;
 }
 
-var SourceMaps = {
-  unknownSource: '$stdin',
-
-  saveAndTrack: function (data, context, hasSuffix, allowNFallbacks) {
-    var trimmedValue = trimLeft(data, context);
-
-    var contextMetadata = {
-      original: {
-        line: context.line,
-        column: context.column
-      },
-      source: context.source
-    };
-    var sourceMetadata = context.outer.inputSourceMapTracker.isTracking(contextMetadata.source) ?
-      context.outer.inputSourceMapTracker.originalPositionFor(contextMetadata, trimmedValue, allowNFallbacks || 0) :
-      {};
-
-    contextMetadata.original.line = sourceMetadata.line || contextMetadata.original.line;
-    contextMetadata.original.column = sourceMetadata.column || contextMetadata.original.column;
-    contextMetadata.source = sourceMetadata.sourceResolved ?
-      sourceMetadata.source :
-      sourceFor(sourceMetadata, contextMetadata, context);
-
-    if (context.outer.options.sourceMapInlineSources) {
-      var sourceMapSourcesContent = context.outer.inputSourceMapTracker.sourcesContentFor(context.source);
-      var source = sourceMapSourcesContent && sourceMapSourcesContent[contextMetadata.source] ?
-        sourceMapSourcesContent :
-        context.outer.sourceReader.sourceAt(context.source);
-
-      if (source)
-        contextMetadata.sourcesContent = source;
-    }
-
-    this.track(trimmedValue, context);
+function snapshot(data, context, fallbacks) {
+  var metadata = {
+    line: context.line,
+    column: context.column,
+    source: context.source
+  };
+  var sourceContent = null;
+  var sourceMetadata = context.outer.inputSourceMapTracker.isTracking(metadata.source) ?
+    context.outer.inputSourceMapTracker.originalPositionFor(metadata, data, fallbacks || 0) :
+    {};
+
+  metadata.line = sourceMetadata.line || metadata.line;
+  metadata.column = sourceMetadata.column || metadata.column;
+  metadata.source = sourceMetadata.sourceResolved ?
+    sourceMetadata.source :
+    sourceFor(sourceMetadata, metadata, context);
+
+  if (context.outer.options.sourceMapInlineSources) {
+    var sourceMapSourcesContent = context.outer.inputSourceMapTracker.sourcesContentFor(context.source);
+    sourceContent = sourceMapSourcesContent && sourceMapSourcesContent[metadata.source] ?
+      sourceMapSourcesContent :
+      context.outer.sourceReader.sourceAt(context.source);
+  }
 
-    if (hasSuffix)
-      context.column++;
+  return sourceContent ?
+    [metadata.line, metadata.column, metadata.source, sourceContent] :
+    [metadata.line, metadata.column, metadata.source];
+}
 
-    return contextMetadata;
-  },
+function trackSuffix(data, context) {
+  var parts = data.split('\n');
 
-  suffix: function (context) {
-    context.column++;
-  },
+  for (var i = 0, l = parts.length; i < l; i++) {
+    var part = parts[i];
+    var cursor = 0;
 
-  track: function (data, context) {
-    var parts = data.split('\n');
+    if (i > 0) {
+      context.line++;
+      context.column = 0;
+    }
 
-    for (var i = 0, l = parts.length; i < l; i++) {
-      var part = parts[i];
-      var cursor = 0;
+    while (true) {
+      var next = part.indexOf(escapePrefix, cursor);
 
-      if (i > 0) {
-        context.line++;
-        context.column = 0;
+      if (next == -1) {
+        context.column += part.substring(cursor).length;
+        break;
       }
 
-      while (true) {
-        var next = part.indexOf('__ESCAPED_', cursor);
-
-        if (next == -1) {
-          context.column += part.substring(cursor).length;
-          break;
-        }
-
-        context.column += next - cursor;
-        cursor += next - cursor;
+      context.column += next - cursor;
+      cursor += next - cursor;
 
-        var escaped = part.substring(next, part.indexOf('__', next + 1) + 2);
-        var encodedValues = escaped.substring(escaped.indexOf('(') + 1, escaped.indexOf(')')).split(',');
-        context.line += ~~encodedValues[0];
-        context.column = (~~encodedValues[0] === 0 ? context.column : 0) + ~~encodedValues[1];
-        cursor += escaped.length;
-      }
+      var escaped = part.substring(next, part.indexOf('__', next + 1) + 2);
+      var encodedValues = escaped.substring(escaped.indexOf('(') + 1, escaped.indexOf(')')).split(',');
+      context.line += ~~encodedValues[0];
+      context.column = (~~encodedValues[0] === 0 ? context.column : 0) + ~~encodedValues[1];
+      cursor += escaped.length;
     }
   }
-};
+}
+
+function track(data, context, snapshotMetadata, fallbacks) {
+  var untracked = trackPrefix(data, context, snapshotMetadata);
+  var metadata = snapshotMetadata ?
+    snapshot(untracked, context, fallbacks) :
+    [];
+
+  if (untracked)
+    trackSuffix(untracked, context);
+
+  return metadata;
+}
 
-module.exports = SourceMaps;
+module.exports = track;
diff --git a/lib/utils/stringify-tokens.js b/lib/utils/stringify-tokens.js
new file mode 100644 (file)
index 0000000..0caf9f1
--- /dev/null
@@ -0,0 +1,24 @@
+function stringify(values, separator) {
+  var i = 0;
+  var result = [];
+
+  while (values[i]) {
+    result.push(values[i][0]);
+    i++;
+  }
+
+  return result.join(separator);
+}
+
+function stringifyBody(properties) {
+  return stringify(properties, ';');
+}
+
+function stringifySelector(list) {
+  return stringify(list, ',');
+}
+
+module.exports = {
+  body: stringifyBody,
+  selector: stringifySelector
+};
index cd2db25..56f2191 100644 (file)
@@ -4,7 +4,7 @@ var SelectorTokenizer = require('../../lib/selectors/tokenizer');
 var extractor = require('../../lib/properties/extractor');
 
 function buildToken(source) {
-  return new SelectorTokenizer({ options: {} }, true, false).toTokens(source)[0];
+  return new SelectorTokenizer({ options: {} }, false).toTokens(source)[0];
 }
 
 vows.describe(extractor)
@@ -24,21 +24,21 @@ vows.describe(extractor)
     'one property': {
       'topic': extractor(buildToken('a{color:red}')),
       'has no properties': function (tokens) {
-        assert.deepEqual(tokens, [['color', 'red', 'color', 'color:red', ['a'], true]]);
+        assert.deepEqual(tokens, [['color', 'red', 'color', ['color:red'], [['a']], true]]);
       }
     },
     'one property - complex selector': {
       'topic': extractor(buildToken('.one{color:red}')),
       'has no properties': function (tokens) {
-        assert.deepEqual(tokens, [['color', 'red', 'color', 'color:red', ['.one'], false]]);
+        assert.deepEqual(tokens, [['color', 'red', 'color', ['color:red'], [['.one']], false]]);
       }
     },
     'two properties': {
       'topic': extractor(buildToken('a{color:red;display:block}')),
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, [
-          ['color', 'red', 'color', 'color:red', ['a'], true],
-          ['display', 'block', 'display', 'display:block', ['a'], true]
+          ['color', 'red', 'color', ['color:red'], [['a']], true],
+          ['display', 'block', 'display', ['display:block'], [['a']], true]
         ]);
       }
     },
@@ -46,9 +46,9 @@ vows.describe(extractor)
       'topic': extractor(buildToken('@media{a{color:red;display:block}p{color:red}}')),
       'has no properties': function (tokens) {
         assert.deepEqual(tokens, [
-          ['color', 'red', 'color', 'color:red', ['a'], true],
-          ['display', 'block', 'display', 'display:block', ['a'], true],
-          ['color', 'red', 'color', 'color:red', ['p'], true]
+          ['color', 'red', 'color', ['color:red'], [['a']], true],
+          ['display', 'block', 'display', ['display:block'], [['a']], true],
+          ['color', 'red', 'color', ['color:red'], [['p']], true]
         ]);
       }
     }
@@ -58,43 +58,43 @@ vows.describe(extractor)
       'vendor prefix': {
         'topic': extractor(buildToken('a{-moz-transform:none}')),
         'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['-moz-transform', 'none', 'transform', '-moz-transform:none', ['a'], true]]);
+          assert.deepEqual(tokens, [['-moz-transform', 'none', 'transform', ['-moz-transform:none'], [['a']], true]]);
         }
       },
       'list-style': {
         'topic': extractor(buildToken('a{list-style:none}')),
         'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['list-style', 'none', 'list-style', 'list-style:none', ['a'], true]]);
+          assert.deepEqual(tokens, [['list-style', 'none', 'list-style', ['list-style:none'], [['a']], true]]);
         }
       },
       'border-radius': {
         'topic': extractor(buildToken('a{border-top-left-radius:none}')),
         'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['border-top-left-radius', 'none', 'border-radius', 'border-top-left-radius:none', ['a'], true]]);
+          assert.deepEqual(tokens, [['border-top-left-radius', 'none', 'border-radius', ['border-top-left-radius:none'], [['a']], true]]);
         }
       },
       'vendor prefixed border-radius': {
         'topic': extractor(buildToken('a{-webkit-border-top-left-radius:none}')),
         'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['-webkit-border-top-left-radius', 'none', 'border-radius', '-webkit-border-top-left-radius:none', ['a'], true]]);
+          assert.deepEqual(tokens, [['-webkit-border-top-left-radius', 'none', 'border-radius', ['-webkit-border-top-left-radius:none'], [['a']], true]]);
         }
       },
       'border-image': {
         'topic': extractor(buildToken('a{border-image-width:2px}')),
         'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['border-image-width', '2px', 'border-image', 'border-image-width:2px', ['a'], true]]);
+          assert.deepEqual(tokens, [['border-image-width', '2px', 'border-image', ['border-image-width:2px'], [['a']], true]]);
         }
       },
       'border-top': {
         'topic': extractor(buildToken('a{border-top-style:none}')),
         'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['border-top-style', 'none', 'border-top', 'border-top-style:none', ['a'], true]]);
+          assert.deepEqual(tokens, [['border-top-style', 'none', 'border-top', ['border-top-style:none'], [['a']], true]]);
         }
       },
       'text-shadow': {
         'topic': extractor(buildToken('a{text-shadow:none}')),
         'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['text-shadow', 'none', 'text-shadow', 'text-shadow:none', ['a'], true]]);
+          assert.deepEqual(tokens, [['text-shadow', 'none', 'text-shadow', ['text-shadow:none'], [['a']], true]]);
         }
       }
     }
index 6b47255..4ee8787 100644 (file)
@@ -7,7 +7,7 @@ var canReorder = require('../../lib/properties/reorderable').canReorder;
 var canReorderSingle = require('../../lib/properties/reorderable').canReorderSingle;
 
 function propertiesIn(source) {
-  return extractProperties(new SelectorTokenizer({ options: {} }, true, false).toTokens(source)[0]);
+  return extractProperties(new SelectorTokenizer({ options: {} }, false).toTokens(source)[0]);
 }
 
 vows.describe(canReorder)
index b7f03b8..19548fa 100644 (file)
@@ -15,7 +15,7 @@ function selectorContext(group, specs, options) {
       var tokens = new Tokenizer({ options: {} }).toTokens(source);
       new SimpleOptimizer(options).optimize(tokens);
 
-      assert.deepEqual(tokens[0] ? tokens[0].value : null, selectors);
+      assert.deepEqual(tokens[0] ? tokens[0][1] : null, selectors);
     };
   }
 
@@ -38,7 +38,7 @@ function propertyContext(group, specs, options) {
     return function (source) {
       var tokens = new Tokenizer({ options: {} }).toTokens(source);
       new SimpleOptimizer(options).optimize(tokens);
-      var value = tokens[0].body.map(function (property) { return property.value; });
+      var value = tokens[0] ? tokens[0][2].map(function (property) { return property[0]; }) : null;
 
       assert.deepEqual(value, selectors);
     };
@@ -59,23 +59,23 @@ vows.describe(SimpleOptimizer)
     selectorContext('default', {
       'optimized': [
         'a{}',
-        [{ value: 'a' }]
+        null
       ],
       'whitespace': [
-        ' div  > span{}',
-        [{ value: 'div>span' }]
+        ' div  > span{color:red}',
+        [['div>span']]
       ],
       'line breaks': [
-        ' div  >\n\r\n span{}',
-        [{ value: 'div>span' }]
+        ' div  >\n\r\n span{color:red}',
+        [['div>span']]
       ],
       '+html': [
         '*+html .foo{display:inline}',
         null
       ],
       'adjacent nav': [
-        'div + nav{}',
-        [{ value: 'div+nav' }]
+        'div + nav{color:red}',
+        [['div+nav']]
       ]
     })
   )
@@ -91,7 +91,7 @@ vows.describe(SimpleOptimizer)
       ],
       '+html - complex': [
         '*+html .foo,.bar{display:inline}',
-        [{ value: '.bar' }]
+        [['.bar']]
       ]
     }, { compatibility: 'ie8' })
   )
@@ -99,23 +99,23 @@ vows.describe(SimpleOptimizer)
     selectorContext('ie7', {
       '+html': [
         '*+html .foo{display:inline}',
-        [{ value: '*+html .foo' }]
+        [['*+html .foo']]
       ],
       '+html - complex': [
         '*+html .foo,.bar{display:inline}',
-        [{ value: '*+html .foo' }, { value: '.bar' }]
+        [['*+html .foo'], ['.bar']]
       ]
     }, { compatibility: 'ie7' })
   )
   .addBatch(
     selectorContext('+adjacentSpace', {
       'with whitespace': [
-        'div + nav{}',
-        [{ value: 'div+ nav' }]
+        'div + nav{color:red}',
+        [['div+ nav']]
       ],
       'without whitespace': [
-        'div+nav{}',
-        [{ value: 'div+ nav' }]
+        'div+nav{color:red}',
+        [['div+ nav']]
       ]
     }, { compatibility: { selectors: { adjacentSpace: true } } })
   )
@@ -307,11 +307,11 @@ vows.describe(SimpleOptimizer)
     propertyContext('ie hacks', {
       'underscore': [
         'a{_width:100px}',
-        []
+        null
       ],
       'star': [
         'a{*width:100px}',
-        []
+        null
       ]
     })
   )
index 95f41e9..d0bbee8 100644 (file)
@@ -13,35 +13,39 @@ var inputMap = fs.readFileSync(inputMapPath, 'utf-8');
 function sourceMapContext(group, specs) {
   var ctx = {};
 
-  function tokenizedContext(target, index) {
+  function tokenizedContext(target) {
     return function (tokenized) {
-      assert.deepEqual(tokenized[index], target);
+      assert.deepEqual(tokenized, target);
+    };
+  }
+
+  function toTokens(source) {
+    return function () {
+      return new Tokenizer({
+        sourceTracker: sourceTracker,
+        sourceReader: sourceReader,
+        inputSourceMapTracker: inputSourceMapTracker,
+        options: {}
+      }, true).toTokens(source);
     };
   }
 
   for (var test in specs) {
-    for (var i = 0; i < specs[test][1].length; i++) {
-      var target = specs[test][1][i];
-      var sourceTracker = new SourceTracker();
-      var sourceReader = new SourceReader();
-      var inputSourceMapTracker = new InputSourceMapTracker({
-        options: { inliner: {} },
-        errors: {},
-        sourceTracker: sourceTracker
-      });
+    var target = specs[test][1];
+    var sourceTracker = new SourceTracker();
+    var sourceReader = new SourceReader();
+    var inputSourceMapTracker = new InputSourceMapTracker({
+      options: { inliner: {} },
+      errors: {},
+      sourceTracker: sourceTracker
+    });
 
-      ctx[group + ' ' + test + ' - #' + (i + 1)] = {
-        topic: typeof specs[test][0] == 'function' ?
-          specs[test][0]() :
-          new Tokenizer({
-            sourceTracker: sourceTracker,
-            sourceReader: sourceReader,
-            inputSourceMapTracker: inputSourceMapTracker,
-            options: {}
-          }, false, true).toTokens(specs[test][0]),
-        tokenized: tokenizedContext(target, i)
-      };
-    }
+    ctx[group + ' ' + test] = {
+      topic: typeof specs[test][0] == 'function' ?
+        specs[test][0] :
+        toTokens(specs[test][0]),
+      tokenized: tokenizedContext(target)
+    };
   }
 
   return ctx;
@@ -52,91 +56,90 @@ vows.describe('source-maps/analyzer')
     sourceMapContext('selectors', {
       'single': [
         'a{}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-          body: []
-        }]
+        [
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            []
+          ]
+        ]
       ],
       'double': [
         'a,div{}',
-        [{
-          kind: 'selector',
-          value: [
-            { value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } },
-            { value: 'div', metadata: { original: { line: 1, column: 2 }, source: undefined } }
-          ],
-          body: []
-        }]
+        [
+          [
+            'selector',
+            [
+              ['a', 1, 0, undefined],
+              ['div', 1, 2, undefined]
+            ],
+            []
+          ]
+        ]
       ],
       'double with whitespace': [
         ' a,\n\ndiv{}',
-        [{
-          kind: 'selector',
-          value: [
-            { value: 'a', metadata: { original: { line: 1, column: 1 }, source: undefined } },
-            { value: '\n\ndiv', metadata: { original: { line: 3, column: 0 }, source: undefined } }
-          ],
-          body: []
-        }]
+        [
+          [
+            'selector',
+            [['a', 1, 1, undefined], ['\n\ndiv', 3, 0, undefined]],
+            []
+          ]
+        ]
       ],
       'triple': [
         'a,div,p{}',
-        [{
-          kind: 'selector',
-          value: [
-            { value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } },
-            { value: 'div', metadata: { original: { line: 1, column: 2 }, source: undefined } },
-            { value: 'p', metadata: { original: { line: 1, column: 6 }, source: undefined } }
-          ],
-          body: []
-        }]
+        [
+          [
+            'selector',
+            [['a', 1, 0, undefined], ['div', 1, 2, undefined], ['p', 1, 6, undefined]],
+            []
+          ]
+        ]
       ],
       'triple with whitespace': [
         ' a,\n\ndiv\na,\n p{}',
-        [{
-          kind: 'selector',
-          value: [
-            { value: 'a', metadata: { original: { line: 1, column: 1 }, source: undefined } },
-            { value: '\n\ndiv\na', metadata: { original: { line: 3, column: 0 }, source: undefined } },
-            { value: '\n p', metadata: { original: { line: 5, column: 1 }, source: undefined } }
-          ],
-          body: []
-        }]
+        [
+          [
+            'selector',
+            [['a', 1, 1, undefined], ['\n\ndiv\na', 3, 0, undefined], ['\n p', 5, 1, undefined]],
+            []
+          ]
+        ]
       ],
       'two': [
         'a{}div{}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-            body: []
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'div', metadata: { original: { line: 1, column: 3 }, source: undefined } }],
-            body: []
-          }
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            []
+          ],
+          [
+            'selector',
+            [['div', 1, 3, undefined]],
+            []
+          ]
         ]
       ],
       'three with whitespace and breaks': [
         'a {}\n\ndiv{}\n \n  p{}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a ', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-            body: []
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'div', metadata: { original: { line: 3, column: 0 }, source: undefined } }],
-            body: []
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'p', metadata: { original: { line: 5, column: 2 }, source: undefined } }],
-            body: []
-          }
+          [
+            'selector',
+            [['a ', 1, 0, undefined]],
+            []
+          ],
+          [
+            'selector',
+            [['div', 3, 0, undefined]],
+            []
+          ],
+          [
+            'selector',
+            [['p', 5, 2, undefined]],
+            []
+          ]
         ]
       ]
     })
@@ -145,78 +148,84 @@ vows.describe('source-maps/analyzer')
     sourceMapContext('properties', {
       'single': [
         'a{color:red}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-          body: [{ value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }]
-        }]
+        [
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            [['color:red', 1, 2, undefined]]
+          ]
+        ]
       ],
       'double': [
         'a{color:red;border:none}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-          body: [
-            { value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } },
-            { value: 'border:none', metadata: { original: { line: 1, column: 12 }, source: undefined } }
+        [
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            [
+              ['color:red', 1, 2, undefined],
+              ['border:none', 1, 12, undefined]
+            ]
           ]
-        }]
+        ]
       ],
       'triple with whitespace': [
         'a{color:red;\nborder:\nnone;\n\n  display:block}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-          body: [
-            { value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } },
-            { value: 'border:none', metadata: { original: { line: 2, column: 0 }, source: undefined } },
-            { value: 'display:block', metadata: { original: { line: 5, column: 2 }, source: undefined } }
+        [
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            [
+              ['color:red', 1, 2, undefined],
+              ['border:none', 2, 0, undefined],
+              ['display:block', 5, 2, undefined]
+            ]
           ]
-        }]
+        ]
       ],
       'two declarations': [
         'a{color:red}div{color:blue}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-            body: [{ value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }]
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'div', metadata: { original: { line: 1, column: 12 }, source: undefined } }],
-            body: [{ value: 'color:blue', metadata: { original: { line: 1, column: 16 }, source: undefined } }]
-          }
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            [['color:red', 1, 2, undefined]]
+          ],
+          [
+            'selector',
+            [['div', 1, 12, undefined]],
+            [['color:blue', 1, 16, undefined]]
+          ]
         ]
       ],
       'two declarations with whitespace': [
         'a{color:red}\n div{color:blue}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-            body: [{ value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }]
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'div', metadata: { original: { line: 2, column: 1 }, source: undefined } }],
-            body: [{ value: 'color:blue', metadata: { original: { line: 2, column: 5 }, source: undefined } }]
-          }
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            [['color:red', 1, 2, undefined]]
+          ],
+          [
+            'selector',
+            [['div', 2, 1, undefined]],
+            [['color:blue', 2, 5, undefined]]
+          ]
         ]
       ],
       'two declarations with whitespace and ending semicolon': [
         'a{color:red;\n}\n div{color:blue}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-            body: [{ value: 'color:red', metadata: { original: { line: 1, column: 2 }, source: undefined } }]
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'div', metadata: { original: { line: 3, column: 1 }, source: undefined } }],
-            body: [{ value: 'color:blue', metadata: { original: { line: 3, column: 5 }, source: undefined } }]
-          }
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            [['color:red', 1, 2, undefined]]
+          ],
+          [
+            'selector',
+            [['div', 3, 1, undefined]],
+            [['color:blue', 3, 5, undefined]]
+          ]
         ]
       ]
     })
@@ -226,36 +235,34 @@ vows.describe('source-maps/analyzer')
       '@import': [
         'a{}@import \n"test.css";\n\na{color:red}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-            body: []
-          },
-          {
-            kind: 'at-rule',
-            value: '@import \n"test.css";',
-            metadata: { original: { line: 1, column: 3 }, source: undefined }
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 4, column: 0 }, source: undefined } }],
-            body: [{ value: 'color:red', metadata: { original: { line: 4, column: 2 }, source: undefined } }]
-          }
+          [
+            'selector',
+            [['a', 1, 0, undefined]],
+            []
+          ],
+          [
+            'at-rule',
+            ['@import \n"test.css";', 1, 3, undefined]
+          ],
+          [
+            'selector',
+            [['a', 4, 0, undefined]],
+            [['color:red', 4, 2, undefined]]
+          ]
         ]
       ],
       '@charset': [
         '@charset "utf-8";a{color:red}',
         [
-          {
-            kind: 'at-rule',
-            value: '@charset "utf-8";',
-            metadata: { original: { line: 1, column: 0 }, source: undefined }
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 1, column: 18 }, source: undefined } }],
-            body: [{ value: 'color:red', metadata: { original: { line: 1, column: 20 }, source: undefined } }]
-          }
+          [
+            'at-rule',
+            ['@charset "utf-8";', 1, 0, undefined]
+          ],
+          [
+            'selector',
+            [['a', 1, 18, undefined]],
+            [['color:red', 1, 20, undefined]]
+          ]
         ]
       ]
     })
@@ -265,99 +272,91 @@ vows.describe('source-maps/analyzer')
       '@media - simple': [
         '@media (min-width:980px){a{color:red}}',
         [
-          {
-            kind: 'block',
-            value: '@media (min-width:980px)',
-            metadata: { original: { line: 1, column: 0 }, source: undefined },
-            isFlatBlock: false,
-            body: [{
-              kind: 'selector',
-              value: [{ value: 'a', metadata: { original: { line: 1, column: 25 }, source: undefined } }],
-              body: [{ value: 'color:red', metadata: { original: { line: 1, column: 27 }, source: undefined } }]
-            }]
-          }
+          [
+            'block',
+            ['@media (min-width:980px)', 1, 0, undefined],
+            [
+              [
+                'selector',
+                [['a', 1, 25, undefined]],
+                [['color:red', 1, 27, undefined]]
+              ]
+            ]
+          ]
         ]
       ],
       '@media - with whitespace': [
         '@media (\nmin-width:980px)\n{\na{\ncolor:\nred}p{}}',
         [
-          {
-            kind: 'block',
-            value: '@media (\nmin-width:980px)',
-            metadata: { original: { line: 1, column: 0 }, source: undefined },
-            isFlatBlock: false,
-            body: [
-              {
-                kind: 'selector',
-                value: [{ value: 'a', metadata: { original: { line: 4, column: 0 }, source: undefined } }],
-                body: [{ value: 'color:red', metadata: { original: { line: 5, column: 0 }, source: undefined } }]
-              },
-              {
-                kind: 'selector',
-                value: [{ value: 'p', metadata: { original: { line: 6, column: 4 }, source: undefined } }],
-                body: []
-              }
+          [
+            'block',
+            ['@media (\nmin-width:980px)', 1, 0, undefined],
+            [
+              [
+                'selector',
+                [['a', 4, 0, undefined]],
+                [['color:red', 5, 0, undefined]]
+              ],
+              [
+                'selector',
+                [['p', 6, 4, undefined]],
+                []
+              ]
             ]
-          }
+          ]
         ]
       ],
       '@media - stray whitespace at end': [
         '@media (min-width:980px){a{color:red} }p{color:red}',
         [
-          {
-            kind: 'block',
-            value: '@media (min-width:980px)',
-            metadata: { original: { line: 1, column: 0 }, source: undefined },
-            isFlatBlock: false,
-            body: [
-              {
-                kind: 'selector',
-                value: [{ value: 'a', metadata: { original: { line: 1, column: 25 }, source: undefined } }],
-                body: [{ value: 'color:red', metadata: { original: { line: 1, column: 27 }, source: undefined } }]
-              }
+          [
+            'block',
+            ['@media (min-width:980px)', 1, 0, undefined],
+            [
+              [
+                'selector',
+                [['a', 1, 25, undefined]],
+                [['color:red', 1, 27, undefined]]
+              ]
             ]
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'p', metadata: { original: { line: 1, column: 39 }, source: undefined } }],
-            body: [{ value: 'color:red', metadata: { original: { line: 1, column: 41 }, source: undefined } }]
-          }
+          ],
+          [
+            'selector',
+            [['p', 1, 39, undefined]],
+            [['color:red', 1, 41, undefined]]
+          ]
         ]
       ],
       '@font-face': [
         '@font-face{font-family: "Font";\nsrc: url("font.ttf");\nfont-weight: normal;font-style: normal}a{}',
         [
-          {
-            kind: 'block',
-            value: '@font-face',
-            metadata: { original: { line: 1, column: 0 }, source: undefined },
-            isFlatBlock: true,
-            body: [
-              { value: 'font-family:"Font"', metadata: { original: { line: 1, column: 11 }, source: undefined } },
-              { value: 'src:url("font.ttf")', metadata: { original: { line: 2, column: 0 }, source: undefined } },
-              { value: 'font-weight:normal', metadata: { original: { line: 3, column: 0 }, source: undefined } },
-              { value: 'font-style:normal', metadata: { original: { line: 3, column: 20 }, source: undefined } }
+          [
+            'flat-block',
+            ['@font-face', 1, 0, undefined],
+            [
+              ['font-family:"Font"', 1, 11, undefined],
+              ['src:url("font.ttf")', 2, 0, undefined],
+              ['font-weight:normal', 3, 0, undefined],
+              ['font-style:normal', 3, 20, undefined]
             ]
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 3, column: 39 }, source: undefined } }],
-            body: []
-          }
+          ],
+          [
+            'selector',
+            [['a', 3, 39, undefined]],
+            []
+          ]
         ]
       ],
       '@font-face with breaks': [
         '\n@font-face\n{font-family: "Font"}',
         [
-          {
-            kind: 'block',
-            value: '@font-face',
-            metadata: { original: { line: 2, column: 0 }, source: undefined },
-            isFlatBlock: true,
-            body: [
-              { value: 'font-family:"Font"', metadata: { original: { line: 3, column: 1 }, source: undefined } }
+          [
+            'flat-block',
+            ['@font-face', 2, 0, undefined],
+            [
+              ['font-family:"Font"', 3, 1, undefined]
             ]
-          }
+          ]
         ]
       ]
     })
@@ -367,85 +366,86 @@ vows.describe('source-maps/analyzer')
       'top-level': [
         '__ESCAPED_COMMENT_CLEAN_CSS0(0, 5)__a{}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 1, column: 5 }, source: undefined } }],
-            body: []
-          }
+          [
+            'selector',
+            [['a', 1, 5, undefined]],
+            []
+          ]
         ]
       ],
       'top-level with line breaks': [
         '__ESCAPED_COMMENT_CLEAN_CSS0(2, 5)__a{}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 3, column: 5 }, source: undefined } }],
-            body: []
-          }
+          [
+            'selector',
+            [['a', 3, 5, undefined]],
+            []
+          ]
         ]
       ],
       'in selectors': [
         'div[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__],div[data-id=__ESCAPED_FREE_TEXT_CLEAN_CSS1(0,7)__]{color:red}',
-        [{
-          kind: 'selector',
-          value: [
-            { value: 'div[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__]', metadata: { original: { line: 1, column: 0 }, source: undefined } },
-            { value: 'div[data-id=__ESCAPED_FREE_TEXT_CLEAN_CSS1(0,7)__]', metadata: { original: { line: 2, column: 5 }, source: undefined } }
-          ],
-          body: [{ value: 'color:red', metadata: { original: { line: 2, column: 26 }, source: undefined } }]
-        }]
+        [
+          [
+            'selector',
+            [
+              ['div[data-type=__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__]', 1, 0, undefined],
+              ['div[data-id=__ESCAPED_FREE_TEXT_CLEAN_CSS1(0,7)__]', 2, 5, undefined]
+            ],
+            [['color:red', 2, 26, undefined]]
+          ]
+        ]
       ],
       'in properties': [
         'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__background:url(__ESCAPED_URL_CLEAN_CSS0(0,20)__);color:blue}a{font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__;color:red}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'div', metadata: { original: { line: 1, column: 0 }, source: undefined } }],
-            body: [
-              { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__', metadata: { original: { line: 1, column: 4 }, source: undefined }},
-              { value: 'background:url(__ESCAPED_URL_CLEAN_CSS0(0,20)__)', metadata: { original: { line: 3, column: 5 }, source: undefined } },
-              { value: 'color:blue', metadata: { original: { line: 3, column: 42 }, source: undefined } }
+          [
+            'selector',
+            [['div', 1, 0, undefined]],
+            [
+              ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(2,5)__', 1, 4, undefined],
+              ['background:url(__ESCAPED_URL_CLEAN_CSS0(0,20)__)', 3, 5, undefined],
+              ['color:blue', 3, 42, undefined]
             ]
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'a', metadata: { original: { line: 3, column: 53 }, source: undefined } }],
-            body: [
-              { value: 'font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__', metadata: { original: { line: 3, column: 55 }, source: undefined } },
-              { value: 'color:red', metadata: { original: { line: 4, column: 4 }, source: undefined } }
+          ],
+          [
+            'selector',
+            [['a', 3, 53, undefined]],
+            [
+              ['font-family:__ESCAPED_FREE_TEXT_CLEAN_CSS0(1,3)__', 3, 55, undefined],
+              ['color:red', 4, 4, undefined]
             ]
-          }
+          ]
         ]
       ],
       'in at-rules': [
         '@charset __ESCAPED_FREE_TEXT_CLEAN_CSS0(1, 5)__;div{}',
         [
-          {
-            kind: 'at-rule',
-            value: '@charset __ESCAPED_FREE_TEXT_CLEAN_CSS0(1, 5)__;',
-            metadata: { original: { line: 1, column: 0 }, source: undefined }
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'div', metadata: { original: { line: 2, column: 7 }, source: undefined } }],
-            body: []
-          }
+          [
+            'at-rule',
+            ['@charset __ESCAPED_FREE_TEXT_CLEAN_CSS0(1, 5)__;', 1, 0, undefined]
+          ],
+          [
+            'selector',
+            [['div', 2, 7, undefined]],
+            []
+          ]
         ]
       ],
       'in blocks': [
         '@media (__ESCAPED_COMMENT_CLEAN_CSS0(2, 1)__min-width:980px){a{color:red}}',
         [
-          {
-            kind: 'block',
-            value: '@media (__ESCAPED_COMMENT_CLEAN_CSS0(2, 1)__min-width:980px)',
-            metadata: { original: { line: 1, column: 0 }, source: undefined },
-            isFlatBlock: false,
-            body: [{
-              kind: 'selector',
-              value: [{ value: 'a', metadata: { original: { line: 3, column: 18 }, source: undefined } }],
-              body: [{ value: 'color:red', metadata: { original: { line: 3, column: 20 }, source: undefined } }]
-            }]
-          }
+          [
+            'block',
+            ['@media (__ESCAPED_COMMENT_CLEAN_CSS0(2, 1)__min-width:980px)', 1, 0, undefined],
+            [
+              [
+                'selector',
+                [['a', 3, 18, undefined]],
+                [['color:red', 3, 20, undefined]]
+              ]
+            ]
+          ]
         ]
       ]
     })
@@ -457,43 +457,41 @@ vows.describe('source-maps/analyzer')
           var tracker = new SourceTracker();
           var reader = new SourceReader();
           var inputTracker = new InputSourceMapTracker({ options: { inliner: {} }, errors: {}, sourceTracker: tracker });
-          var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, false, true);
+          var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, true);
 
           var data = tracker.store('one.css', 'a{}');
           return tokenizer.toTokens(data);
         },
-        [{
-          kind: 'selector',
-          value: [{ value: 'a', metadata: { original: { line: 1, column: 0 }, source: 'one.css' } }],
-          body: []
-        }]
+        [
+          [
+            'selector',
+            [['a', 1, 0, 'one.css']],
+            []
+          ]
+        ]
       ],
       'two': [
         function () {
           var tracker = new SourceTracker();
           var reader = new SourceReader();
           var inputTracker = new InputSourceMapTracker({ options: { inliner: {} }, errors: {}, sourceTracker: tracker });
-          var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, false, true);
+          var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, true);
 
           var data1 = tracker.store('one.css', 'a{}');
           var data2 = tracker.store('two.css', '\na{color:red}');
           return tokenizer.toTokens(data1 + data2);
         },
         [
-          {
-            kind: 'selector',
-            value: [
-              { value: 'a', metadata: { original: { line: 1, column: 0 }, source: 'one.css' } }
-            ],
-            body: []
-          },
-          {
-            kind: 'selector',
-            value: [
-              { value: 'a', metadata: { original: { line: 2, column: 0 }, source: 'two.css' } }
-            ],
-            body: [{ value: 'color:red', metadata: { original: { line: 2, column: 2 }, source: 'two.css' } }]
-          }
+          [
+            'selector',
+            [['a', 1, 0, 'one.css']],
+            []
+          ],
+          [
+            'selector',
+            [['a', 2, 0, 'two.css']],
+            [['color:red', 2, 2, 'two.css']]
+          ]
         ]
       ]
     })
@@ -507,14 +505,16 @@ vows.describe('source-maps/analyzer')
           var inputTracker = new InputSourceMapTracker({ options: { inliner: {}, sourceMap: inputMap, options: {} }, errors: {}, sourceTracker: tracker });
           inputTracker.track('', function () {});
 
-          var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, false, true);
+          var tokenizer = new Tokenizer({ sourceTracker: tracker, sourceReader: reader, inputSourceMapTracker: inputTracker, options: {} }, true);
           return tokenizer.toTokens('div > a {\n  color: red;\n}');
         },
-        [{
-          kind: 'selector',
-          value: [{ value: 'div > a ', metadata: { original: { line: 1, column: 4 }, source: 'styles.less' } }],
-          body: [{ value: 'color:red', metadata: { original: { line: 2, column: 2 }, source: 'styles.less' } }]
-        }]
+        [
+          [
+            'selector',
+            [['div > a ', 1, 4, 'styles.less']],
+            [['color:red', 2, 2, 'styles.less']]
+          ]
+        ]
       ]
     })
   )
index cc04461..1111958 100644 (file)
@@ -3,13 +3,18 @@ var assert = require('assert');
 var Tokenizer = require('../../lib/selectors/tokenizer');
 var SourceTracker = require('../../lib/utils/source-tracker');
 
-function tokenizerContext(name, specs, addMetadata) {
+function tokenizerContext(name, specs) {
   var ctx = {};
 
   function tokenized(target) {
     return function (source) {
-      var tokenized = new Tokenizer({ options: {}, sourceTracker: new SourceTracker(), warnings: [] }, addMetadata).toTokens(source);
-      assert.deepEqual(target, tokenized);
+      var tokenized = new Tokenizer({
+        options: {},
+        sourceTracker: new SourceTracker(),
+        warnings: []
+      }).toTokens(source);
+
+      assert.deepEqual(tokenized, target);
     };
   }
 
@@ -40,209 +45,145 @@ vows.describe(Tokenizer)
       ],
       'an empty selector': [
         'a{}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a' }],
-          body: []
-        }]
+        [
+          ['selector', [['a']], []]
+        ]
       ],
       'an empty selector with whitespace': [
         'a{ \n  }',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a' }],
-          body: []
-        }]
+        [
+          ['selector', [['a']], []]
+        ]
       ],
       'a selector': [
         'a{color:red}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a' }],
-          body: [{ value: 'color:red' }]
-        }]
+        [
+          ['selector', [['a']], [['color:red']]]
+        ]
       ],
       'a selector with whitespace': [
         'a {color:red;\n\ndisplay :\r\n  block }',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a ' }],
-          body: [
-            { value: 'color:red' },
-            { value: 'display:block'
-          }]
-        }]
+        [
+          ['selector', [['a ']], [['color:red'], ['display:block']]]
+        ]
       ],
       'a selector with suffix whitespace': [
         'div a{color:red\r\n}',
-        [{ kind: 'selector', value: [{ value: 'div a' }], body: [{ value: 'color:red' }] }]
+        [
+          ['selector', [['div a']], [['color:red']]]
+        ]
       ],
       'a selector with whitespace in functions': [
         'a{color:rgba( 255, 255, 0, 0.5  )}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a' }],
-          body: [{ value: 'color:rgba(255,255,0,0.5)' }]
-        }]
+        [
+          ['selector', [['a']], [['color:rgba(255,255,0,0.5)']]]
+        ]
       ],
       'a selector with empty properties': [
         'a{color:red; ; ; ;}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a' }],
-          body: [{ value: 'color:red' }]
-        }]
+        [
+          ['selector', [['a']], [['color:red']]]
+        ]
       ],
       'a selector with quoted attribute': [
         'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]{color:red}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]' }],
-          body: [{ value: 'color:red' }]
-        }]
+        [
+          ['selector', [['a[data-kind=__ESCAPED_FREE_TEXT_CLEAN_CSS0__]']], [['color:red']]]
+        ]
       ],
       'a selector with escaped quote': [
         '.this-class\\\'s-got-an-apostrophe{}',
-        [{
-          kind: 'selector',
-          value: [{ value: '.this-class\\\'s-got-an-apostrophe' }],
-          body: []
-        }]
+        [
+          ['selector', [['.this-class\\\'s-got-an-apostrophe']], []]
+        ]
       ],
       'a double selector': [
         'a,\n\ndiv.class > p {color:red}',
-        [{
-          kind: 'selector',
-          value: [
-            { value: 'a' },
-            { value: '\n\ndiv.class > p ' }
-          ],
-          body: [{ value: 'color:red' }]
-        }]
+        [
+          ['selector', [['a'], ['\n\ndiv.class > p ']], [['color:red']]]
+        ]
       ],
       'two selectors': [
         'a{color:red}div{color:blue}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a' }],
-            body: [{ value: 'color:red' }]
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'div' }],
-            body: [{ value: 'color:blue' }]
-          }
+          ['selector', [['a']], [['color:red']]],
+          ['selector', [['div']], [['color:blue']]],
         ]
       ],
       'two comments and a selector separated by newline': [
         '__ESCAPED_COMMENT_CLEAN_CSS0__\n__ESCAPED_COMMENT_CLEAN_CSS1__\ndiv{}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'div' }],
-            body: []
-          }
+          ['selector', [['div']], []]
         ]
       ],
       'two properties wrapped between comments': [
         'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__color:red__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'div' }],
-          body: [
-            { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__' },
-            { value: 'color:red' },
-            { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__' }
-          ]
-        }]
+        [
+          ['selector', [['div']], [['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__'], ['color:red'], ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__']]]
+        ]
       ],
       'pseudoselector after an argument one': [
         'div:nth-child(2n):not(.test){}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'div:nth-child(2n):not(.test)' }],
-          body: []
-        }]
+        [
+          ['selector', [['div:nth-child(2n):not(.test)']], []]
+        ]
       ],
       'media query': [
         '@media (min-width:980px){}',
-        [{
-          kind: 'block',
-          value: '@media (min-width:980px)',
-          body: [],
-          isFlatBlock: false
-        }]
+        [
+          ['block', ['@media (min-width:980px)'], []]
+        ]
       ],
       'media query with selectors': [
         '@media (min-width:980px){a{color:red}}',
-        [{
-          kind: 'block',
-          value: '@media (min-width:980px)',
-          body: [{
-            kind: 'selector',
-            value: [{ value: 'a' }],
-            body: [{ value: 'color:red' }]
-          }],
-          isFlatBlock: false
-        }]
+        [
+          ['block', ['@media (min-width:980px)'], [
+            ['selector', [['a']], [['color:red']]]
+          ]]
+        ]
       ],
       '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}}',
-        [{
-          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
-        }]
+        [
+          ['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)'], [
+            ['selector', [['a']], [['color:#000']]]
+          ]]
+        ]
       ],
       'font-face': [
         '@font-face{font-family: fontName;font-size:12px}',
-        [{
-          kind: 'block',
-          value: '@font-face',
-          body: [
-            { value: 'font-family:fontName' },
-            { value: 'font-size:12px' }
-          ],
-          isFlatBlock: true
-        }]
+        [
+          ['flat-block', ['@font-face'], [['font-family:fontName'], ['font-size:12px']]]
+        ]
       ],
       'charset': [
         '@charset \'utf-8\';a{color:red}',
         [
-          {
-            kind: 'at-rule',
-            value: '@charset \'utf-8\';'
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'a' }],
-            body: [{ value: 'color:red' }]
-          }
+          ['at-rule', ['@charset \'utf-8\';']],
+          ['selector', [['a']], [['color:red']]]
         ]
       ],
       'charset after a line break': [
         '\n@charset \n\'utf-8\';',
         [
-          {
-            kind: 'at-rule',
-            value: '@charset \n\'utf-8\';'
-          }
+          ['at-rule', ['@charset \n\'utf-8\';']]
         ]
       ],
       'keyframes with quoted attribute': [
         '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__{}',
-        [{
-          kind: 'block',
-          value: '@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__',
-          body: [],
-          isFlatBlock: false
-        }]
+        [
+          ['block', ['@keyframes __ESCAPED_FREE_TEXT_CLEAN_CSS0__'], []]
+        ]
+      ],
+      'variables': [
+        'a{border:var(--width)var(--style)var(--color)}',
+        [
+          [
+            'selector',
+            [[ 'a' ]],
+            [['border:var(--width) var(--style) var(--color)']]
+          ]
+        ]
       ]
     })
   )
@@ -250,125 +191,16 @@ vows.describe(Tokenizer)
     tokenizerContext('broken', {
       'missing end brace': [
         'a{display:block',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a' }],
-          body: []
-        }]
+        [
+          ['selector', [['a']], []]
+        ]
       ],
       'missing end brace in the middle': [
         'body{color:red;a{color:blue;}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'body' }],
-          body: [{ value: 'color:red' }]
-        }]
-      ]
-    })
-  )
-  .addBatch(
-    tokenizerContext('metadata', {
-      'no content': [
-        '',
-        []
-      ],
-      'an escaped comment': [
-        '__ESCAPED_COMMENT_CLEAN_CSS0__',
-        []
-      ],
-      'an escaped special comment': [
-        '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__',
-        [{ kind: 'text', value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__' }]
-      ],
-      'an empty selector': [
-        'a{}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a' }],
-          body: [],
-          metadata: {
-            body: '',
-            bodiesList: [],
-            selector: 'a',
-            selectorsList: ['a']
-          }
-        }]
-      ],
-      'a double selector': [
-        'a,\n\ndiv.class > p {color:red}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'a' }, { value: '\n\ndiv.class > p ' }],
-          body: [{ value: 'color:red' }],
-          metadata: {
-            body: 'color:red',
-            bodiesList: ['color:red'],
-            selector: 'a,\n\ndiv.class > p ',
-            selectorsList: ['a', '\n\ndiv.class > p ']
-          }
-        }]
-      ],
-      'two selectors': [
-        'a{color:red}div{color:blue}',
         [
-          {
-            kind: 'selector',
-            value: [{ value: 'a' }],
-            body: [{ value: 'color:red' }],
-            metadata: {
-              body: 'color:red',
-              bodiesList: ['color:red'],
-              selector: 'a',
-              selectorsList: ['a']
-            }
-          },
-          {
-            kind: 'selector',
-            value: [{ value: 'div' }],
-            body: [{ value: 'color:blue' }],
-            metadata: {
-              body: 'color:blue',
-              bodiesList: ['color:blue'],
-              selector: 'div',
-              selectorsList: ['div']
-            }
-          }
+          ['selector', [['body']], [['color:red']]]
         ]
-      ],
-      'two properties wrapped between comments': [
-        'div{__ESCAPED_COMMENT_CLEAN_CSS0(0, 5)__color:red__ESCAPED_COMMENT_CLEAN_CSS1(0, 5)__}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'div' }],
-          body: [
-            { value: 'color:red' }
-          ],
-          metadata: {
-            body: 'color:red',
-            bodiesList: ['color:red'],
-            selector: 'div',
-            selectorsList: ['div']
-          }
-        }]
-      ],
-      'two properties wrapped between special comments': [
-        'div{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(0, 5)__color:red__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1(0, 5)__}',
-        [{
-          kind: 'selector',
-          value: [{ value: 'div' }],
-          body: [
-            { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(0, 5)__' },
-            { value: 'color:red' },
-            { value: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1(0, 5)__' }
-          ],
-          metadata: {
-            body: '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(0, 5)__,color:red,__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1(0, 5)__',
-            bodiesList: ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0(0, 5)__', 'color:red', '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1(0, 5)__'],
-            selector: 'div',
-            selectorsList: ['div']
-          }
-        }]
       ]
-    }, true)
+    })
   )
   .export(module);
index 37f3e9b..86e031d 100644 (file)
@@ -1489,7 +1489,7 @@ vows.describe('source-map')
   })
   .addBatch({
     'advanced optimizations': {
-      'new property in smart sort': {
+      'new property in restructuring': {
         'topic': function () {
           return new CleanCSS({ sourceMap: true }).minify('a{color:#000}div{color:red}.one{display:block}.two{display:inline;color:red}');
         },