Fixes all unit tests on top of the new tokenizer.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 13 Nov 2016 10:54:38 +0000 (11:54 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Fri, 16 Dec 2016 10:49:11 +0000 (11:49 +0100)
This involves the following changes too:

* /lib/selectors -> /lib/optimizer;
* new /lib/optimizer/tidy* modules for cleaning up tokens;
* redone URL rewriting as it doesn't require full document scan
  anymore, as it's run right after tokenization;
* compatibility class is now a simple function;
* introducing `readSources` method instead of SourceReader class.

86 files changed:
lib/clean.js
lib/optimizer/advanced.js [moved from lib/selectors/advanced.js with 53% similarity]
lib/optimizer/basic.js [moved from lib/selectors/simple.js with 59% similarity]
lib/optimizer/extract-properties.js [moved from lib/selectors/extractor.js with 62% similarity]
lib/optimizer/is-special.js [moved from lib/selectors/is-special.js with 100% similarity]
lib/optimizer/merge-adjacent.js [new file with mode: 0644]
lib/optimizer/merge-media-queries.js [moved from lib/selectors/merge-media-queries.js with 83% similarity]
lib/optimizer/merge-non-adjacent-by-body.js [moved from lib/selectors/merge-non-adjacent-by-body.js with 67% similarity]
lib/optimizer/merge-non-adjacent-by-selector.js [moved from lib/selectors/merge-non-adjacent-by-selector.js with 85% similarity]
lib/optimizer/reduce-non-adjacent.js [moved from lib/selectors/reduce-non-adjacent.js with 90% similarity]
lib/optimizer/remove-duplicate-media-queries.js [new file with mode: 0644]
lib/optimizer/remove-duplicates.js [moved from lib/selectors/remove-duplicates.js with 82% similarity]
lib/optimizer/reorderable.js [moved from lib/selectors/reorderable.js with 100% similarity]
lib/optimizer/restructure.js [moved from lib/selectors/restructure.js with 89% similarity]
lib/optimizer/tidy-at-rule.js [new file with mode: 0644]
lib/optimizer/tidy-block.js [new file with mode: 0644]
lib/optimizer/tidy-rule-duplicates.js [new file with mode: 0644]
lib/optimizer/tidy-rules.js [new file with mode: 0644]
lib/properties/break-up.js
lib/properties/can-override.js
lib/properties/clone.js
lib/properties/every-combination.js
lib/properties/hack.js [new file with mode: 0644]
lib/properties/has-inherit.js
lib/properties/optimizer.js
lib/properties/override-compactor.js
lib/properties/remove-unused.js
lib/properties/restore-from-optimizing.js
lib/properties/restore.js
lib/properties/shorthand-compactor.js
lib/properties/validator.js
lib/properties/wrap-for-optimizing.js
lib/selectors/clean-up.js [deleted file]
lib/selectors/merge-adjacent.js [deleted file]
lib/selectors/remove-duplicate-media-queries.js [deleted file]
lib/stringifier/helpers.js
lib/stringifier/one-time.js
lib/stringifier/simple.js
lib/stringifier/source-maps.js
lib/tokenizer/marker.js
lib/urls/rebase-config.js [new file with mode: 0644]
lib/urls/rebase.js [deleted file]
lib/urls/reduce.js [deleted file]
lib/urls/rewrite-url.js [new file with mode: 0644]
lib/urls/rewrite.js [deleted file]
lib/utils/compatibility.js
lib/utils/object.js [deleted file]
lib/utils/override.js [new file with mode: 0644]
lib/utils/quote-scanner.js [deleted file]
lib/utils/read-sources.js [new file with mode: 0644]
lib/utils/source-reader.js [deleted file]
lib/utils/source-tracker.js [deleted file]
test/batch-test.js
test/fixtures/big-min.css
test/fixtures/bootstrap-min.css
test/fixtures/font-awesome-ie7-min.css
test/fixtures/issue-308-min.css
test/integration-test.js
test/module-test.js
test/optimizer/advanced-test.js [moved from test/selectors/advanced-test.js with 100% similarity]
test/optimizer/basic-test.js [moved from test/selectors/simple-test.js with 57% similarity]
test/optimizer/extract-properties-test.js [new file with mode: 0644]
test/optimizer/merge-adjacent-test.js [moved from test/selectors/merge-adjacent-test.js with 100% similarity]
test/optimizer/merge-media-queries-test.js [moved from test/selectors/merge-media-queries-test.js with 100% similarity]
test/optimizer/merge-non-adjacent-by-body-test.js [moved from test/selectors/merge-non-adjacent-by-body-test.js with 100% similarity]
test/optimizer/merge-non-adjacent-by-selector-test.js [moved from test/selectors/merge-non-adjacent-by-selector-test.js with 100% similarity]
test/optimizer/reduce-non-adjacent-test.js [moved from test/selectors/reduce-non-adjacent-test.js with 100% similarity]
test/optimizer/remove-duplicate-media-queries-test.js [moved from test/selectors/remove-duplicate-media-queries-test.js with 100% similarity]
test/optimizer/remove-duplicates-test.js [moved from test/selectors/remove-duplicates-test.js with 100% similarity]
test/optimizer/reorderable-test.js [moved from test/selectors/reorderable-test.js with 98% similarity]
test/optimizer/restructure-test.js [moved from test/selectors/restructure-test.js with 100% similarity]
test/properties/break-up-test.js
test/properties/longhand-overriding-test.js
test/properties/optimizer-test.js
test/properties/override-compacting-test.js
test/properties/populate-components-test.js
test/properties/remove-unused-test.js
test/properties/restore-from-optimizing-test.js
test/properties/restore-test.js
test/properties/shorthand-compacting-source-maps-test.js [deleted file]
test/properties/shorthand-compacting-test.js
test/properties/wrap-for-optimizing-test.js
test/selectors/extractor-test.js [deleted file]
test/test-helper.js
test/utils/compatibility-test.js
test/utils/quote-scanner-test.js [deleted file]

index 1fe9390..31ba217 100644 (file)
@@ -5,35 +5,21 @@
  * Copyright (C) 2016 JakubPawlowicz.com
  */
 
-var ImportInliner = require('./imports/inliner');
-var rebaseUrls = require('./urls/rebase');
-
-var tokenize = require('./tokenizer/tokenize');
-var simpleOptimize = require('./selectors/simple');
-var advancedOptimize = require('./selectors/advanced');
-
-var simpleStringify = require('./stringifier/simple');
-var sourceMapStringify = require('./stringifier/source-maps');
-
-var CommentsProcessor = require('./text/comments-processor');
-var ExpressionsProcessor = require('./text/expressions-processor');
-var FreeTextProcessor = require('./text/free-text-processor');
-var UrlsProcessor = require('./text/urls-processor');
-
-var Compatibility = require('./utils/compatibility');
-var InputSourceMapTracker = require('./utils/input-source-map-tracker');
-var SourceTracker = require('./utils/source-tracker');
-var SourceReader = require('./utils/source-reader');
-var Validator = require('./properties/validator');
-
 var fs = require('fs');
 var path = require('path');
 var url = require('url');
 
-var override = require('./utils/object').override;
-
+var compatibility = require('./utils/compatibility');
+var Validator = require('./properties/validator');
+var override = require('./utils/override');
 var DEFAULT_TIMEOUT = 5000;
 
+var readSources = require('./utils/read-sources');
+var basicOptimize = require('./optimizer/basic');
+var advancedOptimize = require('./optimizer/advanced');
+var simpleStringify = require('./stringifier/simple');
+var sourceMapStringify = require('./stringifier/source-maps');
+
 var CleanCSS = module.exports = function CleanCSS(options) {
   options = options || {};
 
@@ -41,7 +27,7 @@ var CleanCSS = module.exports = function CleanCSS(options) {
     advanced: undefined === options.advanced ? true : !!options.advanced,
     aggressiveMerging: undefined === options.aggressiveMerging ? true : !!options.aggressiveMerging,
     benchmark: options.benchmark,
-    compatibility: new Compatibility(options.compatibility).toOptions(),
+    compatibility: compatibility(options.compatibility),
     debug: options.debug,
     explicitRoot: !!options.root,
     explicitTarget: !!options.target,
@@ -92,140 +78,83 @@ function proxyOptionsFrom(httpProxy) {
     {};
 }
 
-CleanCSS.prototype.minify = function (data, callback) {
+CleanCSS.prototype.minify = function (input, callback) {
   var context = {
     stats: {},
     errors: [],
     warnings: [],
     options: this.options,
-    debug: this.options.debug,
     localOnly: !callback,
-    sourceTracker: new SourceTracker(),
     validator: new Validator(this.options.compatibility)
   };
 
-  if (context.options.sourceMap)
-    context.inputSourceMapTracker = new InputSourceMapTracker(context);
+  return runner(context.localOnly)(function () {
+    return readSources(input, context, function (tokens) {
+      var stringify = context.options.sourceMap ?
+        sourceMapStringify :
+        simpleStringify;
 
-  context.sourceReader = new SourceReader(context, data);
-  data = context.sourceReader.toString();
+      var optimizedTokens = optimize(tokens, context);
+      var optimizedStyles = stringify(optimizedTokens, context);
+      var output = withMetadata(optimizedStyles, context);
 
-  if (context.options.processImport || data.indexOf('@shallow') > 0) {
-    // inline all imports
-    var runner = callback ?
-      process.nextTick :
-      function (callback) { return callback(); };
-
-    return runner(function () {
-      return new ImportInliner(context).process(data, {
-        localOnly: context.localOnly,
-        imports: context.options.processImportFrom,
-        whenDone: runMinifier(callback, context)
-      });
+      return callback ?
+        callback(context.errors.length > 0 ? context.errors : null, output) :
+        output;
     });
-  } else {
-    return runMinifier(callback, context)(data);
-  }
+  });
 };
 
-function runMinifier(callback, context) {
-  function whenSourceMapReady (data) {
-    data = context.options.debug ?
-      minifyWithDebug(context, data) :
-      minify(context, data);
-    data = withMetadata(context, data);
-
-    return callback ?
-      callback.call(null, context.errors.length > 0 ? context.errors : null, data) :
-      data;
-  }
-
-  return function (data) {
-    if (context.options.sourceMap) {
-      return context.inputSourceMapTracker.track(data, function () {
-        if (context.options.sourceMapInlineSources) {
-          return context.inputSourceMapTracker.resolveSources(function () {
-            return whenSourceMapReady(data);
-          });
-        } else {
-          return whenSourceMapReady(data);
-        }
-      });
-    } else {
-      return whenSourceMapReady(data);
-    }
-  };
-}
-
-function withMetadata(context, data) {
-  data.stats = context.stats;
-  data.errors = context.errors;
-  data.warnings = context.warnings;
-  return data;
+function runner(localOnly) {
+  // to always execute code asynchronously when a callback is given
+  // more at blog.izs.me/post/59142742143/designing-apis-for-asynchrony
+  return localOnly ?
+    function (callback) { return callback(); } :
+    process.nextTick;
 }
 
-function minifyWithDebug(context, data) {
-  var startedAt = process.hrtime();
-  context.stats.originalSize = context.sourceTracker.removeAll(data).length;
-
-  data = minify(context, data);
+function optimize(tokens, context) {
+  var optimized;
 
-  var elapsed = process.hrtime(startedAt);
-  context.stats.timeSpent = ~~(elapsed[0] * 1e3 + elapsed[1] / 1e6);
-  context.stats.efficiency = 1 - data.styles.length / context.stats.originalSize;
-  context.stats.minifiedSize = data.styles.length;
+  optimized = basicOptimize(tokens, context);
+  optimized = context.options.advanced ?
+    advancedOptimize(tokens, context, true) :
+    optimized;
 
-  return data;
+  return optimized;
 }
 
-function benchmark(runner) {
-  return function (processor, action) {
-    var name =  processor.constructor.name + '#' + action;
-    var start = process.hrtime();
-    runner(processor, action);
-    var itTook = process.hrtime(start);
-    console.log('%d ms: ' + name, 1000 * itTook[0] + itTook[1] / 1000000);
-  };
+function withMetadata(output, context) {
+  output.stats = context.stats;
+  output.errors = context.errors;
+  output.warnings = context.warnings;
+  return output;
 }
 
-function minify(context, data) {
-  var options = context.options;
+// function minifyWithDebug(context, data) {
+//   var startedAt = process.hrtime();
+//   context.stats.originalSize = context.sourceTracker.removeAll(data).length;
 
-  var commentsProcessor = new CommentsProcessor(context, options.keepSpecialComments, options.keepBreaks, options.sourceMap);
-  var expressionsProcessor = new ExpressionsProcessor(options.sourceMap);
-  var freeTextProcessor = new FreeTextProcessor(options.sourceMap);
-  var urlsProcessor = new UrlsProcessor(context, options.sourceMap, options.compatibility.properties.urlQuotes);
+//   data = minify(context, data);
 
-  var stringify = options.sourceMap ? sourceMapStringify : simpleStringify;
+//   var elapsed = process.hrtime(startedAt);
+//   context.stats.timeSpent = ~~(elapsed[0] * 1e3 + elapsed[1] / 1e6);
+//   context.stats.efficiency = 1 - data.styles.length / context.stats.originalSize;
+//   context.stats.minifiedSize = data.styles.length;
 
-  var run = function (processor, action) {
-    data = typeof processor == 'function' ?
-      processor(data) :
-      processor[action](data);
-  };
-
-  if (options.benchmark)
-    run = benchmark(run);
-
-  run(commentsProcessor, 'escape');
-  run(expressionsProcessor, 'escape');
-  run(urlsProcessor, 'escape');
-  run(freeTextProcessor, 'escape');
+//   return data;
+// }
 
-  function restoreEscapes(data, prefixContent) {
-    data = freeTextProcessor.restore(data, prefixContent);
-    data = urlsProcessor.restore(data);
-    data = options.rebase ? rebaseUrls(data, context) : data;
-    data = expressionsProcessor.restore(data);
-    return commentsProcessor.restore(data);
-  }
+// function minify(context, data) {
+  // var options = context.options;
+  // var stringify = options.sourceMap ?
+  //   sourceMapStringify :
+  //   simpleStringify;
 
-  var tokens = tokenize(data, context);
+  // var tokens = tokenize(data, context);
+  // var allTokens = inline()
+  // var optimizedTokens = optimize(tokens, context);
+  // var output = stringify(optimizedTokens, context);
 
-  simpleOptimize(tokens, options, context);
-
-  if (options.advanced)
-    advancedOptimize(tokens, options, context, true);
-
-  return stringify(tokens, options, restoreEscapes, context.inputSourceMapTracker);
-}
+  // return output;
+// }
similarity index 53%
rename from lib/selectors/advanced.js
rename to lib/optimizer/advanced.js
index 38630e6..fb5d9ec 100644 (file)
@@ -9,16 +9,18 @@ var restructure = require('./restructure');
 var removeDuplicateMediaQueries = require('./remove-duplicate-media-queries');
 var mergeMediaQueries = require('./merge-media-queries');
 
+var Token = require('../tokenizer/token');
+
 function removeEmpty(tokens) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
     var isEmpty = false;
 
     switch (token[0]) {
-      case 'selector':
+      case Token.RULE:
         isEmpty = token[1].length === 0 || token[2].length === 0;
         break;
-      case 'block':
+      case Token.BLOCK:
         removeEmpty(token[2]);
         isEmpty = token[2].length === 0;
     }
@@ -31,56 +33,58 @@ function removeEmpty(tokens) {
   }
 }
 
-function recursivelyOptimizeBlocks(tokens, options, context) {
+function recursivelyOptimizeBlocks(tokens, context) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (token[0] == 'block') {
-      var isKeyframes = /@(-moz-|-o-|-webkit-)?keyframes/.test(token[1][0]);
-      optimize(token[2], options, context, !isKeyframes);
+    if (token[0] == Token.BLOCK) {
+      var isKeyframes = /@(-moz-|-o-|-webkit-)?keyframes/.test(token[1][0][0]);
+      optimize(token[2], context, !isKeyframes);
     }
   }
 }
 
-function recursivelyOptimizeProperties(tokens, options, context) {
+function recursivelyOptimizeProperties(tokens, context) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
     switch (token[0]) {
-      case 'selector':
-        optimizeProperties(token[1], token[2], false, true, options, context);
+      case Token.RULE:
+        optimizeProperties(token[1], token[2], false, true, context);
         break;
-      case 'block':
-        recursivelyOptimizeProperties(token[2], options, context);
+      case Token.BLOCK:
+        recursivelyOptimizeProperties(token[2], context);
     }
   }
 }
 
-function optimize(tokens, options, context, withRestructuring) {
-  recursivelyOptimizeBlocks(tokens, options, context);
-  recursivelyOptimizeProperties(tokens, options, context);
+function optimize(tokens, context, withRestructuring) {
+  recursivelyOptimizeBlocks(tokens, context);
+  recursivelyOptimizeProperties(tokens, context);
 
-  removeDuplicates(tokens);
-  mergeAdjacent(tokens, options, context);
-  reduceNonAdjacent(tokens, options, context);
+  removeDuplicates(tokens, context);
+  mergeAdjacent(tokens, context);
+  reduceNonAdjacent(tokens, context);
 
-  mergeNonAdjacentBySelector(tokens, options, context);
-  mergeNonAdjacentByBody(tokens, options);
+  mergeNonAdjacentBySelector(tokens, context);
+  mergeNonAdjacentByBody(tokens, context);
 
-  if (options.restructuring && withRestructuring) {
-    restructure(tokens, options);
-    mergeAdjacent(tokens, options, context);
+  if (context.options.restructuring && withRestructuring) {
+    restructure(tokens, context);
+    mergeAdjacent(tokens, context);
   }
 
-  if (options.mediaMerging) {
-    removeDuplicateMediaQueries(tokens);
-    var reduced = mergeMediaQueries(tokens);
+  if (context.options.mediaMerging) {
+    removeDuplicateMediaQueries(tokens, context);
+    var reduced = mergeMediaQueries(tokens, context);
     for (var i = reduced.length - 1; i >= 0; i--) {
-      optimize(reduced[i][2], options, context, false);
+      optimize(reduced[i][2], context, false);
     }
   }
 
   removeEmpty(tokens);
+
+  return tokens;
 }
 
 module.exports = optimize;
similarity index 59%
rename from lib/selectors/simple.js
rename to lib/optimizer/basic.js
index b5753e1..ec95bda 100644 (file)
@@ -1,20 +1,27 @@
-var cleanUpSelectors = require('./clean-up').selectors;
-var cleanUpBlock = require('./clean-up').block;
-var cleanUpAtRule = require('./clean-up').atRule;
+var tidyRules = require('./tidy-rules');
+var tidyBlock = require('./tidy-block');
+var tidyAtRule = require('./tidy-at-rule');
 var split = require('../utils/split');
 
+var Token = require('../tokenizer/token');
+var Marker = require('../tokenizer/marker');
+
 var RGB = require('../colors/rgb');
 var HSL = require('../colors/hsl');
 var HexNameShortener = require('../colors/hex-name-shortener');
 
+var Hack = require('../properties/hack');
+
 var wrapForOptimizing = require('../properties/wrap-for-optimizing').all;
 var restoreFromOptimizing = require('../properties/restore-from-optimizing');
 var removeUnused = require('../properties/remove-unused');
 
+var rebaseConfig = require('../urls/rebase-config');
+var rewriteUrl = require('../urls/rewrite-url');
+
 var DEFAULT_ROUNDING_PRECISION = 2;
 var CHARSET_TOKEN = '@charset';
 var CHARSET_REGEXP = new RegExp('^' + CHARSET_TOKEN, 'i');
-var IMPORT_REGEXP = /^@import["'\s]/i;
 
 var FONT_NUMERAL_WEIGHTS = ['100', '200', '300', '400', '500', '600', '700', '800', '900'];
 var FONT_NAME_WEIGHTS = ['normal', 'bold', 'bolder', 'lighter'];
@@ -23,6 +30,10 @@ var FONT_NAME_WEIGHTS_WITHOUT_NORMAL = ['bold', 'bolder', 'lighter'];
 var WHOLE_PIXEL_VALUE = /(?:^|\s|\()(-?\d+)px/;
 var TIME_VALUE = /^(\-?[\d\.]+)(m?s)$/;
 
+var QUOTED_PATTERN = /^('.*'|".*")$/;
+var QUOTED_BUT_SAFE_PATTERN = /^['"][a-zA-Z][a-zA-Z\d\-_]+['"]$/;
+var URL_PREFIX_PATTERN = /^url\(/i;
+
 var valueMinifiers = {
   'background': function (value, index, total) {
     return index === 0 && total == 1 && (value == 'none' || value == 'transparent') ? '0 0' : value;
@@ -40,8 +51,8 @@ var valueMinifiers = {
   }
 };
 
-function isNegative(property, idx) {
-  return property.value[idx] && property.value[idx][0][0] == '-' && parseFloat(property.value[idx][0]) < 0;
+function isNegative(value) {
+  return value && value[1][0] == '-' && parseFloat(value[1]) < 0;
 }
 
 function zeroMinifier(name, value) {
@@ -72,7 +83,10 @@ function zeroDegMinifier(_, value) {
 }
 
 function whitespaceMinifier(name, value) {
-  if (name.indexOf('filter') > -1 || value.indexOf(' ') == -1)
+  if (name.indexOf('filter') > -1 || value.indexOf(' ') == -1 || value.indexOf('expression') === 0)
+    return value;
+
+  if (value.indexOf(Marker.SINGLE_QUOTE) > -1 || value.indexOf(Marker.DOUBLE_QUOTE) > -1)
     return value;
 
   value = value.replace(/\s+/g, ' ');
@@ -116,11 +130,12 @@ function multipleZerosMinifier(property) {
   var values = property.value;
   var spliceAt;
 
-  if (values.length == 4 && values[0][0] === '0' && values[1][0] === '0' && values[2][0] === '0' && values[3][0] === '0') {
-    if (property.name.indexOf('box-shadow') > -1)
+  if (values.length == 4 && values[0][1] === '0' && values[1][1] === '0' && values[2][1] === '0' && values[3][1] === '0') {
+    if (property.name.indexOf('box-shadow') > -1) {
       spliceAt = 2;
-    else
+    } else {
       spliceAt = 1;
+    }
   }
 
   if (spliceAt) {
@@ -222,13 +237,13 @@ function minifyBorderRadius(property) {
   var values = property.value;
   var spliceAt;
 
-  if (values.length == 3 && values[1][0] == '/' && values[0][0] == values[2][0])
+  if (values.length == 3 && values[1][1] == '/' && values[0][1] == values[2][1])
     spliceAt = 1;
-  else if (values.length == 5 && values[2][0] == '/' && values[0][0] == values[3][0] && values[1][0] == values[4][0])
+  else if (values.length == 5 && values[2][1] == '/' && values[0][1] == values[3][1] && values[1][1] == values[4][1])
     spliceAt = 2;
-  else if (values.length == 7 && values[3][0] == '/' && values[0][0] == values[4][0] && values[1][0] == values[5][0] && values[2][0] == values[6][0])
+  else if (values.length == 7 && values[3][1] == '/' && values[0][1] == values[4][1] && values[1][1] == values[5][1] && values[2][1] == values[6][1])
     spliceAt = 3;
-  else if (values.length == 9 && values[4][0] == '/' && values[0][0] == values[5][0] && values[1][0] == values[6][0] && values[2][0] == values[7][0] && values[3][0] == values[8][0])
+  else if (values.length == 9 && values[4][1] == '/' && values[0][1] == values[5][1] && values[1][1] == values[6][1] && values[2][1] == values[7][1] && values[3][1] == values[8][1])
     spliceAt = 4;
 
   if (spliceAt) {
@@ -239,104 +254,154 @@ function minifyBorderRadius(property) {
 
 function minifyFilter(property) {
   if (property.value.length == 1) {
-    property.value[0][0] = property.value[0][0].replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\W)/, function (match, filter, suffix) {
+    property.value[0][1] = property.value[0][1].replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\W)/, function (match, filter, suffix) {
       return filter.toLowerCase() + suffix;
     });
   }
 
-  property.value[0][0] = property.value[0][0]
+  property.value[0][1] = property.value[0][1]
     .replace(/,(\S)/g, ', $1')
     .replace(/ ?= ?/g, '=');
 }
 
 function minifyFont(property) {
   var values = property.value;
-  var hasNumeral = FONT_NUMERAL_WEIGHTS.indexOf(values[0][0]) > -1 ||
-    values[1] && FONT_NUMERAL_WEIGHTS.indexOf(values[1][0]) > -1 ||
-    values[2] && FONT_NUMERAL_WEIGHTS.indexOf(values[2][0]) > -1;
+  var hasNumeral = FONT_NUMERAL_WEIGHTS.indexOf(values[0][1]) > -1 ||
+    values[1] && FONT_NUMERAL_WEIGHTS.indexOf(values[1][1]) > -1 ||
+    values[2] && FONT_NUMERAL_WEIGHTS.indexOf(values[2][1]) > -1;
 
   if (hasNumeral)
     return;
 
-  if (values[1] == '/')
+  if (values[1] && values[1][1] == '/')
     return;
 
   var normalCount = 0;
-  if (values[0][0] == 'normal')
+  if (values[0][1] == 'normal')
     normalCount++;
-  if (values[1] && values[1][0] == 'normal')
+  if (values[1] && values[1][1] == 'normal')
     normalCount++;
-  if (values[2] && values[2][0] == 'normal')
+  if (values[2] && values[2][1] == 'normal')
     normalCount++;
 
   if (normalCount > 1)
     return;
 
   var toOptimize;
-  if (FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[0][0]) > -1)
+  if (FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[0][1]) > -1)
     toOptimize = 0;
-  else if (values[1] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[1][0]) > -1)
+  else if (values[1] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[1][1]) > -1)
     toOptimize = 1;
-  else if (values[2] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[2][0]) > -1)
+  else if (values[2] && FONT_NAME_WEIGHTS_WITHOUT_NORMAL.indexOf(values[2][1]) > -1)
     toOptimize = 2;
-  else if (FONT_NAME_WEIGHTS.indexOf(values[0][0]) > -1)
+  else if (FONT_NAME_WEIGHTS.indexOf(values[0][1]) > -1)
     toOptimize = 0;
-  else if (values[1] && FONT_NAME_WEIGHTS.indexOf(values[1][0]) > -1)
+  else if (values[1] && FONT_NAME_WEIGHTS.indexOf(values[1][1]) > -1)
     toOptimize = 1;
-  else if (values[2] && FONT_NAME_WEIGHTS.indexOf(values[2][0]) > -1)
+  else if (values[2] && FONT_NAME_WEIGHTS.indexOf(values[2][1]) > -1)
     toOptimize = 2;
 
   if (toOptimize !== undefined) {
-    property.value[toOptimize][0] = valueMinifiers['font-weight'](values[toOptimize][0]);
+    property.value[toOptimize][1] = valueMinifiers['font-weight'](values[toOptimize][1]);
     property.dirty = true;
   }
 }
 
-function optimizeBody(properties, options) {
+function normalizeUrl(value) {
+  return value
+    .replace(URL_PREFIX_PATTERN, 'url(')
+    .replace(/\\?\n|\\?\r\n/g, '');
+}
+
+function removeUrlQuotes(value) {
+  return /^url\(['"].+['"]\)$/.test(value) && !/^url\(['"].*[\*\s\(\)'"].*['"]\)$/.test(value) && !/^url\(['"]data:[^;]+;charset/.test(value) ?
+    value.replace(/["']/g, '') :
+    value;
+}
+
+function isQuoted(value) {
+  return QUOTED_PATTERN.test(value);
+}
+
+function removeQuotes(name, value) {
+  if (name == 'content') {
+    return value;
+  }
+
+  return QUOTED_BUT_SAFE_PATTERN.test(value) ?
+    value.substring(1, value.length - 1) :
+    value;
+}
+
+function optimizeBody(properties, context) {
+  var options = context.options;
   var property, name, value;
+  var valueIsUrl;
   var _properties = wrapForOptimizing(properties);
 
   for (var i = 0, l = _properties.length; i < l; i++) {
     property = _properties[i];
     name = property.name;
 
+    if (property.value.length === 0) {
+      property.unused = true;
+    }
+
     if (property.hack && (
-        (property.hack == 'star' || property.hack == 'underscore') && !options.compatibility.properties.iePrefixHack ||
-        property.hack == 'backslash' && !options.compatibility.properties.ieSuffixHack ||
-        property.hack == 'bang' && !options.compatibility.properties.ieBangHack))
+        (property.hack == Hack.STAR || property.hack == Hack.UNDERSCORE) && !options.compatibility.properties.iePrefixHack ||
+        property.hack == Hack.BACKSLASH && !options.compatibility.properties.ieSuffixHack ||
+        property.hack == Hack.BANG && !options.compatibility.properties.ieBangHack)) {
       property.unused = true;
+    }
 
-    if (name.indexOf('padding') === 0 && (isNegative(property, 0) || isNegative(property, 1) || isNegative(property, 2) || isNegative(property, 3)))
+    if (name.indexOf('padding') === 0 && (isNegative(property.value[0]) || isNegative(property.value[1]) || isNegative(property.value[2]) || isNegative(property.value[3])))
       property.unused = true;
 
     if (property.unused)
       continue;
 
-    if (property.variable) {
-      if (property.block)
-        optimizeBody(property.value[0], options);
+    if (property.block) {
+      optimizeBody(property.value[0][1], context);
       continue;
     }
 
     for (var j = 0, m = property.value.length; j < m; j++) {
-      value = property.value[j][0];
+      value = property.value[j][1];
+      valueIsUrl = isUrl(value);
+
+      if (valueIsUrl && !context.validator.isValidUrl(value)) {
+        property.unused = true;
+        break;
+      }
 
       if (valueMinifiers[name])
         value = valueMinifiers[name](value, j, m);
 
-      value = whitespaceMinifier(name, value);
-      value = precisionMinifier(name, value, options.precision);
-      value = pixelLengthMinifier(name, value, options.compatibility);
-      value = timeUnitMinifier(name, value);
-      value = zeroMinifier(name, value);
-      if (options.compatibility.properties.zeroUnits) {
-        value = zeroDegMinifier(name, value);
-        value = unitMinifier(name, value, options.unitsRegexp);
+      if (valueIsUrl) {
+        value = normalizeUrl(value);
+        value = options.compatibility.properties.urlQuotes ?
+          value :
+          removeUrlQuotes(value);
+        value = options.rebase && context.validator.isValidUrl(value) ?
+          rewriteUrl(value, options.rebaseConfig) :
+          value;
+      } else if (isQuoted(value)) {
+        value = removeQuotes(name, value);
+      } else {
+        value = whitespaceMinifier(name, value);
+        value = precisionMinifier(name, value, options.precision);
+        value = pixelLengthMinifier(name, value, options.compatibility);
+        value = timeUnitMinifier(name, value);
+        value = zeroMinifier(name, value);
+        if (options.compatibility.properties.zeroUnits) {
+          value = zeroDegMinifier(name, value);
+          value = unitMinifier(name, value, options.unitsRegexp);
+        }
+        if (options.compatibility.properties.colors)
+          value = colorMininifier(name, value, options.compatibility);
       }
-      if (options.compatibility.properties.colors)
-        value = colorMininifier(name, value, options.compatibility);
 
-      property.value[j][0] = value;
+      property.value[j][1] = value;
     }
 
     multipleZerosMinifier(property);
@@ -351,6 +416,43 @@ function optimizeBody(properties, options) {
 
   restoreFromOptimizing(_properties, true);
   removeUnused(_properties);
+
+  if (_properties.length != properties.length) {
+    removeComments(properties, options);
+  }
+}
+
+function isUrl(value) {
+  return URL_PREFIX_PATTERN.test(value);
+}
+
+function removeComments(tokens, options) {
+  var token;
+  var i;
+
+  for (i = 0; i < tokens.length; i++) {
+    token = tokens[i];
+
+    if (token[0] != Token.COMMENT) {
+      continue;
+    }
+
+    optimizeComment(token, options);
+
+    if (token[1].length === 0) {
+      tokens.splice(i, 1);
+      i--;
+    }
+  }
+}
+
+function optimizeComment(token, options) {
+  if (token[1][0][2] == Marker.EXCLAMATION && (options.keepSpecialComments == '*' || options.commentsKept < options.keepSpecialComments)) {
+    options.commentsKept++;
+    return;
+  }
+
+  token[1] = [];
 }
 
 function cleanupCharsets(tokens) {
@@ -359,20 +461,20 @@ function cleanupCharsets(tokens) {
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
-    if (token[0] != 'at-rule')
+    if (token[0] != Token.AT_RULE)
       continue;
 
-    if (!CHARSET_REGEXP.test(token[1][0]))
+    if (!CHARSET_REGEXP.test(token[1]))
       continue;
 
-    if (hasCharset || token[1][0].indexOf(CHARSET_TOKEN) == -1) {
+    if (hasCharset || token[1].indexOf(CHARSET_TOKEN) == -1) {
       tokens.splice(i, 1);
       i--;
       l--;
     } else {
       hasCharset = true;
       tokens.splice(i, 1);
-      tokens.unshift(['at-rule', [token[1][0].replace(CHARSET_REGEXP, CHARSET_TOKEN)]]);
+      tokens.unshift([Token.AT_RULE, token[1].replace(CHARSET_REGEXP, CHARSET_TOKEN)]);
     }
   }
 }
@@ -405,43 +507,40 @@ function buildPrecision(options) {
   return precision;
 }
 
-function optimize(tokens, options, context) {
+function basicOptimize(tokens, context) {
+  var options = context.options;
   var ie7Hack = options.compatibility.selectors.ie7Hack;
   var adjacentSpace = options.compatibility.selectors.adjacentSpace;
   var spaceAfterClosingBrace = options.compatibility.properties.spaceAfterClosingBrace;
   var mayHaveCharset = false;
-  var afterContent = false;
 
-  options.unitsRegexp = buildUnitRegexp(options);
-  options.precision = buildPrecision(options);
+  options.unitsRegexp = options.unitsRegexp || buildUnitRegexp(options);
+  options.precision = options.precision || buildPrecision(options);
+  options.commentsKept = options.commentsKept || 0;
+  options.rebaseConfig = options.rebaseConfig || rebaseConfig(context);
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
 
     switch (token[0]) {
-      case 'selector':
-        token[1] = cleanUpSelectors(token[1], !ie7Hack, adjacentSpace);
-        optimizeBody(token[2], options);
-        afterContent = true;
+      case Token.AT_RULE:
+        token[1] = tidyAtRule(token[1]);
+        mayHaveCharset = true;
         break;
-      case 'block':
-        cleanUpBlock(token[1], spaceAfterClosingBrace);
-        optimize(token[2], options, context);
-        afterContent = true;
+      case Token.AT_RULE_BLOCK:
+        optimizeBody(token[2], context);
         break;
-      case 'flat-block':
-        cleanUpBlock(token[1], spaceAfterClosingBrace);
-        optimizeBody(token[2], options);
-        afterContent = true;
+      case Token.BLOCK:
+        token[1] = tidyBlock(token[1], spaceAfterClosingBrace);
+        basicOptimize(token[2], context);
+        break;
+      case Token.COMMENT:
+        optimizeComment(token, options);
+        break;
+      case Token.RULE:
+        token[1] = tidyRules(token[1], !ie7Hack, adjacentSpace);
+        optimizeBody(token[2], context);
         break;
-      case 'at-rule':
-        cleanUpAtRule(token[1]);
-        mayHaveCharset = true;
-    }
-
-    if (token[0] == 'at-rule' && IMPORT_REGEXP.test(token[1]) && afterContent) {
-      context.warnings.push('Ignoring @import rule "' + token[1] + '" as it appears after rules thus browsers will ignore them.');
-      token[1] = '';
     }
 
     if (token[1].length === 0 || (token[2] && token[2].length === 0)) {
@@ -451,8 +550,11 @@ function optimize(tokens, options, context) {
     }
   }
 
-  if (mayHaveCharset)
+  if (mayHaveCharset) {
     cleanupCharsets(tokens);
+  }
+
+  return tokens;
 }
 
-module.exports = optimize;
+module.exports = basicOptimize;
similarity index 62%
rename from lib/selectors/extractor.js
rename to lib/optimizer/extract-properties.js
index 497198b..4077061 100644 (file)
@@ -2,33 +2,35 @@
 // IMPORTANT: Mind Token class and this code is not related!
 // Properties will be tokenized in one step, see #429
 
-var stringifySelectors = require('../stringifier/one-time').selectors;
+var stringifyRules = require('../stringifier/one-time').rules;
 var stringifyValue = require('../stringifier/one-time').value;
+var Token = require('../tokenizer/token');
 
-var AT_RULE = 'at-rule';
-
-function extract(token) {
+function extractProperties(token) {
   var properties = [];
+  var inSpecificSelector;
+  var property;
+  var name;
+  var value;
+  var i, l;
 
-  if (token[0] == 'selector') {
-    var inSpecificSelector = !/[\.\+>~]/.test(stringifySelectors(token[1]));
-    for (var i = 0, l = token[2].length; i < l; i++) {
-      var property = token[2][i];
+  if (token[0] == Token.RULE) {
+    inSpecificSelector = !/[\.\+>~]/.test(stringifyRules(token[1]));
 
-      if (property.indexOf('__ESCAPED') === 0)
-        continue;
+    for (i = 0, l = token[2].length; i < l; i++) {
+      property = token[2][i];
 
-      if (property[0] == AT_RULE)
+      if (property[0] != Token.PROPERTY)
         continue;
 
-      var name = token[2][i][0][0];
+      name = property[1][1];
       if (name.length === 0)
         continue;
 
       if (name.indexOf('--') === 0)
         continue;
 
-      var value = stringifyValue(token[2], i);
+      value = stringifyValue(property, i);
 
       properties.push([
         name,
@@ -40,9 +42,9 @@ function extract(token) {
         inSpecificSelector
       ]);
     }
-  } else if (token[0] == 'block') {
-    for (var j = 0, k = token[2].length; j < k; j++) {
-      properties = properties.concat(extract(token[2][j]));
+  } else if (token[0] == Token.BLOCK) {
+    for (i = 0, l = token[2].length; i < l; i++) {
+      properties = properties.concat(extractProperties(token[2][i]));
     }
   }
 
@@ -66,4 +68,4 @@ function findNameRoot(name) {
   return name.replace(/^\-\w+\-/, '').match(/([a-zA-Z]+)/)[0].toLowerCase();
 }
 
-module.exports = extract;
+module.exports = extractProperties;
diff --git a/lib/optimizer/merge-adjacent.js b/lib/optimizer/merge-adjacent.js
new file mode 100644 (file)
index 0000000..622902b
--- /dev/null
@@ -0,0 +1,38 @@
+var optimizeProperties = require('../properties/optimizer');
+
+var stringifyBody = require('../stringifier/one-time').body;
+var stringifyRules = require('../stringifier/one-time').rules;
+var tidyRules = require('./tidy-rules');
+var isSpecial = require('./is-special');
+
+var Token = require('../tokenizer/token');
+
+function mergeAdjacent(tokens, context) {
+  var lastToken = [null, [], []];
+  var options = context.options;
+  var adjacentSpace = options.compatibility.selectors.adjacentSpace;
+
+  for (var i = 0, l = tokens.length; i < l; i++) {
+    var token = tokens[i];
+
+    if (token[0] != Token.RULE) {
+      lastToken = [null, [], []];
+      continue;
+    }
+
+    if (lastToken[0] == Token.RULE && stringifyRules(token[1]) == stringifyRules(lastToken[1])) {
+      var joinAt = [lastToken[2].length];
+      Array.prototype.push.apply(lastToken[2], token[2]);
+      optimizeProperties(token[1], lastToken[2], joinAt, true, context);
+      token[2] = [];
+    } else if (lastToken[0] == Token.RULE && stringifyBody(token[2]) == stringifyBody(lastToken[2]) &&
+        !isSpecial(options, stringifyRules(token[1])) && !isSpecial(options, stringifyRules(lastToken[1]))) {
+      lastToken[1] = tidyRules(lastToken[1].concat(token[1]), false, adjacentSpace);
+      token[2] = [];
+    } else {
+      lastToken = token;
+    }
+  }
+}
+
+module.exports = mergeAdjacent;
similarity index 83%
rename from lib/selectors/merge-media-queries.js
rename to lib/optimizer/merge-media-queries.js
index 0df0d6f..fcf9ed2 100644 (file)
@@ -1,5 +1,8 @@
 var canReorder = require('./reorderable').canReorder;
-var extractProperties = require('./extractor');
+var extractProperties = require('./extract-properties');
+
+var stringifyRules = require('../stringifier/one-time').rules;
+var Token = require('../tokenizer/token');
 
 function mergeMediaQueries(tokens) {
   var candidates = {};
@@ -7,13 +10,15 @@ function mergeMediaQueries(tokens) {
 
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
-    if (token[0] != 'block')
+    if (token[0] != Token.BLOCK) {
       continue;
+    }
 
-    var candidate = candidates[token[1][0]];
+    var key = stringifyRules(token[1]);
+    var candidate = candidates[key];
     if (!candidate) {
       candidate = [];
-      candidates[token[1][0]] = candidate;
+      candidates[key] = candidate;
     }
 
     candidate.push(i);
similarity index 67%
rename from lib/selectors/merge-non-adjacent-by-body.js
rename to lib/optimizer/merge-non-adjacent-by-body.js
index de148a0..2549cce 100644 (file)
@@ -1,14 +1,16 @@
 var stringifyBody = require('../stringifier/one-time').body;
-var stringifySelectors = require('../stringifier/one-time').selectors;
-var cleanUpSelectors = require('./clean-up').selectors;
+var stringifyRules = require('../stringifier/one-time').rules;
+var tidyRules = require('./tidy-rules');
 var isSpecial = require('./is-special');
 
+var Token = require('../tokenizer/token');
+
 function unsafeSelector(value) {
   return /\.|\*| :/.test(value);
 }
 
 function isBemElement(token) {
-  var asString = stringifySelectors(token[1]);
+  var asString = stringifyRules(token[1]);
   return asString.indexOf('__') > -1 || asString.indexOf('--') > -1;
 }
 
@@ -17,27 +19,28 @@ function withoutModifier(selector) {
 }
 
 function removeAnyUnsafeElements(left, candidates) {
-  var leftSelector = withoutModifier(stringifySelectors(left[1]));
+  var leftSelector = withoutModifier(stringifyRules(left[1]));
 
   for (var body in candidates) {
     var right = candidates[body];
-    var rightSelector = withoutModifier(stringifySelectors(right[1]));
+    var rightSelector = withoutModifier(stringifyRules(right[1]));
 
     if (rightSelector.indexOf(leftSelector) > -1 || leftSelector.indexOf(rightSelector) > -1)
       delete candidates[body];
   }
 }
 
-function mergeNonAdjacentByBody(tokens, options) {
-  var candidates = {};
+function mergeNonAdjacentByBody(tokens, context) {
+  var options = context.options;
   var adjacentSpace = options.compatibility.selectors.adjacentSpace;
+  var candidates = {};
 
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
-    if (token[0] != 'selector')
+    if (token[0] != Token.RULE)
       continue;
 
-    if (token[2].length > 0 && (!options.semanticMerging && unsafeSelector(stringifySelectors(token[1]))))
+    if (token[2].length > 0 && (!options.semanticMerging && unsafeSelector(stringifyRules(token[1]))))
       candidates = {};
 
     if (token[2].length > 0 && options.semanticMerging && isBemElement(token))
@@ -45,9 +48,9 @@ function mergeNonAdjacentByBody(tokens, options) {
 
     var candidateBody = stringifyBody(token[2]);
     var oldToken = candidates[candidateBody];
-    if (oldToken && !isSpecial(options, stringifySelectors(token[1])) && !isSpecial(options, stringifySelectors(oldToken[1]))) {
+    if (oldToken && !isSpecial(options, stringifyRules(token[1])) && !isSpecial(options, stringifyRules(oldToken[1]))) {
       token[1] = token[2].length > 0 ?
-        cleanUpSelectors(oldToken[1].concat(token[1]), false, adjacentSpace) :
+        tidyRules(oldToken[1].concat(token[1]), false, adjacentSpace) :
         oldToken[1].concat(token[1]);
 
       oldToken[2] = [];
@@ -1,20 +1,22 @@
 var optimizeProperties = require('../properties/optimizer');
-var stringifySelectors = require('../stringifier/one-time').selectors;
-var extractProperties = require('./extractor');
+var stringifyRules = require('../stringifier/one-time').rules;
+var extractProperties = require('./extract-properties');
 var canReorder = require('./reorderable').canReorder;
 
-function mergeNonAdjacentBySelector(tokens, options, context) {
+var Token = require('../tokenizer/token');
+
+function mergeNonAdjacentBySelector(tokens, context) {
   var allSelectors = {};
   var repeatedSelectors = [];
   var i;
 
   for (i = tokens.length - 1; i >= 0; i--) {
-    if (tokens[i][0] != 'selector')
+    if (tokens[i][0] != Token.RULE)
       continue;
     if (tokens[i][2].length === 0)
       continue;
 
-    var selector = stringifySelectors(tokens[i][1]);
+    var selector = stringifyRules(tokens[i][1]);
     allSelectors[selector] = [i].concat(allSelectors[selector] || []);
 
     if (allSelectors[selector].length == 2)
@@ -66,7 +68,7 @@ function mergeNonAdjacentBySelector(tokens, options, context) {
           Array.prototype.push.apply(target[2], moved[2]);
         }
 
-        optimizeProperties(target[1], target[2], joinAt, true, options, context);
+        optimizeProperties(target[1], target[2], joinAt, true, context);
         moved[2] = [];
       }
     }
similarity index 90%
rename from lib/selectors/reduce-non-adjacent.js
rename to lib/optimizer/reduce-non-adjacent.js
index 44b540c..267231a 100644 (file)
@@ -1,24 +1,28 @@
 var optimizeProperties = require('../properties/optimizer');
 var stringifyBody = require('../stringifier/one-time').body;
-var stringifySelectors = require('../stringifier/one-time').selectors;
+var stringifyRules = require('../stringifier/one-time').rules;
 var isSpecial = require('./is-special');
 var cloneArray = require('../utils/clone-array');
 
-function reduceNonAdjacent(tokens, options, context) {
+var Token = require('../tokenizer/token');
+
+function reduceNonAdjacent(tokens, context) {
+  var options = context.options;
   var candidates = {};
   var repeated = [];
 
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
 
-    if (token[0] != 'selector')
+    if (token[0] != Token.RULE) {
       continue;
-    if (token[2].length === 0)
+    } else if (token[2].length === 0) {
       continue;
+    }
 
-    var selectorAsString = stringifySelectors(token[1]);
+    var selectorAsString = stringifyRules(token[1]);
     var isComplexAndNotSpecial = token[1].length > 1 && !isSpecial(options, selectorAsString);
-    var wrappedSelectors = options.sourceMap ? wrappedSelectorsFrom(token[1]) : token[1];
+    var wrappedSelectors = wrappedSelectorsFrom(token[1]);
     var selectors = isComplexAndNotSpecial ?
       [selectorAsString].concat(wrappedSelectors) :
       [selectorAsString];
@@ -150,7 +154,7 @@ function reduceSelector(tokens, selector, data, context, options, outerContext)
       joinsAt.push((joinsAt.length > 0 ? joinsAt[joinsAt.length - 1] : 0) + bodiesAsList[j].length);
   }
 
-  optimizeProperties(selector, bodies, joinsAt, false, options, outerContext);
+  optimizeProperties(selector, bodies, joinsAt, false, outerContext);
 
   var processedCount = processedTokens.length;
   var propertyIdx = bodies.length - 1;
diff --git a/lib/optimizer/remove-duplicate-media-queries.js b/lib/optimizer/remove-duplicate-media-queries.js
new file mode 100644 (file)
index 0000000..9d243dd
--- /dev/null
@@ -0,0 +1,30 @@
+var stringifyAll = require('../stringifier/one-time').all;
+var stringifyRules = require('../stringifier/one-time').rules;
+
+var Token = require('../tokenizer/token');
+
+function removeDuplicateMediaQueries(tokens) {
+  var candidates = {};
+  var candidate;
+  var token;
+  var key;
+  var i, l;
+
+  for (i = 0, l = tokens.length; i < l; i++) {
+    token = tokens[i];
+    if (token[0] != Token.BLOCK) {
+      continue;
+    }
+
+    key = stringifyRules(token[1]) + '%' + stringifyAll(token[2]);
+    candidate = candidates[key];
+
+    if (candidate) {
+      candidate[2] = [];
+    }
+
+    candidates[key] = token;
+  }
+}
+
+module.exports = removeDuplicateMediaQueries;
similarity index 82%
rename from lib/selectors/remove-duplicates.js
rename to lib/optimizer/remove-duplicates.js
index 3a2ce95..fab62b9 100644 (file)
@@ -1,5 +1,7 @@
 var stringifyBody = require('../stringifier/one-time').body;
-var stringifySelectors = require('../stringifier/one-time').selectors;
+var stringifyRules = require('../stringifier/one-time').rules;
+
+var Token = require('../tokenizer/token');
 
 function removeDuplicates(tokens) {
   var matched = {};
@@ -9,10 +11,10 @@ function removeDuplicates(tokens) {
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     token = tokens[i];
-    if (token[0] != 'selector')
+    if (token[0] != Token.RULE)
       continue;
 
-    id = stringifySelectors(token[1]);
+    id = stringifyRules(token[1]);
 
     if (matched[id] && matched[id].length == 1)
       moreThanOnce.push(id);
similarity index 89%
rename from lib/selectors/restructure.js
rename to lib/optimizer/restructure.js
index c3e17f1..0694167 100644 (file)
@@ -1,11 +1,13 @@
-var extractProperties = require('./extractor');
+var extractProperties = require('./extract-properties');
 var canReorderSingle = require('./reorderable').canReorderSingle;
 var stringifyBody = require('../stringifier/one-time').body;
-var stringifySelectors = require('../stringifier/one-time').selectors;
-var cleanUpSelectorDuplicates = require('./clean-up').selectorDuplicates;
+var stringifyRules = require('../stringifier/one-time').rules;
+var tidyRuleDuplicates = require('./tidy-rule-duplicates');
 var isSpecial = require('./is-special');
 var cloneArray = require('../utils/clone-array');
 
+var Token = require('../tokenizer/token');
+
 function naturalSorter(a, b) {
   return a > b;
 }
@@ -17,7 +19,8 @@ function cloneAndMergeSelectors(propertyA, propertyB) {
   return cloned;
 }
 
-function restructure(tokens, options) {
+function restructure(tokens, context) {
+  var options = context.options;
   var movableTokens = {};
   var movedProperties = [];
   var multiPropertyMoveCache = {};
@@ -67,7 +70,7 @@ function restructure(tokens, options) {
   function cacheId(cachedTokens) {
     var id = [];
     for (var i = 0, l = cachedTokens.length; i < l; i++) {
-      id.push(stringifySelectors(cachedTokens[i][1]));
+      id.push(stringifyRules(cachedTokens[i][1]));
     }
     return id.join(ID_JOIN_CHARACTER);
   }
@@ -77,7 +80,7 @@ function restructure(tokens, options) {
     var mergeableTokens = [];
 
     for (var i = sourceTokens.length - 1; i >= 0; i--) {
-      if (isSpecial(options, stringifySelectors(sourceTokens[i][1])))
+      if (isSpecial(options, stringifyRules(sourceTokens[i][1])))
         continue;
 
       mergeableTokens.unshift(sourceTokens[i]);
@@ -112,7 +115,7 @@ function restructure(tokens, options) {
       qualifiedTokens.unshift(bestFit[0][i]);
     }
 
-    allSelectors = cleanUpSelectorDuplicates(allSelectors);
+    allSelectors = tidyRuleDuplicates(allSelectors);
     dropAsNewTokenAt(position, [movedProperty], allSelectors, qualifiedTokens);
   }
 
@@ -141,7 +144,7 @@ function restructure(tokens, options) {
   function sizeDifference(tokensVariant, propertySize, propertiesCount) {
     var allSelectorsSize = 0;
     for (var i = tokensVariant.length - 1; i >= 0; i--) {
-      allSelectorsSize += tokensVariant[i][2].length > propertiesCount ? stringifySelectors(tokensVariant[i][1]).length : -1;
+      allSelectorsSize += tokensVariant[i][2].length > propertiesCount ? stringifyRules(tokensVariant[i][1]).length : -1;
     }
     return allSelectorsSize - (tokensVariant.length - 1) * propertySize + 1;
   }
@@ -159,7 +162,7 @@ function restructure(tokens, options) {
         for (k = 0, m = properties.length; k < m; k++) {
           var property = properties[k];
 
-          var mergeablePropertyName = mergeableProperty[0][0];
+          var mergeablePropertyName = mergeableProperty[1][1];
           var propertyName = property[0];
           var propertyBody = property[4];
           if (mergeablePropertyName == propertyName && stringifyBody([mergeableProperty]) == propertyBody) {
@@ -174,7 +177,7 @@ function restructure(tokens, options) {
       allProperties.unshift(properties[i][3]);
     }
 
-    var newToken = ['selector', allSelectors, allProperties];
+    var newToken = [Token.RULE, allSelectors, allProperties];
     tokens.splice(position, 0, newToken);
   }
 
@@ -250,7 +253,7 @@ function restructure(tokens, options) {
       qualifiedTokens.unshift(bestFit[0][i]);
     }
 
-    allSelectors = cleanUpSelectorDuplicates(allSelectors);
+    allSelectors = tidyRuleDuplicates(allSelectors);
     dropAsNewTokenAt(position, properties, allSelectors, qualifiedTokens);
 
     for (i = properties.length - 1; i >= 0; i--) {
@@ -279,14 +282,14 @@ function restructure(tokens, options) {
 
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
-    var isSelector;
+    var isRule;
     var j, k, m;
     var samePropertyAt;
 
-    if (token[0] == 'selector') {
-      isSelector = true;
-    } else if (token[0] == 'block') {
-      isSelector = false;
+    if (token[0] == Token.RULE) {
+      isRule = true;
+    } else if (token[0] == Token.BLOCK) {
+      isRule = false;
     } else {
       continue;
     }
@@ -332,7 +335,7 @@ function restructure(tokens, options) {
         }
       }
 
-      if (!isSelector || unmovableInCurrentToken.indexOf(j) > -1)
+      if (!isRule || unmovableInCurrentToken.indexOf(j) > -1)
         continue;
 
       var key = property[4];
@@ -353,11 +356,11 @@ function restructure(tokens, options) {
     }
   }
 
-  var position = tokens[0] && tokens[0][0] == 'at-rule' && tokens[0][1][0].indexOf('@charset') === 0 ? 1 : 0;
+  var position = tokens[0] && tokens[0][0] == Token.AT_RULE && tokens[0][1].indexOf('@charset') === 0 ? 1 : 0;
   for (; position < tokens.length - 1; position++) {
-    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))
+    var isImportRule = tokens[position][0] === Token.AT_RULE && tokens[position][1].indexOf('@import') === 0;
+    var isComment = tokens[position][0] === Token.COMMENT;
+    if (!(isImportRule || isComment))
       break;
   }
 
diff --git a/lib/optimizer/tidy-at-rule.js b/lib/optimizer/tidy-at-rule.js
new file mode 100644 (file)
index 0000000..06f49d9
--- /dev/null
@@ -0,0 +1,7 @@
+function tidyAtRule(value) {
+  return value
+    .replace(/\s+/g, ' ')
+    .trim();
+}
+
+module.exports = tidyAtRule;
diff --git a/lib/optimizer/tidy-block.js b/lib/optimizer/tidy-block.js
new file mode 100644 (file)
index 0000000..10b437c
--- /dev/null
@@ -0,0 +1,18 @@
+function tidyBlock(values, spaceAfterClosingBrace) {
+  var i;
+
+  for (i = values.length - 1; i >= 0; i--) {
+    values[i][0] = values[i][0]
+      .replace(/\n|\r\n/g, ' ')
+      .replace(/\s+/g, ' ')
+      .replace(/(,|:|\() /g, '$1')
+      .replace(/ \)/g, ')')
+      .replace(/'([a-zA-Z][a-zA-Z\d\-_]+)'/, '$1')
+      .replace(/"([a-zA-Z][a-zA-Z\d\-_]+)"/, '$1')
+      .replace(spaceAfterClosingBrace ? null : /\) /g, ')');
+  }
+
+  return values;
+}
+
+module.exports = tidyBlock;
diff --git a/lib/optimizer/tidy-rule-duplicates.js b/lib/optimizer/tidy-rule-duplicates.js
new file mode 100644 (file)
index 0000000..e488d64
--- /dev/null
@@ -0,0 +1,21 @@
+function ruleSorter(s1, s2) {
+  return s1[0] > s2[0] ? 1 : -1;
+}
+
+function tidyRuleDuplicates(rules) {
+  var list = [];
+  var repeated = [];
+
+  for (var i = 0, l = rules.length; i < l; i++) {
+    var rule = rules[i];
+
+    if (repeated.indexOf(rule[0]) == -1) {
+      repeated.push(rule[0]);
+      list.push(rule);
+    }
+  }
+
+  return list.sort(ruleSorter);
+}
+
+module.exports = tidyRuleDuplicates;
diff --git a/lib/optimizer/tidy-rules.js b/lib/optimizer/tidy-rules.js
new file mode 100644 (file)
index 0000000..5a38f14
--- /dev/null
@@ -0,0 +1,137 @@
+var Marker = require('../tokenizer/marker');
+
+var RELATION_PATTERN = /[>\+~]/;
+var WHITESPACE_PATTERN = /\s/;
+
+var STAR_PLUS_HTML_HACK = '*+html ';
+var STAR_FIRST_CHILD_PLUS_HTML_HACK = '*:first-child+html ';
+
+function removeWhitespace(value) {
+  var stripped = [];
+  var character;
+  var isNewLineNix;
+  var isNewLineWin;
+  var isEscaped;
+  var wasEscaped;
+  var isQuote;
+  var isAttribute;
+  var isRelation;
+  var isWhitespace;
+  var roundBracketLevel = 0;
+  var wasRelation = false;
+  var wasWhitespace = false;
+  var i, l;
+
+  for (i = 0, l = value.length; i < l; i++) {
+    character = value[i];
+
+    isNewLineNix = character == Marker.NEW_LINE_NIX;
+    isNewLineWin = character == Marker.NEW_LINE_NIX && value[i - 1] == Marker.NEW_LINE_WIN;
+    isRelation = !isEscaped && RELATION_PATTERN.test(character);
+    isWhitespace = WHITESPACE_PATTERN.test(character);
+
+    if (wasEscaped && isQuote && isNewLineWin) {
+      // swallow escaped new windows lines in comments
+      stripped.pop();
+      stripped.pop();
+    } else if (isEscaped && isQuote && isNewLineNix) {
+      // swallow escaped new *nix lines in comments
+      stripped.pop();
+    } else if (isEscaped) {
+      stripped.push(character);
+    } else if (character == Marker.OPEN_SQUARE_BRACKET && !isQuote) {
+      stripped.push(character);
+      isAttribute = true;
+    } else if (character == Marker.CLOSE_SQUARE_BRACKET && !isQuote) {
+      stripped.push(character);
+      isAttribute = false;
+    } else if (character == Marker.OPEN_ROUND_BRACKET && !isQuote) {
+      stripped.push(character);
+      roundBracketLevel++;
+    } else if (character == Marker.CLOSE_ROUND_BRACKET && !isQuote) {
+      stripped.push(character);
+      roundBracketLevel--;
+    } else if ((character == Marker.SINGLE_QUOTE || character == Marker.DOUBLE_QUOTE) && !isQuote) {
+      stripped.push(character);
+      isQuote = true;
+    } else if (character == Marker.SINGLE_QUOTE || character == Marker.DOUBLE_QUOTE) {
+      stripped.push(character);
+      isQuote = false;
+    } else if (isWhitespace && wasRelation) {
+      continue;
+    } else if (isWhitespace && (isAttribute || roundBracketLevel > 0) && !isQuote) {
+      // skip space
+    } else if (isWhitespace && wasWhitespace && !isQuote) {
+      // skip extra space
+    } else if ((isNewLineWin || isNewLineNix) && (isAttribute || roundBracketLevel > 0) && isQuote) {
+      // skip newline
+    } else if (isRelation && wasWhitespace) {
+      stripped.pop();
+      stripped.push(character);
+    } else if (isWhitespace) {
+      stripped.push(Marker.SPACE);
+    } else {
+      stripped.push(character);
+    }
+
+    wasEscaped = isEscaped;
+    isEscaped = character == Marker.BACK_SLASH;
+    wasRelation = isRelation;
+    wasWhitespace = isWhitespace;
+  }
+
+  return stripped.join('');
+}
+
+function removeQuotes(value) {
+  return value
+    .replace(/([^\[])'([a-zA-Z][a-zA-Z\d\-_]+)([^\]])'/g, '$1$2$3')
+    .replace(/([^\[])"([a-zA-Z][a-zA-Z\d\-_]+)([^\]])"/g, '$1$2$3');
+}
+
+function ruleSorter(s1, s2) {
+  return s1[0] > s2[0] ? 1 : -1;
+}
+
+function tidyRules(rules, removeUnsupported, adjacentSpace) {
+  var list = [];
+  var repeated = [];
+
+  for (var i = 0, l = rules.length; i < l; i++) {
+    var rule = rules[i];
+    var reduced = rule[0];
+
+    reduced = removeWhitespace(reduced);
+    reduced = removeQuotes(reduced);
+
+    if (adjacentSpace && reduced.indexOf('nav') > 0) {
+      reduced = reduced.replace(/\+nav(\S|$)/, '+ nav$1');
+    }
+
+    if (removeUnsupported && reduced.indexOf(STAR_PLUS_HTML_HACK) > -1) {
+      continue;
+    }
+
+    if (removeUnsupported && reduced.indexOf(STAR_FIRST_CHILD_PLUS_HTML_HACK) > -1) {
+      continue;
+    }
+
+    if (reduced.indexOf('*') > -1) {
+      reduced = reduced
+        .replace(/\*([:#\.\[])/g, '$1')
+        .replace(/^(\:first\-child)?\+html/, '*$1+html');
+    }
+
+    if (repeated.indexOf(reduced) > -1) {
+      continue;
+    }
+
+    rule[0] = reduced;
+    repeated.push(reduced);
+    list.push(rule);
+  }
+
+  return list.sort(ruleSorter);
+}
+
+module.exports = tidyRules;
index 6657b7a..4b01aca 100644 (file)
@@ -1,34 +1,49 @@
 var wrapSingle = require('./wrap-for-optimizing').single;
 var InvalidPropertyError = require('./invalid-property-error');
 
-var split = require('../utils/split');
+var Token = require('../tokenizer/token');
+
 var MULTIPLEX_SEPARATOR = ',';
 
 function _colorFilter(validator) {
   return function (value) {
-    return value[0] == 'invert' || validator.isValidColor(value[0]);
+    return value[1] == 'invert' || validator.isValidColor(value[1]);
   };
 }
 
 function _styleFilter(validator) {
   return function (value) {
-    return value[0] != 'inherit' && validator.isValidStyle(value[0]) && !validator.isValidColorValue(value[0]);
+    return value[1] != 'inherit' && validator.isValidStyle(value[1]) && !validator.isValidColorValue(value[1]);
   };
 }
 
 function _wrapDefault(name, property, compactable) {
   var descriptor = compactable[name];
-  if (descriptor.doubleValues && descriptor.defaultValue.length == 2)
-    return wrapSingle([[name, property.important], [descriptor.defaultValue[0]], [descriptor.defaultValue[1]]]);
-  else if (descriptor.doubleValues && descriptor.defaultValue.length == 1)
-    return wrapSingle([[name, property.important], [descriptor.defaultValue[0]]]);
-  else
-    return wrapSingle([[name, property.important], [descriptor.defaultValue]]);
+  if (descriptor.doubleValues && descriptor.defaultValue.length == 2) {
+    return wrapSingle([
+      Token.PROPERTY,
+      [Token.PROPERTY_NAME, name],
+      [Token.PROPERTY_VALUE, descriptor.defaultValue[0]],
+      [Token.PROPERTY_VALUE, descriptor.defaultValue[1]]
+    ]);
+  } else if (descriptor.doubleValues && descriptor.defaultValue.length == 1) {
+    return wrapSingle([
+      Token.PROPERTY,
+      [Token.PROPERTY_NAME, name],
+      [Token.PROPERTY_VALUE, descriptor.defaultValue[0]]
+    ]);
+  } else {
+    return wrapSingle([
+      Token.PROPERTY,
+      [Token.PROPERTY_NAME, name],
+      [Token.PROPERTY_VALUE, descriptor.defaultValue]
+    ]);
+  }
 }
 
 function _widthFilter(validator) {
   return function (value) {
-    return value[0] != 'inherit' && validator.isValidWidth(value[0]) && !validator.isValidStyleKeyword(value[0]) && !validator.isValidColorValue(value[0]);
+    return value[1] != 'inherit' && validator.isValidWidth(value[1]) && !validator.isValidStyleKeyword(value[1]) && !validator.isValidColorValue(value[1]);
   };
 }
 
@@ -49,18 +64,25 @@ function background(property, compactable, validator) {
   var originSet = false;
   var repeatSet = false;
 
-  if (property.value.length == 1 && property.value[0][0] == 'inherit') {
+  var anyValueSet = false;
+
+  if (property.value.length == 1 && property.value[0][1] == 'inherit') {
     // NOTE: 'inherit' is not a valid value for background-attachment
     color.value = image.value =  repeat.value = position.value = size.value = origin.value = clip.value = property.value;
     return components;
   }
 
+  if (property.value.length == 1 && property.value[0][1] == '0 0') {
+    return components;
+  }
+
   for (var i = values.length - 1; i >= 0; i--) {
     var value = values[i];
 
-    if (validator.isValidBackgroundAttachment(value[0])) {
+    if (validator.isValidBackgroundAttachment(value[1])) {
       attachment.value = [value];
-    } else if (validator.isValidBackgroundBox(value[0])) {
+      anyValueSet = true;
+    } else if (validator.isValidBackgroundBox(value[1])) {
       if (clipSet) {
         origin.value = [value];
         originSet = true;
@@ -68,27 +90,24 @@ function background(property, compactable, validator) {
         clip.value = [value];
         clipSet = true;
       }
-    } else if (validator.isValidBackgroundRepeat(value[0])) {
+      anyValueSet = true;
+    } else if (validator.isValidBackgroundRepeat(value[1])) {
       if (repeatSet) {
         repeat.value.unshift(value);
       } else {
         repeat.value = [value];
         repeatSet = true;
       }
-    } else if (validator.isValidBackgroundPositionPart(value[0]) || validator.isValidBackgroundSizePart(value[0])) {
+      anyValueSet = true;
+    } else if (validator.isValidBackgroundPositionPart(value[1]) || validator.isValidBackgroundSizePart(value[1])) {
       if (i > 0) {
         var previousValue = values[i - 1];
 
-        if (previousValue[0].indexOf('/') > 0) {
-          var twoParts = split(previousValue[0], '/');
-          // NOTE: we do this slicing as value may contain metadata too, like for source maps
-          size.value = [[twoParts.pop()].concat(previousValue.slice(1)), value];
-          values[i - 1] = [twoParts.pop()].concat(previousValue.slice(1));
-        } else if (i > 1 && values[i - 2][0] == '/') {
+        if (previousValue[1] == '/') {
+          size.value = [value];
+        } else if (i > 1 && values[i - 2][1] == '/') {
           size.value = [previousValue, value];
           i -= 2;
-        } else if (previousValue[0] == '/') {
-          size.value = [value];
         } else {
           if (!positionSet)
             position.value = [];
@@ -103,21 +122,23 @@ function background(property, compactable, validator) {
         position.value.unshift(value);
         positionSet = true;
       }
-    } else if (validator.isValidBackgroundPositionAndSize(value[0])) {
-      var sizeValue = split(value[0], '/');
-      // NOTE: we do this slicing as value may contain metadata too, like for source maps
-      size.value = [[sizeValue.pop()].concat(value.slice(1))];
-      position.value = [[sizeValue.pop()].concat(value.slice(1))];
-    } else if ((color.value[0][0] == compactable[color.name].defaultValue || color.value[0][0] == 'none') && validator.isValidColor(value[0])) {
+      anyValueSet = true;
+    } else if ((color.value[0][1] == compactable[color.name].defaultValue || color.value[0][1] == 'none') && validator.isValidColor(value[1])) {
       color.value = [value];
-    } else if (validator.isValidUrl(value[0]) || validator.isValidFunction(value[0])) {
+      anyValueSet = true;
+    } else if (validator.isValidUrl(value[1]) || validator.isValidFunction(value[1])) {
       image.value = [value];
+      anyValueSet = true;
     }
   }
 
   if (clipSet && !originSet)
     origin.value = clip.value.slice(0);
 
+  if (!anyValueSet) {
+    throw new InvalidPropertyError('Invalid background value.');
+  }
+
   return components;
 }
 
@@ -126,7 +147,7 @@ function borderRadius(property, compactable) {
   var splitAt = -1;
 
   for (var i = 0, l = values.length; i < l; i++) {
-    if (values[i][0] == '/') {
+    if (values[i][1] == '/') {
       splitAt = i;
       break;
     }
@@ -172,7 +193,10 @@ function fourValues(property, compactable) {
     value[3] = value[1].slice(0);
 
   for (var i = componentNames.length - 1; i >= 0; i--) {
-    var component = wrapSingle([[componentNames[i], property.important]]);
+    var component = wrapSingle([
+      Token.PROPERTY,
+      [Token.PROPERTY_NAME, componentNames[i]]
+    ]);
     component.value = [value[i]];
     components.unshift(component);
   }
@@ -188,7 +212,7 @@ function multiplex(splitWith) {
 
     // find split commas
     for (i = 0, l = values.length; i < l; i++) {
-      if (values[i][0] == ',')
+      if (values[i][1] == ',')
         splitsAt.push(i);
     }
 
@@ -215,7 +239,7 @@ function multiplex(splitWith) {
       components[i].multiplex = true;
 
       for (j = 1, m = splitComponents.length; j < m; j++) {
-        components[i].value.push([MULTIPLEX_SEPARATOR]);
+        components[i].value.push([Token.PROPERTY_VALUE, MULTIPLEX_SEPARATOR]);
         Array.prototype.push.apply(components[i].value, splitComponents[j][i].value);
       }
     }
@@ -230,7 +254,7 @@ function listStyle(property, compactable, validator) {
   var image = _wrapDefault('list-style-image', property, compactable);
   var components = [type, position, image];
 
-  if (property.value.length == 1 && property.value[0][0] == 'inherit') {
+  if (property.value.length == 1 && property.value[0][1] == 'inherit') {
     type.value = position.value = image.value = [property.value[0]];
     return components;
   }
@@ -241,7 +265,7 @@ function listStyle(property, compactable, validator) {
 
   // `image` first...
   for (index = 0, total = values.length; index < total; index++) {
-    if (validator.isValidUrl(values[index][0]) || values[index][0] == '0') {
+    if (validator.isValidUrl(values[index][1]) || values[index][1] == '0') {
       image.value = [values[index]];
       values.splice(index, 1);
       break;
@@ -250,7 +274,7 @@ function listStyle(property, compactable, validator) {
 
   // ... then `type`...
   for (index = 0, total = values.length; index < total; index++) {
-    if (validator.isValidListStyleType(values[index][0])) {
+    if (validator.isValidListStyleType(values[index][1])) {
       type.value = [values[index]];
       values.splice(index, 1);
       break;
@@ -258,7 +282,7 @@ function listStyle(property, compactable, validator) {
   }
 
   // ... and what's left is a `position`
-  if (values.length > 0 && validator.isValidListStylePosition(values[0][0]))
+  if (values.length > 0 && validator.isValidListStylePosition(values[0][1]))
     position.value = [values[0]];
 
   return components;
@@ -284,8 +308,8 @@ function widthStyleColor(property, compactable, validator) {
       width = component;
   }
 
-  if ((property.value.length == 1 && property.value[0][0] == 'inherit') ||
-      (property.value.length == 3 && property.value[0][0] == 'inherit' && property.value[1][0] == 'inherit' && property.value[2][0] == 'inherit')) {
+  if ((property.value.length == 1 && property.value[0][1] == 'inherit') ||
+      (property.value.length == 3 && property.value[0][1] == 'inherit' && property.value[1][1] == 'inherit' && property.value[2][1] == 'inherit')) {
     color.value = style.value = width.value = [property.value[0]];
     return components;
   }
@@ -298,7 +322,7 @@ function widthStyleColor(property, compactable, validator) {
 
   if (values.length > 0) {
     matches = values.filter(_widthFilter(validator));
-    match = matches.length > 1 && (matches[0][0] == 'none' || matches[0][0] == 'auto') ? matches[1] : matches[0];
+    match = matches.length > 1 && (matches[0][1] == 'none' || matches[0][1] == 'auto') ? matches[1] : matches[0];
     if (match) {
       width.value = [match];
       values.splice(values.indexOf(match), 1);
index 474e237..6405e97 100644 (file)
@@ -11,8 +11,8 @@ function always() {
 }
 
 function alwaysButIntoFunction(property1, property2, validator) {
-  var value1 = property1.value[0][0];
-  var value2 = property2.value[0][0];
+  var value1 = property1.value[0][1];
+  var value2 = property2.value[0][1];
 
   var validFunction1 = validator.isValidFunction(value1);
   var validFunction2 = validator.isValidFunction(value2);
@@ -31,8 +31,8 @@ function backgroundImage(property1, property2, validator) {
   // Understandability: (none | url | inherit) > (same function) > (same value)
 
   // (none | url)
-  var image1 = property1.value[0][0];
-  var image2 = property2.value[0][0];
+  var image1 = property1.value[0][1];
+  var image2 = property2.value[0][1];
 
   if (image2 == 'none' || image2 == 'inherit' || validator.isValidUrl(image2))
     return true;
@@ -53,8 +53,8 @@ function color(property1, property2, validator) {
   // Understandability: (hex | named) > (rgba | hsla) > (same function name) > anything else
   // NOTE: at this point rgb and hsl are replaced by hex values by clean-css
 
-  var color1 = property1.value[0][0];
-  var color2 = property2.value[0][0];
+  var color1 = property1.value[0][1];
+  var color2 = property2.value[0][1];
 
   if (!validator.colorOpacity && (validator.isValidRgbaColor(color1) || validator.isValidHslaColor(color1)))
     return false;
@@ -78,22 +78,22 @@ function color(property1, property2, validator) {
 }
 
 function twoOptionalFunctions(property1, property2, validator) {
-  var value1 = property1.value[0][0];
-  var value2 = property2.value[0][0];
+  var value1 = property1.value[0][1];
+  var value2 = property2.value[0][1];
 
   return !(validator.isValidFunction(value1) ^ validator.isValidFunction(value2));
 }
 
 function sameValue(property1, property2) {
-  var value1 = property1.value[0][0];
-  var value2 = property2.value[0][0];
+  var value1 = property1.value[0][1];
+  var value2 = property2.value[0][1];
 
   return value1 === value2;
 }
 
 function sameFunctionOrValue(property1, property2, validator) {
-  var value1 = property1.value[0][0];
-  var value2 = property2.value[0][0];
+  var value1 = property1.value[0][1];
+  var value2 = property2.value[0][1];
 
   // Functions with the same name can override each other
   if (validator.areSameFunction(value1, value2))
@@ -109,8 +109,8 @@ function unit(property1, property2, validator) {
   // NOTE: there is no point in having different vendor-specific functions override each other or standard functions,
   //       or having standard functions override vendor-specific functions, but standard functions can override each other
   // NOTE: vendor-specific property values are not taken into consideration here at the moment
-  var value1 = property1.value[0][0];
-  var value2 = property2.value[0][0];
+  var value1 = property1.value[0][1];
+  var value2 = property2.value[0][1];
 
   if (validator.isValidAndCompatibleUnitWithoutFunction(value1) && !validator.isValidAndCompatibleUnitWithoutFunction(value2))
     return false;
index 5be6441..05c1b17 100644 (file)
@@ -1,4 +1,5 @@
 var wrapSingle = require('./wrap-for-optimizing').single;
+var Token = require('../tokenizer/token');
 
 function deep(property) {
   var cloned = shallow(property);
@@ -15,7 +16,12 @@ function deep(property) {
 }
 
 function shallow(property) {
-  var cloned = wrapSingle([[property.name, property.important, property.hack]]);
+  var cloned = wrapSingle([
+    Token.PROPERTY,
+    [Token.PROPERTY_NAME, property.name]
+  ]);
+  cloned.important = property.important;
+  cloned.hack = property.hack;
   cloned.unused = false;
   return cloned;
 }
index be3faa6..71a1c0e 100644 (file)
@@ -1,6 +1,6 @@
 var shallowClone = require('./clone').shallow;
 
-var MULTIPLEX_SEPARATOR = ',';
+var Marker = require('../tokenizer/marker');
 
 function everyCombination(fn, left, right, validator) {
   var samePositon = !left.shorthand && !right.shorthand && !left.multiplex && !right.multiplex;
@@ -9,7 +9,7 @@ function everyCombination(fn, left, right, validator) {
 
   for (var i = 0, l = left.value.length; i < l; i++) {
     for (var j = 0, m = right.value.length; j < m; j++) {
-      if (left.value[i][0] == MULTIPLEX_SEPARATOR || right.value[j][0] == MULTIPLEX_SEPARATOR)
+      if (left.value[i][1] == Marker.COMMA || right.value[j][1] == Marker.COMMA)
         continue;
 
       if (samePositon && i != j)
diff --git a/lib/properties/hack.js b/lib/properties/hack.js
new file mode 100644 (file)
index 0000000..0e769e4
--- /dev/null
@@ -0,0 +1,8 @@
+var Hack = {
+  BANG: 'bang',
+  BACKSLASH: 'backslash',
+  STAR: 'star',
+  UNDERSCORE: 'underscore'
+};
+
+module.exports = Hack;
index 96a103b..84f220d 100644 (file)
@@ -1,6 +1,6 @@
 function hasInherit(property) {
   for (var i = property.value.length - 1; i >= 0; i--) {
-    if (property.value[i][0] == 'inherit')
+    if (property.value[i][1] == 'inherit')
       return true;
   }
 
index 855b7f9..565158b 100644 (file)
@@ -189,23 +189,24 @@ function _optimize(properties, mergeAdjacent, aggressiveMerging, validator) {
   }
 }
 
-function optimize(selector, properties, mergeAdjacent, withCompacting, options, context) {
+function optimize(selector, properties, mergeAdjacent, withCompacting, context) {
   var validator = context.validator;
   var warnings = context.warnings;
 
   var _properties = wrapForOptimizing(properties);
   populateComponents(_properties, validator, warnings);
-  _optimize(_properties, mergeAdjacent, options.aggressiveMerging, validator);
+  _optimize(_properties, mergeAdjacent, context.options.aggressiveMerging, validator);
 
   for (var i = 0, l = _properties.length; i < l; i++) {
     var _property = _properties[i];
-    if (_property.variable && _property.block)
-      optimize(selector, _property.value[0], mergeAdjacent, withCompacting, options, context);
+    if (_property.block) {
+      optimize(selector, _property.value[0][1], mergeAdjacent, withCompacting, context);
+    }
   }
 
-  if (withCompacting && options.shorthandCompacting) {
-    compactOverrides(_properties, options.compatibility, validator);
-    compactShorthands(_properties, options.sourceMap, validator);
+  if (withCompacting && context.options.shorthandCompacting) {
+    compactOverrides(_properties, context.options.compatibility, validator);
+    compactShorthands(_properties, validator);
   }
 
   restoreFromOptimizing(_properties);
index bbf13ed..2efb829 100644 (file)
@@ -9,7 +9,8 @@ var sameVendorPrefixesIn = require('./vendor-prefixes').same;
 
 var stringifyProperty = require('../stringifier/one-time').property;
 
-var MULTIPLEX_SEPARATOR = ',';
+var Token = require('../tokenizer/token');
+var Marker = require('../tokenizer/marker');
 
 // Used when searching for a component that matches property
 function nameMatchFilter(to) {
@@ -25,7 +26,7 @@ function wouldBreakCompatibility(property, validator) {
     var canOverride = descriptor && descriptor.canOverride || canOverride.sameValue;
 
     var _component = shallowClone(component);
-    _component.value = [[descriptor.defaultValue]];
+    _component.value = [[Token.PROPERTY_VALUE, descriptor.defaultValue]];
 
     if (!canOverride(_component, component, validator))
       return true;
@@ -84,7 +85,7 @@ function turnIntoMultiplex(property, size) {
     var value = component.value.slice(0);
 
     for (var j = 1; j < size; j++) {
-      component.value.push([MULTIPLEX_SEPARATOR]);
+      component.value.push([Token.PROPERTY_VALUE, Marker.COMMA]);
       Array.prototype.push.apply(component.value, value);
     }
   }
@@ -94,7 +95,7 @@ function multiplexSize(component) {
   var size = 0;
 
   for (var i = 0, l = component.value.length; i < l; i++) {
-    if (component.value[i][0] == MULTIPLEX_SEPARATOR)
+    if (component.value[i][1] == Marker.COMMA)
       size++;
   }
 
@@ -102,7 +103,10 @@ function multiplexSize(component) {
 }
 
 function lengthOf(property) {
-  var fakeAsArray = [[property.name]].concat(property.value);
+  var fakeAsArray = [
+    Token.PROPERTY,
+    [Token.PROPERTY_NAME, property.name]
+  ].concat(property.value);
   return stringifyProperty([fakeAsArray], 0).length;
 }
 
@@ -133,10 +137,10 @@ function overridingFunction(shorthand, validator) {
 
 function anyValue(fn, property) {
   for (var i = 0, l = property.value.length; i < l; i++) {
-    if (property.value[i][0] == MULTIPLEX_SEPARATOR)
+    if (property.value[i][1] == Marker.COMMA)
       continue;
 
-    if (fn(property.value[i][0]))
+    if (fn(property.value[i][1]))
       return true;
   }
 
@@ -172,7 +176,7 @@ function wouldResultInLongerValue(left, right) {
 
   var lengthAfter = lengthOf(simpleClone);
 
-  return lengthBefore < lengthAfter;
+  return lengthBefore <= lengthAfter;
 }
 
 function isCompactable(property) {
@@ -191,7 +195,7 @@ function anyLayerIsNone(values) {
   var layers = intoLayers(values);
 
   for (var i = 0, l = layers.length; i < l; i++) {
-    if (layers[i].length == 1 && layers[i][0][0] == 'none')
+    if (layers[i].length == 1 && layers[i][0][1] == 'none')
       return true;
   }
 
@@ -203,7 +207,7 @@ function intoLayers(values) {
 
   for (var i = 0, layer = [], l = values.length; i < l; i++) {
     var value = values[i];
-    if (value[0] == MULTIPLEX_SEPARATOR) {
+    if (value[1] == Marker.COMMA) {
       layers.push(layer);
       layer = [];
     } else {
@@ -226,7 +230,7 @@ function compactOverrides(properties, compatibility, validator) {
     if (!isCompactable(right))
       continue;
 
-    if (right.variable)
+    if (right.block)
       continue;
 
     mayOverride = compactable[right.name].canOverride || canOverride.sameValue;
@@ -237,7 +241,7 @@ function compactOverrides(properties, compatibility, validator) {
       if (!isCompactable(left))
         continue;
 
-      if (left.variable)
+      if (left.block)
         continue;
 
       if (left.unused || right.unused)
@@ -291,7 +295,7 @@ function compactOverrides(properties, compatibility, validator) {
             !compatibility.properties.backgroundClipMerging && component.name.indexOf('background-clip') > -1 ||
             !compatibility.properties.backgroundOriginMerging && component.name.indexOf('background-origin') > -1 ||
             !compatibility.properties.backgroundSizeMerging && component.name.indexOf('background-size') > -1;
-          var nonMergeableValue = compactable[right.name].nonMergeableValue === right.value[0][0];
+          var nonMergeableValue = compactable[right.name].nonMergeableValue === right.value[0][1];
 
           if (disabledBackgroundMerging || nonMergeableValue)
             continue;
@@ -299,7 +303,7 @@ function compactOverrides(properties, compatibility, validator) {
           if (!compatibility.properties.merging && wouldBreakCompatibility(left, validator))
             continue;
 
-          if (component.value[0][0] != right.value[0][0] && (hasInherit(left) || hasInherit(right)))
+          if (component.value[0][1] != right.value[0][1] && (hasInherit(left) || hasInherit(right)))
             continue;
 
           if (wouldResultInLongerValue(left, right))
index 08cb9b6..7b90c40 100644 (file)
@@ -2,8 +2,9 @@ function removeUnused(properties) {
   for (var i = properties.length - 1; i >= 0; i--) {
     var property = properties[i];
 
-    if (property.unused)
+    if (property.unused) {
       property.all.splice(property.position, 1);
+    }
   }
 }
 
index f0dfc18..02424dc 100644 (file)
@@ -1,3 +1,5 @@
+var Hack = require('./hack');
+var Marker = require('../tokenizer/marker');
 var compactable = require('./compactable');
 
 var BACKSLASH_HACK = '\\9';
@@ -6,32 +8,24 @@ var STAR_HACK = '*';
 var UNDERSCORE_HACK = '_';
 var BANG_HACK = '!ie';
 
-function restoreImportant(property) {
-  property.value[property.value.length - 1][0] += IMPORTANT_TOKEN;
-}
-
-function restoreHack(property) {
-  if (property.hack == 'underscore')
-    property.name = UNDERSCORE_HACK + property.name;
-  else if (property.hack == 'star')
-    property.name = STAR_HACK + property.name;
-  else if (property.hack == 'backslash')
-    property.value[property.value.length - 1][0] += BACKSLASH_HACK;
-  else if (property.hack == 'bang')
-    property.value[property.value.length - 1][0] += ' ' + BANG_HACK;
-}
-
 function restoreFromOptimizing(properties, simpleMode) {
-  for (var i = properties.length - 1; i >= 0; i--) {
-    var property = properties[i];
-    var descriptor = compactable[property.name];
-    var restored;
+  var property;
+  var descriptor;
+  var restored;
+  var current;
+  var i;
+
+  for (i = properties.length - 1; i >= 0; i--) {
+    property = properties[i];
+    descriptor = compactable[property.name];
 
-    if (property.unused)
+    if (property.unused) {
       continue;
+    }
 
-    if (!property.dirty && !property.important && !property.hack)
+    if (!property.dirty && !property.important && !property.hack) {
       continue;
+    }
 
     if (!simpleMode && descriptor && descriptor.shorthand) {
       restored = descriptor.restore(property, compactable);
@@ -40,20 +34,37 @@ function restoreFromOptimizing(properties, simpleMode) {
       restored = property.value;
     }
 
-    if (property.important)
+    if (property.important) {
       restoreImportant(property);
+    }
 
-    if (property.hack)
+    if (property.hack) {
       restoreHack(property);
+    }
 
-    if (!('all' in property))
-      continue;
+    if ('all' in property) {
+      current = property.all[property.position];
+      current[1][1] = property.name;
 
-    var current = property.all[property.position];
-    current[0][0] = property.name;
+      current.splice(2, current.length - 1);
+      Array.prototype.push.apply(current, restored);
+    }
+  }
+}
 
-    current.splice(1, current.length - 1);
-    Array.prototype.push.apply(current, restored);
+function restoreImportant(property) {
+  property.value[property.value.length - 1][1] += IMPORTANT_TOKEN;
+}
+
+function restoreHack(property) {
+  if (property.hack == Hack.UNDERSCORE) {
+    property.name = UNDERSCORE_HACK + property.name;
+  } else if (property.hack == Hack.STAR) {
+    property.name = STAR_HACK + property.name;
+  } else if (property.hack == Hack.BACKSLASH) {
+    property.value[property.value.length - 1][1] += BACKSLASH_HACK;
+  } else if (property.hack == Hack.BANG) {
+    property.value[property.value.length - 1][1] += Marker.SPACE + BANG_HACK;
   }
 }
 
index a9f8e6f..a770f74 100644 (file)
@@ -1,12 +1,12 @@
 var shallowClone = require('./clone').shallow;
-var MULTIPLEX_SEPARATOR = ',';
-var SIZE_POSITION_SEPARATOR = '/';
+var Token = require('../tokenizer/token');
+var Marker = require('../tokenizer/marker');
 
 function isInheritOnly(values) {
   for (var i = 0, l = values.length; i < l; i++) {
-    var value = values[i][0];
+    var value = values[i][1];
 
-    if (value != 'inherit' && value != MULTIPLEX_SEPARATOR && value != SIZE_POSITION_SEPARATOR)
+    if (value != 'inherit' && value != Marker.COMMA && value != Marker.FORWARD_SLASH)
       return false;
   }
 
@@ -24,13 +24,13 @@ function background(property, compactable, lastInMultiplex) {
 
   function isDefaultValue(component) {
     var descriptor = compactable[component.name];
-    if (descriptor.doubleValues) {
-      if (descriptor.defaultValue.length == 1)
-        return component.value[0][0] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][0] == descriptor.defaultValue[0] : true);
-      else
-        return component.value[0][0] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][0] : component.value[0][0]) == descriptor.defaultValue[1];
+
+    if (descriptor.doubleValues && descriptor.defaultValue.length == 1) {
+      return component.value[0][1] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][1] == descriptor.defaultValue[0] : true);
+    } else if (descriptor.doubleValues && descriptor.defaultValue.length != 1) {
+      return component.value[0][1] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][1] : component.value[0][1]) == descriptor.defaultValue[1];
     } else {
-      return component.value[0][0] == descriptor.defaultValue;
+      return component.value[0][1] == descriptor.defaultValue;
     }
   }
 
@@ -42,12 +42,12 @@ function background(property, compactable, lastInMultiplex) {
       var originComponent = components[i - 1];
       var isOriginDefault = isDefaultValue(originComponent);
 
-      needsOne = component.value[0][0] == originComponent.value[0][0];
+      needsOne = component.value[0][1] == originComponent.value[0][1];
 
       needsBoth = !needsOne && (
         (isOriginDefault && !isDefault) ||
         (!isOriginDefault && !isDefault) ||
-        (!isOriginDefault && isDefault && component.value[0][0] != originComponent.value[0][0]));
+        (!isOriginDefault && isDefault && component.value[0][1] != originComponent.value[0][1]));
 
       if (needsOne) {
         restoreValue(originComponent);
@@ -70,7 +70,7 @@ function background(property, compactable, lastInMultiplex) {
         restoreValue(positionComponent);
       } else if (needsBoth) {
         restoreValue(component);
-        restored.unshift([SIZE_POSITION_SEPARATOR]);
+        restored.unshift([Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]);
         restoreValue(positionComponent);
       } else if (positionComponent.value.length == 1) {
         restoreValue(positionComponent);
@@ -85,11 +85,11 @@ function background(property, compactable, lastInMultiplex) {
     }
   }
 
-  if (restored.length === 0 && property.value.length == 1 && property.value[0][0] == '0')
+  if (restored.length === 0 && property.value.length == 1 && property.value[0][1] == '0')
     restored.push(property.value[0]);
 
   if (restored.length === 0)
-    restored.push([compactable[property.name].defaultValue]);
+    restored.push([Token.PROPERTY_VALUE, compactable[property.name].defaultValue]);
 
   if (isInheritOnly(restored))
     return [restored[0]];
@@ -121,13 +121,13 @@ function borderRadius(property, compactable) {
     var verticalValues = fourValues(vertical, compactable);
 
     if (horizontalValues.length == verticalValues.length &&
-        horizontalValues[0][0] == verticalValues[0][0] &&
-        (horizontalValues.length > 1 ? horizontalValues[1][0] == verticalValues[1][0] : true) &&
-        (horizontalValues.length > 2 ? horizontalValues[2][0] == verticalValues[2][0] : true) &&
-        (horizontalValues.length > 3 ? horizontalValues[3][0] == verticalValues[3][0] : true)) {
+        horizontalValues[0][1] == verticalValues[0][1] &&
+        (horizontalValues.length > 1 ? horizontalValues[1][1] == verticalValues[1][1] : true) &&
+        (horizontalValues.length > 2 ? horizontalValues[2][1] == verticalValues[2][1] : true) &&
+        (horizontalValues.length > 3 ? horizontalValues[3][1] == verticalValues[3][1] : true)) {
       return horizontalValues;
     } else {
-      return horizontalValues.concat([['/']]).concat(verticalValues);
+      return horizontalValues.concat([[Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]]).concat(verticalValues);
     }
   } else {
     return fourValues(property, compactable);
@@ -141,11 +141,11 @@ function fourValues(property) {
   var value3 = components[2].value[0];
   var value4 = components[3].value[0];
 
-  if (value1[0] == value2[0] && value1[0] == value3[0] && value1[0] == value4[0]) {
+  if (value1[1] == value2[1] && value1[1] == value3[1] && value1[1] == value4[1]) {
     return [value1];
-  } else if (value1[0] == value3[0] && value2[0] == value4[0]) {
+  } else if (value1[1] == value3[1] && value2[1] == value4[1]) {
     return [value1, value2];
-  } else if (value2[0] == value4[0]) {
+  } else if (value2[1] == value4[1]) {
     return [value1, value2, value3];
   } else {
     return [value1, value2, value3, value4];
@@ -164,7 +164,7 @@ function multiplex(restoreWith) {
 
     // At this point we don't know what's the multiplex size, e.g. how many background layers are there
     for (i = 0, l = property.components[0].value.length; i < l; i++) {
-      if (property.components[0].value[i][0] == MULTIPLEX_SEPARATOR)
+      if (property.components[0].value[i][1] == Marker.COMMA)
         multiplexSize++;
     }
 
@@ -180,7 +180,7 @@ function multiplex(restoreWith) {
         // The trick is some properties has more than one value, so we iterate over values looking for
         // a multiplex separator - a comma
         for (var k = componentMultiplexSoFar[_component.name] || 0, n = componentToClone.value.length; k < n; k++) {
-          if (componentToClone.value[k][0] == MULTIPLEX_SEPARATOR) {
+          if (componentToClone.value[k][1] == Marker.COMMA) {
             componentMultiplexSoFar[_component.name] = k + 1;
             break;
           }
@@ -195,7 +195,7 @@ function multiplex(restoreWith) {
       Array.prototype.push.apply(restored, _restored);
 
       if (i < multiplexSize)
-        restored.push([',']);
+        restored.push([Token.PROPERTY_VALUE, Marker.COMMA]);
     }
 
     return restored;
@@ -210,12 +210,12 @@ function withoutDefaults(property, compactable) {
     var component = components[i];
     var descriptor = compactable[component.name];
 
-    if (component.value[0][0] != descriptor.defaultValue)
+    if (component.value[0][1] != descriptor.defaultValue)
       restored.unshift(component.value[0]);
   }
 
   if (restored.length === 0)
-    restored.push([compactable[property.name].defaultValue]);
+    restored.push([Token.PROPERTY_VALUE, compactable[property.name].defaultValue]);
 
   if (isInheritOnly(restored))
     return [restored[0]];
index 9be7623..5ee882a 100644 (file)
@@ -5,6 +5,8 @@ var populateComponents = require('./populate-components');
 var wrapSingle = require('./wrap-for-optimizing').single;
 var everyCombination = require('./every-combination');
 
+var Token = require('../tokenizer/token');
+
 function mixedImportance(components) {
   var important;
 
@@ -33,9 +35,13 @@ function componentSourceMaps(components) {
   return sourceMapping;
 }
 
-function replaceWithShorthand(properties, candidateComponents, name, sourceMaps, validator) {
+function replaceWithShorthand(properties, candidateComponents, name, validator) {
   var descriptor = compactable[name];
-  var newValuePlaceholder = [[name], [descriptor.defaultValue]];
+  var newValuePlaceholder = [
+    Token.PROPERTY,
+    [Token.PROPERTY_NAME, name],
+    [Token.PROPERTY_VALUE, descriptor.defaultValue]
+  ];
   var all;
 
   var newProperty = wrapSingle(newValuePlaceholder);
@@ -64,11 +70,10 @@ function replaceWithShorthand(properties, candidateComponents, name, sourceMaps,
     candidateComponents[componentName].unused = true;
   }
 
-  if (sourceMaps) {
-    var sourceMapping = componentSourceMaps(candidateComponents);
-    if (sourceMapping.length > 0)
-      newValuePlaceholder[0].push(sourceMapping);
-  }
+  // var sourceMapping = componentSourceMaps(candidateComponents);
+  // if (sourceMapping.length > 0)
+  //   newValuePlaceholder[0].push(sourceMapping);
+  // }
 
   newProperty.position = all.length;
   newProperty.all = all;
@@ -77,7 +82,7 @@ function replaceWithShorthand(properties, candidateComponents, name, sourceMaps,
   properties.push(newProperty);
 }
 
-function invalidateOrCompact(properties, position, candidates, sourceMaps, validator) {
+function invalidateOrCompact(properties, position, candidates, validator) {
   var property = properties[position];
 
   for (var name in candidates) {
@@ -94,11 +99,11 @@ function invalidateOrCompact(properties, position, candidates, sourceMaps, valid
     if (mixedImportance(candidateComponents))
       continue;
 
-    replaceWithShorthand(properties, candidateComponents, name, sourceMaps, validator);
+    replaceWithShorthand(properties, candidateComponents, name, validator);
   }
 }
 
-function compactShortands(properties, sourceMaps, validator) {
+function compactShortands(properties, validator) {
   var candidates = {};
 
   if (properties.length < 3)
@@ -112,7 +117,7 @@ function compactShortands(properties, sourceMaps, validator) {
     if (property.hack)
       continue;
 
-    if (property.variable)
+    if (property.block)
       continue;
 
     var descriptor = compactable[property.name];
@@ -120,7 +125,7 @@ function compactShortands(properties, sourceMaps, validator) {
       continue;
 
     if (property.shorthand) {
-      invalidateOrCompact(properties, i, candidates, sourceMaps, validator);
+      invalidateOrCompact(properties, i, candidates, validator);
     } else {
       var componentOf = descriptor.componentOf;
       candidates[componentOf] = candidates[componentOf] || {};
@@ -128,7 +133,7 @@ function compactShortands(properties, sourceMaps, validator) {
     }
   }
 
-  invalidateOrCompact(properties, i, candidates, sourceMaps, validator);
+  invalidateOrCompact(properties, i, candidates, validator);
 }
 
 module.exports = compactShortands;
index db4ee89..c3fa054 100644 (file)
@@ -1,7 +1,5 @@
 // Validates various CSS property values
 
-var split = require('../utils/split');
-
 var widthKeywords = ['thin', 'thick', 'medium', 'inherit', 'initial'];
 var allUnits = ['px', '%', 'em', 'in', 'cm', 'mm', 'ex', 'pt', 'pc', 'ch', 'rem', 'vh', 'vm', 'vmin', 'vmax', 'vw'];
 var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(' + allUnits.join('|') + '|)|auto|inherit)';
@@ -21,6 +19,8 @@ var cssUnitRegex = new RegExp('^' + cssUnitRegexStr + '$', 'i');
 var cssUnitOrCalcRegex = new RegExp('^' + cssUnitOrCalcRegexStr + '$', 'i');
 var cssUnitAnyRegex = new RegExp('^' + cssUnitAnyRegexStr + '$', 'i');
 
+var urlRegex = /^url\([\s\S]+\)$/i;
+
 var backgroundRepeatKeywords = ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'inherit'];
 var backgroundAttachmentKeywords = ['inherit', 'scroll', 'fixed', 'local'];
 var backgroundPositionKeywords = ['center', 'top', 'bottom', 'left', 'right'];
@@ -79,8 +79,7 @@ Validator.prototype.isValidColorValue = function (s) {
 };
 
 Validator.prototype.isValidUrl = function (s) {
-  // NOTE: at this point all URLs are replaced with placeholders by clean-css, so we check for those placeholders
-  return s.indexOf('__ESCAPED_URL_CLEAN_CSS') === 0;
+  return urlRegex.test(s);
 };
 
 Validator.prototype.isValidUnit = function (s) {
@@ -100,15 +99,15 @@ Validator.prototype.isValidAndCompatibleUnitWithoutFunction = function (s) {
 };
 
 Validator.prototype.isValidFunctionWithoutVendorPrefix = function (s) {
-  return cssFunctionNoVendorRegex.test(s);
+  return !urlRegex.test(s) && cssFunctionNoVendorRegex.test(s);
 };
 
 Validator.prototype.isValidFunctionWithVendorPrefix = function (s) {
-  return cssFunctionVendorRegex.test(s);
+  return !urlRegex.test(s) && cssFunctionVendorRegex.test(s);
 };
 
 Validator.prototype.isValidFunction = function (s) {
-  return cssFunctionAnyRegex.test(s);
+  return !urlRegex.test(s) && cssFunctionAnyRegex.test(s);
 };
 
 Validator.prototype.isValidBackgroundRepeat = function (s) {
@@ -148,14 +147,6 @@ Validator.prototype.isValidBackgroundSizePart = function (s) {
   return backgroundSizeKeywords.indexOf(s) >= 0 || cssUnitRegex.test(s) || this.isValidVariable(s);
 };
 
-Validator.prototype.isValidBackgroundPositionAndSize = function (s) {
-  if (s.indexOf('/') < 0)
-    return false;
-
-  var twoParts = split(s, '/');
-  return this.isValidBackgroundSizePart(twoParts.pop()) && this.isValidBackgroundPositionPart(twoParts.pop());
-};
-
 Validator.prototype.isValidListStyleType = function (s) {
   return listStyleTypeKeywords.indexOf(s) >= 0 || this.isValidVariable(s);
 };
index cb12a2b..22ad546 100644 (file)
@@ -1,20 +1,31 @@
-var BACKSLASH_HACK = '\\';
-var IMPORTANT_WORD = 'important';
-var IMPORTANT_TOKEN = '!'+IMPORTANT_WORD;
-var IMPORTANT_WORD_MATCH = new RegExp(IMPORTANT_WORD+'$', 'i');
-var IMPORTANT_TOKEN_MATCH = new RegExp(IMPORTANT_TOKEN+'$', 'i');
-var STAR_HACK = '*';
-var UNDERSCORE_HACK = '_';
-var BANG_HACK = '!';
+var Hack = require('./hack');
+var Marker = require('../tokenizer/marker');
+var Token = require('../tokenizer/token');
+
+var Match = {
+  BACKSLASH: '\\',
+  BANG: '!',
+  BANG_SUFFIX_PATTERN: /!\w+$/,
+  IMPORTANT_TOKEN: '!important',
+  IMPORTANT_TOKEN_PATTERN: new RegExp('!important$', 'i'),
+  IMPORTANT_WORD: 'important',
+  IMPORTANT_WORD_PATTERN: new RegExp('important$', 'i'),
+  STAR: '*',
+  SUFFIX_BANG_PATTERN: /!$/,
+  UNDERSCORE: '_'
+};
 
 function wrapAll(properties) {
   var wrapped = [];
+  var single;
+  var i;
 
-  for (var i = properties.length - 1; i >= 0; i--) {
-    if (typeof properties[i][0] == 'string')
+  for (i = properties.length - 1; i >= 0; i--) {
+    if (properties[i][0] != Token.PROPERTY) {
       continue;
+    }
 
-    var single = wrapSingle(properties[i]);
+    single = wrapSingle(properties[i]);
     single.all = properties;
     single.position = i;
     wrapped.unshift(single);
@@ -24,9 +35,15 @@ function wrapAll(properties) {
 }
 
 function isMultiplex(property) {
-  for (var i = 1, l = property.length; i < l; i++) {
-    if (property[i][0] == ',' || property[i][0] == '/')
+  var value;
+  var i, l;
+
+  for (i = 3, l = property.length; i < l; i++) {
+    value = property[i];
+
+    if (value[0] == Token.PROPERTY_VALUE && (value[1] == Marker.COMMA || value[1] == Marker.FORWARD_SLASH)) {
       return true;
+    }
   }
 
   return false;
@@ -34,81 +51,100 @@ function isMultiplex(property) {
 
 function hackType(property) {
   var type = false;
-  var name = property[0][0];
+  var name = property[1][1];
   var lastValue = property[property.length - 1];
 
-  if (name[0] == UNDERSCORE_HACK) {
-    type = 'underscore';
-  } else if (name[0] == STAR_HACK) {
-    type = 'star';
-  } else if (lastValue[0][0] == BANG_HACK && !lastValue[0].match(IMPORTANT_WORD_MATCH)) {
-    type = 'bang';
-  } else if (lastValue[0].indexOf(BANG_HACK) > 0 && !lastValue[0].match(IMPORTANT_WORD_MATCH)) {
-    type = 'bang';
-  } else if (lastValue[0].indexOf(BACKSLASH_HACK) > 0 && lastValue[0].indexOf(BACKSLASH_HACK) == lastValue[0].length - BACKSLASH_HACK.length - 1) {
-    type = 'backslash';
-  } else if (lastValue[0].indexOf(BACKSLASH_HACK) === 0 && lastValue[0].length == 2) {
-    type = 'backslash';
+  if (name[0] == Match.UNDERSCORE) {
+    type = Hack.UNDERSCORE;
+  } else if (name[0] == Match.STAR) {
+    type = Hack.STAR;
+  } else if (lastValue[1][0] == Match.BANG && !lastValue[1].match(Match.IMPORTANT_WORD_PATTERN)) {
+    type = Hack.BANG;
+  } else if (lastValue[1].indexOf(Match.BANG) > 0 && !lastValue[1].match(Match.IMPORTANT_WORD_PATTERN) && Match.BANG_SUFFIX_PATTERN.test(lastValue[1])) {
+    type = Hack.BANG;
+  } else if (lastValue[1].indexOf(Match.BACKSLASH) > 0 && lastValue[1].indexOf(Match.BACKSLASH) == lastValue[1].length - Match.BACKSLASH.length - 1) {
+    type = Hack.BACKSLASH;
+  } else if (lastValue[1].indexOf(Match.BACKSLASH) === 0 && lastValue[1].length == 2) {
+    type = Hack.BACKSLASH;
   }
 
   return type;
 }
 
 function isImportant(property) {
-  if (property.length > 1) {
-    var p = property[property.length - 1][0];
-    if (typeof(p) === 'string') {
-      return IMPORTANT_TOKEN_MATCH.test(p);
-    }
+  if (property.length < 3)
+    return false;
+
+  var lastValue = property[property.length - 1];
+  if (Match.IMPORTANT_TOKEN_PATTERN.test(lastValue[1])) {
+    return true;
+  } else if (Match.IMPORTANT_WORD_PATTERN.test(lastValue[1]) && Match.SUFFIX_BANG_PATTERN.test(property[property.length - 2][1])) {
+    return true;
   }
+
   return false;
 }
 
 function stripImportant(property) {
-  if (property.length > 0)
-    property[property.length - 1][0] = property[property.length - 1][0].replace(IMPORTANT_TOKEN_MATCH, '');
+  var lastValue = property[property.length - 1];
+  var oneButLastValue = property[property.length - 2];
+
+  if (Match.IMPORTANT_TOKEN_PATTERN.test(lastValue[1])) {
+    lastValue[1] = lastValue[1].replace(Match.IMPORTANT_TOKEN_PATTERN, '');
+  } else {
+    lastValue[1] = lastValue[1].replace(Match.IMPORTANT_WORD_PATTERN, '');
+    oneButLastValue[1] = oneButLastValue[1].replace(Match.SUFFIX_BANG_PATTERN, '');
+  }
+
+  if (lastValue[1].length === 0) {
+    property.pop();
+  }
+
+  if (oneButLastValue[1].length === 0) {
+    property.pop();
+  }
 }
 
 function stripPrefixHack(property) {
-  property[0][0] = property[0][0].substring(1);
+  property[1][1] = property[1][1].substring(1);
 }
 
 function stripSuffixHack(property, hackType) {
   var lastValue = property[property.length - 1];
-  lastValue[0] = lastValue[0]
-    .substring(0, lastValue[0].indexOf(hackType == 'backslash' ? BACKSLASH_HACK : BANG_HACK))
+  lastValue[1] = lastValue[1]
+    .substring(0, lastValue[1].indexOf(hackType == Hack.BACKSLASH ? Match.BACKSLASH : Match.BANG))
     .trim();
 
-  if (lastValue[0].length === 0)
+  if (lastValue[1].length === 0) {
     property.pop();
+  }
 }
 
 function wrapSingle(property) {
-  var _isImportant = isImportant(property);
-  if (_isImportant)
+  var importantProperty = isImportant(property);
+  if (importantProperty) {
     stripImportant(property);
+  }
 
-  var _hackType = hackType(property);
-  if (_hackType == 'star' || _hackType == 'underscore')
+  var hackProperty = hackType(property);
+  if (hackProperty == Hack.STAR || hackProperty == Hack.UNDERSCORE) {
     stripPrefixHack(property);
-  else if (_hackType == 'backslash' || _hackType == 'bang')
-    stripSuffixHack(property, _hackType);
-
-  var isVariable = property[0][0].indexOf('--') === 0;
+  } else if (hackProperty == Hack.BACKSLASH || hackProperty == Hack.BANG) {
+    stripSuffixHack(property, hackProperty);
+  }
 
   return {
-    block: isVariable && property[1] && Array.isArray(property[1][0][0]),
+    block: property[2] && property[2][0] == Token.PROPERTY_BLOCK,
     components: [],
     dirty: false,
-    hack: _hackType,
-    important: _isImportant,
-    name: property[0][0],
-    multiplex: property.length > 2 ? isMultiplex(property) : false,
+    hack: hackProperty,
+    important: importantProperty,
+    name: property[1][1],
+    multiplex: property.length > 3 ? isMultiplex(property) : false,
     position: 0,
     shorthand: false,
-    unused: property.length < 2,
-    value: property.slice(1),
-    variable: isVariable
+    unused: false,
+    value: property.slice(2)
   };
 }
 
diff --git a/lib/selectors/clean-up.js b/lib/selectors/clean-up.js
deleted file mode 100644 (file)
index 6bb17b9..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-function removeWhitespace(match, value) {
-  return '[' + value.replace(/ /g, '') + ']';
-}
-
-function selectorSorter(s1, s2) {
-  return s1[0] > s2[0] ? 1 : -1;
-}
-
-function whitespaceReplacements(_, p1, p2, p3) {
-  if (p1 && p2 && p3.length)
-    return p1 + p2 + ' ';
-  else if (p1 && p2)
-    return p1 + p2;
-  else
-    return p2;
-}
-
-var CleanUp = {
-  selectors: function (selectors, removeUnsupported, adjacentSpace) {
-    var list = [];
-    var repeated = [];
-
-    for (var i = 0, l = selectors.length; i < l; i++) {
-      var selector = selectors[i];
-      var reduced = selector[0]
-        .replace(/\s+/g, ' ')
-        .replace(/ ?, ?/g, ',')
-        .replace(/\s*(\\)?([>+~])(\s*)/g, whitespaceReplacements)
-        .trim();
-
-      if (adjacentSpace && reduced.indexOf('nav') > 0)
-        reduced = reduced.replace(/\+nav(\S|$)/, '+ nav$1');
-
-      if (removeUnsupported && (reduced.indexOf('*+html ') != -1 || reduced.indexOf('*:first-child+html ') != -1))
-        continue;
-
-      if (reduced.indexOf('*') > -1) {
-        reduced = reduced
-          .replace(/\*([:#\.\[])/g, '$1')
-          .replace(/^(\:first\-child)?\+html/, '*$1+html');
-      }
-
-      if (reduced.indexOf('[') > -1)
-        reduced = reduced.replace(/\[([^\]]+)\]/g, removeWhitespace);
-
-      if (repeated.indexOf(reduced) == -1) {
-        selector[0] = reduced;
-        repeated.push(reduced);
-        list.push(selector);
-      }
-    }
-
-    return list.sort(selectorSorter);
-  },
-
-  selectorDuplicates: function (selectors) {
-    var list = [];
-    var repeated = [];
-
-    for (var i = 0, l = selectors.length; i < l; i++) {
-      var selector = selectors[i];
-
-      if (repeated.indexOf(selector[0]) == -1) {
-        repeated.push(selector[0]);
-        list.push(selector);
-      }
-    }
-
-    return list.sort(selectorSorter);
-  },
-
-  block: function (values, spaceAfterClosingBrace) {
-    values[0] = values[0]
-      .replace(/\s+/g, ' ')
-      .replace(/(,|:|\() /g, '$1')
-      .replace(/ \)/g, ')');
-
-    if (!spaceAfterClosingBrace)
-      values[0] = values[0].replace(/\) /g, ')');
-  },
-
-  atRule: function (values) {
-    values[0] = values[0]
-      .replace(/\s+/g, ' ')
-      .trim();
-  }
-};
-
-module.exports = CleanUp;
diff --git a/lib/selectors/merge-adjacent.js b/lib/selectors/merge-adjacent.js
deleted file mode 100644 (file)
index 3de1c1f..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-var optimizeProperties = require('../properties/optimizer');
-
-var stringifyBody = require('../stringifier/one-time').body;
-var stringifySelectors = require('../stringifier/one-time').selectors;
-var cleanUpSelectors = require('./clean-up').selectors;
-var isSpecial = require('./is-special');
-
-function mergeAdjacent(tokens, options, context) {
-  var lastToken = [null, [], []];
-  var adjacentSpace = options.compatibility.selectors.adjacentSpace;
-
-  for (var i = 0, l = tokens.length; i < l; i++) {
-    var token = tokens[i];
-
-    if (token[0] != 'selector') {
-      lastToken = [null, [], []];
-      continue;
-    }
-
-    if (lastToken[0] == 'selector' && stringifySelectors(token[1]) == stringifySelectors(lastToken[1])) {
-      var joinAt = [lastToken[2].length];
-      Array.prototype.push.apply(lastToken[2], token[2]);
-      optimizeProperties(token[1], lastToken[2], joinAt, true, options, context);
-      token[2] = [];
-    } else if (lastToken[0] == 'selector' && stringifyBody(token[2]) == stringifyBody(lastToken[2]) &&
-        !isSpecial(options, stringifySelectors(token[1])) && !isSpecial(options, stringifySelectors(lastToken[1]))) {
-      lastToken[1] = cleanUpSelectors(lastToken[1].concat(token[1]), false, adjacentSpace);
-      token[2] = [];
-    } else {
-      lastToken = token;
-    }
-  }
-}
-
-module.exports = mergeAdjacent;
diff --git a/lib/selectors/remove-duplicate-media-queries.js b/lib/selectors/remove-duplicate-media-queries.js
deleted file mode 100644 (file)
index 44070b0..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-var stringifyAll = require('../stringifier/one-time').all;
-
-function removeDuplicateMediaQueries(tokens) {
-  var candidates = {};
-
-  for (var i = 0, l = tokens.length; i < l; i++) {
-    var token = tokens[i];
-    if (token[0] != 'block')
-      continue;
-
-    var key = token[1][0] + '%' + stringifyAll(token[2]);
-    var candidate = candidates[key];
-
-    if (candidate)
-      candidate[2] = [];
-
-    candidates[key] = token;
-  }
-}
-
-module.exports = removeDuplicateMediaQueries;
index d3f4abe..66aabb4 100644 (file)
@@ -1,43 +1,35 @@
 var lineBreak = require('os').EOL;
+var emptyCharacter = '';
 
-var AT_RULE = 'at-rule';
-var PROPERTY_SEPARATOR = ';';
-
-function hasMoreProperties(tokens, index) {
-  for (var i = index, l = tokens.length; i < l; i++) {
-    if (typeof tokens[i] != 'string')
-      return true;
-  }
-
-  return false;
-}
+var Marker = require('../tokenizer/marker');
+var Token = require('../tokenizer/token');
 
 function supportsAfterClosingBrace(token) {
-  return token[0][0] == 'background' || token[0][0] == 'transform' || token[0][0] == 'src';
+  return token[1][1] == 'background' || token[1][1] == 'transform' || token[1][1] == 'src';
 }
 
 function afterClosingBrace(token, valueIndex) {
-  return token[valueIndex][0][token[valueIndex][0].length - 1] == ')' || token[valueIndex][0].indexOf('__ESCAPED_URL_CLEAN_CSS') === 0;
+  return token[valueIndex][1][token[valueIndex][1].length - 1] == Marker.CLOSE_ROUND_BRACKET;
 }
 
 function afterComma(token, valueIndex) {
-  return token[valueIndex][0] == ',';
+  return token[valueIndex][1] == Marker.COMMA;
 }
 
 function afterSlash(token, valueIndex) {
-  return token[valueIndex][0] == '/';
+  return token[valueIndex][1] == Marker.FORWARD_SLASH;
 }
 
 function beforeComma(token, valueIndex) {
-  return token[valueIndex + 1] && token[valueIndex + 1][0] == ',';
+  return token[valueIndex + 1] && token[valueIndex + 1][1] == Marker.COMMA;
 }
 
 function beforeSlash(token, valueIndex) {
-  return token[valueIndex + 1] && token[valueIndex + 1][0] == '/';
+  return token[valueIndex + 1] && token[valueIndex + 1][1] == Marker.FORWARD_SLASH;
 }
 
 function inFilter(token) {
-  return token[0][0] == 'filter' || token[0][0] == '-ms-filter';
+  return token[1][1] == 'filter' || token[1][1] == '-ms-filter';
 }
 
 function inSpecialContext(token, valueIndex, context) {
@@ -48,112 +40,120 @@ function inSpecialContext(token, valueIndex, context) {
     afterComma(token, valueIndex);
 }
 
-function selectors(tokens, context) {
+function rules(tokens, context) {
   var store = context.store;
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     store(tokens[i], context);
 
-    if (i < l - 1)
-      store(',', context);
+    if (i < l - 1) {
+      store(Marker.COMMA, context);
+    }
   }
 }
 
 function body(tokens, context) {
+  var lastPropertyAt = lastPropertyIndex(tokens);
+
   for (var i = 0, l = tokens.length; i < l; i++) {
-    property(tokens, i, i == l - 1, context);
+    property(tokens, i, lastPropertyAt, context);
   }
 }
 
-function property(tokens, position, isLast, context) {
-  var store = context.store;
-  var token = tokens[position];
+function lastPropertyIndex(tokens) {
+  var index = tokens.length - 1;
 
-  if (typeof token == 'string') {
-    store(token, context);
-  } else if (token[0] == AT_RULE) {
-    propertyAtRule(token[1], isLast, context);
-  } else {
-    store(token[0], context);
-    store(':', context);
-    value(tokens, position, isLast, context);
+  for (; index >= 0; index--) {
+    if (tokens[index][0] != Token.COMMENT) {
+      break;
+    }
   }
+
+  return index;
 }
 
-function propertyAtRule(value, isLast, context) {
+function property(tokens, position, lastPropertyAt, context) {
   var store = context.store;
+  var token = tokens[position];
 
-  store(value, context);
-  if (!isLast)
-    store(PROPERTY_SEPARATOR, context);
+  switch (token[0]) {
+    case Token.AT_RULE:
+      // slicing here is a bit inconvenient but necessary since otherwise source map info
+      // would not be passed in. We should consider changing parsing to output an array as
+      // 2nd element. Unfortunately this also refers to properties... :-/
+      store(token.slice(1), context);
+      store(position < lastPropertyAt ? Marker.SEMICOLON : '', context);
+      break;
+    case Token.COMMENT:
+      store(token[1][0], context);
+      break;
+    case Token.PROPERTY:
+      store(token[1][1], context);
+      store(Marker.COLON, context);
+      value(token, context);
+      store(position < lastPropertyAt || token[2][0] == Token.PROPERTY_BLOCK ? Marker.SEMICOLON : '', context);
+  }
 }
 
-function value(tokens, position, isLast, context) {
+function value(token, context) {
   var store = context.store;
-  var token = tokens[position];
-  var isVariableDeclaration = token[0][0].indexOf('--') === 0;
-  var isBlockVariable = isVariableDeclaration && Array.isArray(token[1][0]);
-
-  if (isVariableDeclaration && isBlockVariable && atRulesOrProperties(token[1])) {
-    store('{', context);
-    body(token[1], context);
-    store('};', context);
-    return;
-  }
+  var j, m;
 
-  for (var j = 1, m = token.length; j < m; j++) {
-    store(token[j], context);
+  if (token[2][0] == Token.PROPERTY_BLOCK) {
+    store(Marker.OPEN_BRACE, context);
+    body(token[2][1], context);
+    store(Marker.CLOSE_BRACE, context);
+  } else {
+    for (j = 2, m = token.length; j < m; j++) {
+      store(token[j][1], context);
 
-    if (j < m - 1 && (inFilter(token) || !inSpecialContext(token, j, context))) {
-      store(' ', context);
-    } else if (j == m - 1 && !isLast && hasMoreProperties(tokens, position + 1)) {
-      store(PROPERTY_SEPARATOR, context);
+      if (j < m - 1 && (inFilter(token) || !inSpecialContext(token, j, context))) {
+        store(Marker.SPACE, context);
+      }
     }
   }
 }
 
-function atRulesOrProperties(values) {
-  for (var i = 0, l = values.length; i < l; i++) {
-    if (values[i][0] == AT_RULE || Array.isArray(values[i][0]))
-      return true;
-  }
-
-  return false;
-}
-
 function all(tokens, context) {
-  var joinCharacter = context.keepBreaks ? lineBreak : '';
+  var joinCharacter = context.keepBreaks ? lineBreak : emptyCharacter;
   var store = context.store;
+  var token;
+  var i, l;
 
-  for (var i = 0, l = tokens.length; i < l; i++) {
-    var token = tokens[i];
+  for (i = 0, l = tokens.length; i < l; i++) {
+    token = tokens[i];
 
     switch (token[0]) {
-      case 'at-rule':
-      case 'text':
-        store(token[1][0], context);
+      case Token.AT_RULE:
+        store(token[1], context);
+        store(Marker.SEMICOLON, context);
+        break;
+      case Token.AT_RULE_BLOCK:
+        rules(token[1], context);
         store(joinCharacter, context);
+        store(Marker.OPEN_BRACE, context);
+        body(token[2], context);
+        store(Marker.CLOSE_BRACE, context);
         break;
-      case 'block':
-        selectors([token[1]], context);
-        store('{', context);
+      case Token.BLOCK:
+        rules(token[1], context);
+        store(Marker.OPEN_BRACE, context);
         all(token[2], context);
-        store('}', context);
-        store(joinCharacter, context);
+        store(Marker.CLOSE_BRACE, context);
         break;
-      case 'flat-block':
-        selectors([token[1]], context);
-        store('{', context);
-        body(token[2], context);
-        store('}', context);
-        store(joinCharacter, context);
+      case Token.COMMENT:
+        store(token[1], context);
         break;
-      default:
-        selectors(token[1], context);
-        store('{', context);
+      case Token.RULE:
+        rules(token[1], context);
+        store(Marker.OPEN_BRACE, context);
         body(token[2], context);
-        store('}', context);
-        store(joinCharacter, context);
+        store(Marker.CLOSE_BRACE, context);
+        break;
+    }
+
+    if (i < l - 1) {
+      store(joinCharacter, context);
     }
   }
 }
@@ -162,6 +162,6 @@ module.exports = {
   all: all,
   body: body,
   property: property,
-  selectors: selectors,
+  rules: rules,
   value: value
 };
index 4a65c7f..abcf6c5 100644 (file)
@@ -29,15 +29,15 @@ function property(tokens, position) {
   return fakeContext.output.join('');
 }
 
-function selectors(tokens) {
+function rules(tokens) {
   var fakeContext = context();
-  helpers.selectors(tokens, fakeContext);
+  helpers.rules(tokens, fakeContext);
   return fakeContext.output.join('');
 }
 
-function value(tokens, position) {
+function value(tokens) {
   var fakeContext = context();
-  helpers.value(tokens, position, true, fakeContext);
+  helpers.value(tokens, fakeContext);
   return fakeContext.output.join('');
 }
 
@@ -45,6 +45,6 @@ module.exports = {
   all: all,
   body: body,
   property: property,
-  selectors: selectors,
+  rules: rules,
   value: value
 };
index 0673996..78f9277 100644 (file)
@@ -1,21 +1,21 @@
 var all = require('./helpers').all;
 
-function store(token, context) {
-  context.output.push(typeof token == 'string' ? token : token[0]);
+function store(token, stringifyContext) {
+  stringifyContext.output.push(typeof token == 'string' ? token : token[0]);
 }
 
-function stringify(tokens, options, restoreCallback) {
-  var context = {
-    keepBreaks: options.keepBreaks,
+function stringify(tokens, context) {
+  var stringifyContext = {
+    keepBreaks: context.options.keepBreaks,
     output: [],
-    spaceAfterClosingBrace: options.compatibility.properties.spaceAfterClosingBrace,
+    spaceAfterClosingBrace: context.options.compatibility.properties.spaceAfterClosingBrace,
     store: store
   };
 
-  all(tokens, context, false);
+  all(tokens, stringifyContext, false);
 
   return {
-    styles: restoreCallback(context.output.join('')).trim()
+    styles: stringifyContext.output.join('')
   };
 }
 
index 7748614..c8a1563 100644 (file)
@@ -71,25 +71,24 @@ function trackMapping(mapping, context) {
     context.outputMap.setSourceContent(source, mapping[3][mapping[2]]);
 }
 
-function stringify(tokens, options, restoreCallback, inputMapTracker) {
-  var context = {
+function stringify(tokens, context) {
+  var stringifyContext = {
     column: 0,
-    inputMapTracker: inputMapTracker,
-    keepBreaks: options.keepBreaks,
+    inputMapTracker: context.inputMapTracker,
+    keepBreaks: context.options.keepBreaks,
     line: 1,
     output: [],
     outputMap: new SourceMapGenerator(),
-    restore: restoreCallback,
-    sourceMapInlineSources: options.sourceMapInlineSources,
-    spaceAfterClosingBrace: options.compatibility.properties.spaceAfterClosingBrace,
+    sourceMapInlineSources: context.options.sourceMapInlineSources,
+    spaceAfterClosingBrace: context.options.compatibility.properties.spaceAfterClosingBrace,
     store: store
   };
 
-  all(tokens, context, false);
+  all(tokens, stringifyContext, false);
 
   return {
-    sourceMap: context.outputMap,
-    styles: context.output.join('').trim()
+    sourceMap: stringifyContext.outputMap,
+    styles: stringifyContext.output.join('')
   };
 }
 
index 1129c6e..dea6b0f 100644 (file)
@@ -3,6 +3,7 @@ var Marker = {
   BACK_SLASH: '\\',
   CLOSE_BRACE: '}',
   CLOSE_ROUND_BRACKET: ')',
+  CLOSE_SQUARE_BRACKET: ']',
   COLON: ':',
   COMMA: ',',
   DOUBLE_QUOTE: '"',
@@ -12,6 +13,7 @@ var Marker = {
   NEW_LINE_WIN: '\r',
   OPEN_BRACE: '{',
   OPEN_ROUND_BRACKET: '(',
+  OPEN_SQUARE_BRACKET: '[',
   SEMICOLON: ';',
   SINGLE_QUOTE: '\'',
   SPACE: ' ',
diff --git a/lib/urls/rebase-config.js b/lib/urls/rebase-config.js
new file mode 100644 (file)
index 0000000..9c1a5e7
--- /dev/null
@@ -0,0 +1,31 @@
+var path = require('path');
+
+function rebaseConfig(context) {
+  var config = {
+    absolute: context.options.explicitRoot,
+    relative: !context.options.explicitRoot && context.options.explicitTarget,
+    fromBase: context.options.relativeTo
+  };
+
+  if (!config.absolute && !config.relative) {
+    return null;
+  }
+
+  if (config.absolute && context.options.explicitTarget) {
+    context.warnings.push('Both \'root\' and output file given so rebasing URLs as absolute paths');
+  }
+
+  if (config.absolute) {
+    config.toBase = path.resolve(context.options.root);
+  }
+
+  if (config.relative) {
+    config.toBase = path.resolve(context.options.target);
+  }
+
+  return config.fromBase && config.toBase ?
+    config :
+    null;
+}
+
+module.exports = rebaseConfig;
diff --git a/lib/urls/rebase.js b/lib/urls/rebase.js
deleted file mode 100644 (file)
index 04346b1..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-var path = require('path');
-
-var rewriteUrls = require('./rewrite');
-
-function rebaseUrls(data, context) {
-  var rebaseOpts = {
-    absolute: context.options.explicitRoot,
-    relative: !context.options.explicitRoot && context.options.explicitTarget,
-    fromBase: context.options.relativeTo
-  };
-
-  if (!rebaseOpts.absolute && !rebaseOpts.relative)
-    return data;
-
-  if (rebaseOpts.absolute && context.options.explicitTarget)
-    context.warnings.push('Both \'root\' and output file given so rebasing URLs as absolute paths');
-
-  if (rebaseOpts.absolute)
-    rebaseOpts.toBase = path.resolve(context.options.root);
-
-  if (rebaseOpts.relative)
-    rebaseOpts.toBase = path.resolve(context.options.target);
-
-  if (!rebaseOpts.fromBase || !rebaseOpts.toBase)
-    return data;
-
-  return rewriteUrls(data, rebaseOpts, context);
-}
-
-module.exports = rebaseUrls;
diff --git a/lib/urls/reduce.js b/lib/urls/reduce.js
deleted file mode 100644 (file)
index 35ad555..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-var split = require('../utils/split');
-
-var URL_PREFIX = 'url(';
-var UPPERCASE_URL_PREFIX = 'URL(';
-var URL_SUFFIX = ')';
-var SINGLE_QUOTE_URL_SUFFIX = '\')';
-var DOUBLE_QUOTE_URL_SUFFIX = '")';
-
-var DATA_URI_PREFIX_PATTERN = /^\s*['"]?\s*data:/;
-var DATA_URI_TRAILER_PATTERN = /[\s\};,\/!]/;
-
-var IMPORT_URL_PREFIX = '@import';
-var UPPERCASE_IMPORT_URL_PREFIX = '@IMPORT';
-
-var COMMENT_END_MARKER = /\*\//;
-
-function byUrl(data, context, callback) {
-  var nextStart = 0;
-  var nextStartUpperCase = 0;
-  var nextEnd = 0;
-  var firstMatch;
-  var isDataURI = false;
-  var cursor = 0;
-  var tempData = [];
-  var hasUppercaseUrl = data.indexOf(UPPERCASE_URL_PREFIX) > -1;
-
-  for (; nextEnd < data.length;) {
-    nextStart = data.indexOf(URL_PREFIX, nextEnd);
-    nextStartUpperCase = hasUppercaseUrl ? data.indexOf(UPPERCASE_URL_PREFIX, nextEnd) : -1;
-    if (nextStart == -1 && nextStartUpperCase == -1)
-      break;
-
-    if (nextStart == -1 && nextStartUpperCase > -1)
-      nextStart = nextStartUpperCase;
-
-    if (data[nextStart + URL_PREFIX.length] == '"') {
-      nextEnd = data.indexOf(DOUBLE_QUOTE_URL_SUFFIX, nextStart);
-    } else if (data[nextStart + URL_PREFIX.length] == '\'') {
-      nextEnd = data.indexOf(SINGLE_QUOTE_URL_SUFFIX, nextStart);
-    } else {
-      isDataURI = DATA_URI_PREFIX_PATTERN.test(data.substring(nextStart + URL_PREFIX.length));
-
-      if (isDataURI) {
-        firstMatch = split(data.substring(nextStart), DATA_URI_TRAILER_PATTERN, false, '(', ')', true).pop();
-
-        if (firstMatch && firstMatch[firstMatch.length - 1] == URL_SUFFIX) {
-          nextEnd = nextStart + firstMatch.length - URL_SUFFIX.length;
-        } else {
-          nextEnd = -1;
-        }
-      } else {
-        nextEnd = data.indexOf(URL_SUFFIX, nextStart);
-      }
-    }
-
-
-    // Following lines are a safety mechanism to ensure
-    // incorrectly terminated urls are processed correctly.
-    if (nextEnd == -1) {
-      nextEnd = data.indexOf('}', nextStart);
-
-      if (nextEnd == -1)
-        nextEnd = data.length;
-      else
-        nextEnd--;
-
-      context.warnings.push('Broken URL declaration: \'' + data.substring(nextStart, nextEnd + 1) + '\'.');
-    } else {
-      if (data[nextEnd] != URL_SUFFIX)
-        nextEnd = data.indexOf(URL_SUFFIX, nextEnd);
-    }
-
-    tempData.push(data.substring(cursor, nextStart));
-
-    var url = data.substring(nextStart, nextEnd + 1);
-    callback(url, tempData);
-
-    cursor = nextEnd + 1;
-  }
-
-  return tempData.length > 0 ?
-    tempData.join('') + data.substring(cursor, data.length) :
-    data;
-}
-
-function byImport(data, context, callback) {
-  var nextImport = 0;
-  var nextImportUpperCase = 0;
-  var nextStart = 0;
-  var nextEnd = 0;
-  var cursor = 0;
-  var tempData = [];
-  var nextSingleQuote = 0;
-  var nextDoubleQuote = 0;
-  var untilNextQuote;
-  var withQuote;
-  var SINGLE_QUOTE = '\'';
-  var DOUBLE_QUOTE = '"';
-
-  for (; nextEnd < data.length;) {
-    nextImport = data.indexOf(IMPORT_URL_PREFIX, nextEnd);
-    nextImportUpperCase = data.indexOf(UPPERCASE_IMPORT_URL_PREFIX, nextEnd);
-    if (nextImport == -1 && nextImportUpperCase == -1)
-      break;
-
-    if (nextImport > -1 && nextImportUpperCase > -1 && nextImportUpperCase < nextImport)
-      nextImport = nextImportUpperCase;
-
-    nextSingleQuote = data.indexOf(SINGLE_QUOTE, nextImport);
-    nextDoubleQuote = data.indexOf(DOUBLE_QUOTE, nextImport);
-
-    if (nextSingleQuote > -1 && nextDoubleQuote > -1 && nextSingleQuote < nextDoubleQuote) {
-      nextStart = nextSingleQuote;
-      withQuote = SINGLE_QUOTE;
-    } else if (nextSingleQuote > -1 && nextDoubleQuote > -1 && nextSingleQuote > nextDoubleQuote) {
-      nextStart = nextDoubleQuote;
-      withQuote = DOUBLE_QUOTE;
-    } else if (nextSingleQuote > -1) {
-      nextStart = nextSingleQuote;
-      withQuote = SINGLE_QUOTE;
-    } else if (nextDoubleQuote > -1) {
-      nextStart = nextDoubleQuote;
-      withQuote = DOUBLE_QUOTE;
-    } else {
-      break;
-    }
-
-    tempData.push(data.substring(cursor, nextStart));
-    nextEnd = data.indexOf(withQuote, nextStart + 1);
-
-    untilNextQuote = data.substring(nextImport, nextEnd);
-    if (nextEnd == -1 || /^@import\s+(url\(|__ESCAPED)/i.test(untilNextQuote) || COMMENT_END_MARKER.test(untilNextQuote)) {
-      cursor = nextStart;
-      break;
-    }
-
-    var url = data.substring(nextStart, nextEnd + 1);
-    callback(url, tempData);
-
-    cursor = nextEnd + 1;
-  }
-
-  return tempData.length > 0 ?
-    tempData.join('') + data.substring(cursor, data.length) :
-    data;
-}
-
-function reduceAll(data, context, callback) {
-  data = byUrl(data, context, callback);
-  data = byImport(data, context, callback);
-  return data;
-}
-
-module.exports = reduceAll;
diff --git a/lib/urls/rewrite-url.js b/lib/urls/rewrite-url.js
new file mode 100644 (file)
index 0000000..e524216
--- /dev/null
@@ -0,0 +1,124 @@
+var path = require('path');
+var url = require('url');
+
+var DOUBLE_QUOTE = '"';
+var SINGLE_QUOTE = '\'';
+var URL_PREFIX = 'url(';
+var URL_SUFFIX = ')';
+
+var QUOTE_PREFIX_PATTERN = /^["']/;
+var QUOTE_SUFFIX_PATTERN = /["']$/;
+var ROUND_BRACKETS_PATTERN = /[\(\)]/;
+var URL_PREFIX_PATTERN = /^url\(/;
+var URL_SUFFIX_PATTERN = /\)$/;
+var WHITESPACE_PATTERN = /\s/;
+
+var isWindows = process.platform == 'win32';
+
+function rebase(uri, rebaseConfig) {
+  if (!rebaseConfig) {
+    return uri;
+  }
+
+  if (isAbsolute(uri) || isSVGMarker(uri) || isInternal(uri)) {
+    return uri;
+  }
+
+  if (isData(uri)) {
+    return '\'' + uri + '\'';
+  }
+
+  if (isRemote(uri) && !isRemote(rebaseConfig.toBase)) {
+    return uri;
+  }
+
+  if (isRemote(uri) && !isSameOrigin(uri, rebaseConfig.toBase)) {
+    return uri;
+  }
+
+  if (!isRemote(uri) && isRemote(rebaseConfig.toBase)) {
+    return url.resolve(rebaseConfig.toBase, uri);
+  }
+
+  return rebaseConfig.absolute ?
+    normalize(absolute(uri, rebaseConfig)) :
+    normalize(relative(uri, rebaseConfig));
+}
+
+function isAbsolute(uri) {
+  return uri[0] == '/';
+}
+
+function isSVGMarker(uri) {
+  return uri[0] == '#';
+}
+
+function isInternal(uri) {
+  return /^\w+:\w+/.test(uri);
+}
+
+function isRemote(uri) {
+  return /^[^:]+?:\/\//.test(uri) || uri.indexOf('//') === 0;
+}
+
+function isSameOrigin(uri1, uri2) {
+  return url.parse(uri1).protocol == url.parse(uri2).protocol &&
+    url.parse(uri1).host == url.parse(uri2).host;
+}
+
+function isData(uri) {
+  return uri.indexOf('data:') === 0;
+}
+
+function absolute(uri, rebaseConfig) {
+  return path
+    .resolve(path.join(rebaseConfig.fromBase || '', uri))
+    .replace(rebaseConfig.toBase, '');
+}
+
+function relative(uri, rebaseConfig) {
+  return path.relative(rebaseConfig.toBase, path.join(rebaseConfig.fromBase || '', uri));
+}
+
+function normalize(uri) {
+  return isWindows ? uri.replace(/\\/g, '/') : uri;
+}
+
+function quoteFor(unquotedUrl) {
+  if (unquotedUrl.indexOf(SINGLE_QUOTE) > -1) {
+    return DOUBLE_QUOTE;
+  } else if (unquotedUrl.indexOf(DOUBLE_QUOTE) > -1) {
+    return SINGLE_QUOTE;
+  } else if (hasWhitespace(unquotedUrl) || hasRoundBrackets(unquotedUrl)) {
+    return SINGLE_QUOTE;
+  } else {
+    return '';
+  }
+}
+
+function hasWhitespace(url) {
+  return WHITESPACE_PATTERN.test(url);
+}
+
+function hasRoundBrackets(url) {
+  return ROUND_BRACKETS_PATTERN.test(url);
+}
+
+function rewriteUrl(originalUrl, rebaseConfig) {
+  var strippedUrl = originalUrl
+    .replace(URL_PREFIX_PATTERN, '')
+    .replace(URL_SUFFIX_PATTERN, '')
+    .trim();
+
+  var unquotedUrl = strippedUrl
+    .replace(QUOTE_PREFIX_PATTERN, '')
+    .replace(QUOTE_SUFFIX_PATTERN, '');
+
+  var quote = strippedUrl[0] == SINGLE_QUOTE || strippedUrl[0] == DOUBLE_QUOTE ?
+    strippedUrl[0] :
+    quoteFor(unquotedUrl);
+
+  return URL_PREFIX + quote + rebase(unquotedUrl, rebaseConfig) + quote + URL_SUFFIX;
+}
+
+module.exports = rewriteUrl;
diff --git a/lib/urls/rewrite.js b/lib/urls/rewrite.js
deleted file mode 100644 (file)
index 0f5552b..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-var path = require('path');
-var url = require('url');
-
-var reduceUrls = require('./reduce');
-
-var isWindows = process.platform == 'win32';
-
-function isAbsolute(uri) {
-  return uri[0] == '/';
-}
-
-function isSVGMarker(uri) {
-  return uri[0] == '#';
-}
-
-function isEscaped(uri) {
-  return uri.indexOf('__ESCAPED_URL_CLEAN_CSS__') === 0;
-}
-
-function isInternal(uri) {
-  return /^\w+:\w+/.test(uri);
-}
-
-function isRemote(uri) {
-  return /^[^:]+?:\/\//.test(uri) || uri.indexOf('//') === 0;
-}
-
-function isSameOrigin(uri1, uri2) {
-  return url.parse(uri1).protocol == url.parse(uri2).protocol &&
-    url.parse(uri1).host == url.parse(uri2).host;
-}
-
-function isImport(uri) {
-  return uri.lastIndexOf('.css') === uri.length - 4;
-}
-
-function isData(uri) {
-  return uri.indexOf('data:') === 0;
-}
-
-function absolute(uri, options) {
-  return path
-    .resolve(path.join(options.fromBase || '', uri))
-    .replace(options.toBase, '');
-}
-
-function relative(uri, options) {
-  return path.relative(options.toBase, path.join(options.fromBase || '', uri));
-}
-
-function normalize(uri) {
-  return isWindows ? uri.replace(/\\/g, '/') : uri;
-}
-
-function rebase(uri, options) {
-  if (isAbsolute(uri) || isSVGMarker(uri) || isEscaped(uri) || isInternal(uri))
-    return uri;
-
-  if (options.rebase === false && !isImport(uri))
-    return uri;
-
-  if (!options.imports && isImport(uri))
-    return uri;
-
-  if (isData(uri))
-    return '\'' + uri + '\'';
-
-  if (isRemote(uri) && !isRemote(options.toBase))
-    return uri;
-
-  if (isRemote(uri) && !isSameOrigin(uri, options.toBase))
-    return uri;
-
-  if (!isRemote(uri) && isRemote(options.toBase))
-    return url.resolve(options.toBase, uri);
-
-  return options.absolute ?
-    normalize(absolute(uri, options)) :
-    normalize(relative(uri, options));
-}
-
-function quoteFor(url) {
-  if (url.indexOf('\'') > -1)
-    return '"';
-  else if (url.indexOf('"') > -1)
-    return '\'';
-  else if (/\s/.test(url) || /[\(\)]/.test(url))
-    return '\'';
-  else
-    return '';
-}
-
-function rewriteUrls(data, options, context) {
-  return reduceUrls(data, context, function (originUrl, tempData) {
-    var url = originUrl.replace(/^(url\()?\s*['"]?|['"]?\s*\)?$/g, '');
-    var match = originUrl.match(/^(url\()?\s*(['"]).*?(['"])\s*\)?$/);
-    var quote;
-    if (!!options.urlQuotes && match && match[2] === match[3]) {
-      quote = match[2];
-    } else {
-      quote = quoteFor(url);
-    }
-    tempData.push('url(' + quote + rebase(url, options) + quote + ')');
-  });
-}
-
-module.exports = rewriteUrls;
index 112a6dc..dea8269 100644 (file)
@@ -111,8 +111,8 @@ var DEFAULTS = {
   }
 };
 
-function Compatibility(source) {
-  this.source = source || {};
+function compatibility(source) {
+  return merge(DEFAULTS['*'], calculateSource(source));
 }
 
 function merge(source, target) {
@@ -155,8 +155,4 @@ function calculateSource(source) {
   return merge(template, source);
 }
 
-Compatibility.prototype.toOptions = function () {
-  return merge(DEFAULTS['*'], calculateSource(this.source));
-};
-
-module.exports = Compatibility;
+module.exports = compatibility;
diff --git a/lib/utils/object.js b/lib/utils/object.js
deleted file mode 100644 (file)
index 8e94886..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-module.exports = {
-  override: function (source1, source2) {
-    var target = {};
-    for (var key1 in source1)
-      target[key1] = source1[key1];
-    for (var key2 in source2)
-      target[key2] = source2[key2];
-
-    return target;
-  }
-};
diff --git a/lib/utils/override.js b/lib/utils/override.js
new file mode 100644 (file)
index 0000000..b2ee13a
--- /dev/null
@@ -0,0 +1,14 @@
+function override(source1, source2) {
+  var target = {};
+
+  for (var key1 in source1) {
+    target[key1] = source1[key1];
+  }
+  for (var key2 in source2) {
+    target[key2] = source2[key2];
+  }
+
+  return target;
+}
+
+module.exports = override;
diff --git a/lib/utils/quote-scanner.js b/lib/utils/quote-scanner.js
deleted file mode 100644 (file)
index 1810b9e..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-var COMMENT_START_MARK = '/*';
-
-function QuoteScanner(data) {
-  this.data = data;
-}
-
-var findQuoteEnd = function (data, matched, cursor, oldCursor) {
-  var commentEndMark = '*/';
-  var escapeMark = '\\';
-  var blockEndMark = '}';
-  var dataPrefix = data.substring(oldCursor, cursor);
-  var commentEndedAt = dataPrefix.lastIndexOf(commentEndMark, cursor);
-  var commentStartedAt = findLastCommentStartedAt(dataPrefix, cursor);
-  var commentStarted = false;
-
-  if (commentEndedAt >= cursor && commentStartedAt > -1)
-    commentStarted = true;
-  if (commentStartedAt < cursor && commentStartedAt > commentEndedAt)
-    commentStarted = true;
-
-  if (commentStarted) {
-    var commentEndsAt = data.indexOf(commentEndMark, cursor);
-    if (commentEndsAt > -1)
-      return commentEndsAt;
-
-    commentEndsAt = data.indexOf(blockEndMark, cursor);
-    return commentEndsAt > -1 ? commentEndsAt - 1 : data.length;
-  }
-
-  while (true) {
-    if (data[cursor] === undefined)
-      break;
-    if (data[cursor] == matched && (data[cursor - 1] != escapeMark || data[cursor - 2] == escapeMark))
-      break;
-
-    cursor++;
-  }
-
-  return cursor;
-};
-
-function findLastCommentStartedAt(data, cursor) {
-  var position = cursor;
-
-  while (position > -1) {
-    position = data.lastIndexOf(COMMENT_START_MARK, position);
-
-    if (position > -1 && data[position - 1] != '*') {
-      break;
-    } else {
-      position--;
-    }
-  }
-
-  return position;
-}
-
-function findNext(data, mark, startAt) {
-  var escapeMark = '\\';
-  var candidate = startAt;
-
-  while (true) {
-    candidate = data.indexOf(mark, candidate + 1);
-    if (candidate == -1)
-      return -1;
-    if (data[candidate - 1] != escapeMark)
-      return candidate;
-  }
-}
-
-QuoteScanner.prototype.each = function (callback) {
-  var data = this.data;
-  var tempData = [];
-  var nextStart = 0;
-  var nextEnd = 0;
-  var cursor = 0;
-  var matchedMark = null;
-  var singleMark = '\'';
-  var doubleMark = '"';
-  var dataLength = data.length;
-
-  for (; nextEnd < data.length;) {
-    var nextStartSingle = findNext(data, singleMark, nextEnd);
-    var nextStartDouble = findNext(data, doubleMark, nextEnd);
-
-    if (nextStartSingle == -1)
-      nextStartSingle = dataLength;
-    if (nextStartDouble == -1)
-      nextStartDouble = dataLength;
-
-    if (nextStartSingle < nextStartDouble) {
-      nextStart = nextStartSingle;
-      matchedMark = singleMark;
-    } else {
-      nextStart = nextStartDouble;
-      matchedMark = doubleMark;
-    }
-
-    if (nextStart == -1)
-      break;
-
-    nextEnd = findQuoteEnd(data, matchedMark, nextStart + 1, cursor);
-    if (nextEnd == -1)
-      break;
-
-    var text = data.substring(nextStart, nextEnd + 1);
-    tempData.push(data.substring(cursor, nextStart));
-    if (text.length > 0)
-      callback(text, tempData, nextStart);
-
-    cursor = nextEnd + 1;
-  }
-
-  return tempData.length > 0 ?
-    tempData.join('') + data.substring(cursor, data.length) :
-    data;
-};
-
-module.exports = QuoteScanner;
diff --git a/lib/utils/read-sources.js b/lib/utils/read-sources.js
new file mode 100644 (file)
index 0000000..03be623
--- /dev/null
@@ -0,0 +1,107 @@
+var path = require('path');
+
+var tokenize = require('../tokenizer/tokenize');
+var Token = require('../tokenizer/token');
+
+var rewriteUrl = require('../urls/rewrite-url');
+
+function readSources(input, context, callback) {
+  var tokens;
+
+  if (typeof input == 'string') {
+    tokens = tokenize(input, context);
+  } else if (typeof input == 'object') {
+    tokens = fromHash(input, context);
+  }
+
+  return callback(tokens);
+}
+
+function fromHash(input, context) {
+  var tokens = [];
+  var sourcePath;
+  var source;
+  var baseRelativeTo = context.options.relativeTo || context.options.root;
+  var relativeTo;
+  var fullPath;
+  var config;
+
+  for (sourcePath in input) {
+    source = input[sourcePath];
+    relativeTo = sourcePath[0] == '/' ?
+      context.options.root :
+      baseRelativeTo;
+    fullPath = path.resolve(
+      path.join(
+        relativeTo,
+        sourcePath
+      )
+    );
+
+    config = {
+      relative: true,
+      fromBase: path.dirname(fullPath),
+      toBase: baseRelativeTo
+    };
+
+    tokens = tokens.concat(
+      rebase(
+        tokenize(source.styles, context),
+        context.validator,
+        config
+      )
+    );
+  }
+
+  return tokens;
+}
+
+function rebase(tokens, validator, config) {
+  var token;
+  var i, l;
+
+  for (i = 0, l = tokens.length; i < l; i++) {
+    token = tokens[i];
+
+    switch (token[0]) {
+      case Token.AT_RULE:
+        //
+        break;
+      case Token.AT_RULE_BLOCK:
+        //
+        break;
+      case Token.BLOCK:
+        rebase(token[2], validator, config);
+        break;
+      case Token.PROPERTY:
+        //
+        break;
+      case Token.RULE:
+        rebaseProperties(token[2], validator, config);
+        break;
+    }
+  }
+
+  return tokens;
+}
+
+function rebaseProperties(properties, validator, config) {
+  var property;
+  var value;
+  var i, l;
+  var j, m;
+
+  for (i = 0, l = properties.length; i < l; i++) {
+    property = properties[i];
+
+    for (j = 2 /* 0 is Token.PROPERTY, 1 is name */, m = property.length; j < m; j++) {
+      value = property[j][1];
+
+      if (validator.isValidUrl(value)) {
+        property[j][1] = rewriteUrl(value, config);
+      }
+    }
+  }
+}
+
+module.exports = readSources;
diff --git a/lib/utils/source-reader.js b/lib/utils/source-reader.js
deleted file mode 100644 (file)
index d817e72..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-var path = require('path');
-var rewriteUrls = require('../urls/rewrite');
-
-var REMOTE_RESOURCE = /^(https?:)?\/\//;
-
-function SourceReader(context, data) {
-  this.outerContext = context;
-  this.data = data;
-  this.sources = {};
-}
-
-SourceReader.prototype.sourceAt = function (path) {
-  return this.sources[path];
-};
-
-SourceReader.prototype.trackSource = function (path, source) {
-  this.sources[path] = {};
-  this.sources[path][path] = source;
-};
-
-SourceReader.prototype.toString = function () {
-  if (typeof this.data == 'string')
-    return fromString(this);
-  if (Buffer.isBuffer(this.data))
-    return fromBuffer(this);
-  if (Array.isArray(this.data))
-    return fromArray(this);
-
-  return fromHash(this);
-};
-
-function fromString(self) {
-  var data = self.data;
-  self.trackSource(undefined, data);
-  return data;
-}
-
-function fromBuffer(self) {
-  var data = self.data.toString();
-  self.trackSource(undefined, data);
-  return data;
-}
-
-function fromArray(self) {
-  return self.data
-    .map(function (source) {
-      return self.outerContext.options.processImport === false ?
-        source + '@shallow' :
-        source;
-    })
-    .map(function (source) {
-      return !self.outerContext.options.relativeTo || /^https?:\/\//.test(source) ?
-        source :
-        path.relative(self.outerContext.options.relativeTo, source);
-    })
-    .map(function (source) { return '@import url(' + source + ');'; })
-    .join('');
-}
-
-function fromHash(self) {
-  var data = [];
-  var toBase = path.resolve(self.outerContext.options.target || self.outerContext.options.root);
-
-  for (var source in self.data) {
-    var styles = self.data[source].styles;
-    var inputSourceMap = self.data[source].sourceMap;
-    var isRemote = REMOTE_RESOURCE.test(source);
-    var absoluteSource = isRemote ? source : path.resolve(source);
-    var absoluteSourcePath = path.dirname(absoluteSource);
-
-    var rewriteOptions = {
-      absolute: self.outerContext.options.explicitRoot,
-      relative: !self.outerContext.options.explicitRoot,
-      imports: true,
-      rebase: self.outerContext.options.rebase,
-      fromBase: absoluteSourcePath,
-      toBase: isRemote ? absoluteSourcePath : toBase,
-      urlQuotes: self.outerContext.options.compatibility.properties.urlQuotes
-    };
-    styles = rewriteUrls(styles, rewriteOptions, self.outerContext);
-
-    self.trackSource(source, styles);
-
-    styles = self.outerContext.sourceTracker.store(source, styles);
-
-    // here we assume source map lies in the same directory as `source` does
-    if (self.outerContext.options.sourceMap && inputSourceMap)
-      self.outerContext.inputSourceMapTracker.trackLoaded(source, source, inputSourceMap);
-
-    data.push(styles);
-  }
-
-  return data.join('');
-}
-
-module.exports = SourceReader;
diff --git a/lib/utils/source-tracker.js b/lib/utils/source-tracker.js
deleted file mode 100644 (file)
index 4cc9b6d..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-function SourceTracker() {
-  this.sources = [];
-}
-
-SourceTracker.prototype.store = function (filename, data) {
-  this.sources.push(filename);
-
-  return '__ESCAPED_SOURCE_CLEAN_CSS' + (this.sources.length - 1) + '__' +
-    data +
-    '__ESCAPED_SOURCE_END_CLEAN_CSS__';
-};
-
-SourceTracker.prototype.nextStart = function (data) {
-  var next = /__ESCAPED_SOURCE_CLEAN_CSS(\d+)__/.exec(data);
-
-  return next ?
-    { index: next.index, filename: this.sources[~~next[1]] } :
-    null;
-};
-
-SourceTracker.prototype.nextEnd = function (data) {
-  return /__ESCAPED_SOURCE_END_CLEAN_CSS__/g.exec(data);
-};
-
-SourceTracker.prototype.removeAll = function (data) {
-  return data
-    .replace(/__ESCAPED_SOURCE_CLEAN_CSS\d+__/g, '')
-    .replace(/__ESCAPED_SOURCE_END_CLEAN_CSS__/g, '');
-};
-
-module.exports = SourceTracker;
index 20b3d3f..9ed68f1 100644 (file)
@@ -6,6 +6,9 @@ var CleanCSS = require('../index');
 
 var lineBreak = require('os').EOL;
 
+if (process.platform == 'win32')
+  return;
+
 var batchContexts = function () {
   var context = {};
   var dir = path.join(__dirname, 'fixtures');
index 0f1ceeb..4a5b9d3 100644 (file)
@@ -253,7 +253,7 @@ section article{margin:0 0 16px}
 .bord_double_gris_blanc{line-height:25px;font-size:12px;display:inline-block;border:solid #d2d6db;border-width:1px 0}
 .bord_double_gris_blanc span{display:inline-block;border:solid #fff;border-width:1px 0}
 .bloc_abo{border-top:3px solid #ffd500}
-img[width="642"],img[width="312"]{margin-bottom:6px}
+img[width="312"],img[width="642"]{margin-bottom:6px}
 img[width="202"]{margin-bottom:4px}
 .btn,.btn_abo,.btn_fonce,.btn_petit{display:inline-block;padding:4px 10px;margin-bottom:0;color:#000b15;text-align:center;font-weight:700;vertical-align:middle;background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-ms-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(top,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);cursor:pointer}
 .bt_fonce a,.btn_fonce{color:#fff;background-color:#000b15;background-image:-moz-linear-gradient(top,#5d666d,#000b15);background-image:-ms-linear-gradient(top,#5d666d,#000b15);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5d666d),to(#000b15));background-image:-webkit-linear-gradient(top,#5d666d,#000b15);background-image:-o-linear-gradient(top,#5d666d,#000b15);background-image:linear-gradient(top,#5d666d,#000b15);background-repeat:repeat-x;border-color:#000b15;border-color:rgba(0,0,0,.1)}
@@ -2374,13 +2374,12 @@ body.init-bar-is-closed #bar-liberation{height:15px}
 #page-mailfriend .content input[type=checkbox]{float:right;margin-top:5px}
 #page-mailfriend .content label{float:left;display:block;width:80%;font-weight:700}
 #page-paywall{width:520px;font-size:12px}
-#page-paywall .content .arguments .arg .price,#page-paywall .content .arguments .arg h4,#page-paywall .content .video h5{font-family:Georgia,"Times New Roman",Times,serif}
 #page-paywall .content{position:relative;padding:20px 0}
 #page-paywall .content a.close{display:block;float:right}
 #page-paywall .content a.close span{background:url(http://s0.libe.com/libe/img/common/_sprites_icons/icons.png?9914d0d70a49) -20px -98px no-repeat;display:block;margin:auto;width:15px;height:15px}
 #page-paywall .content a.close strong{text-transform:uppercase;font-size:8px}
 #page-paywall .content .video{margin-bottom:20px;width:437px}
-#page-paywall .content .video h5{margin-bottom:15px;padding:3px 0 5px;border-top:1px dotted;border-bottom:1px dotted;float:right;font-size:16px;font-style:italic;font-weight:400}
+#page-paywall .content .video h5{margin-bottom:15px;padding:3px 0 5px;border-top:1px dotted;border-bottom:1px dotted;float:right;font-family:Georgia,"Times New Roman",Times,serif;font-size:16px;font-style:italic;font-weight:400}
 #page-paywall .content .arguments,#page-paywall .content .video h4{margin-bottom:20px;clear:both}
 #page-paywall .content .video h4 span{float:right;padding:0 0 0 10px;text-transform:uppercase;line-height:13px;font-size:16px;font-weight:400}
 #page-paywall .content .video .player{width:354px;height:200px;float:right}
@@ -2390,8 +2389,8 @@ body.access-ess #page-paywall .content .arguments .arg{float:none;margin:auto}
 #page-paywall .content .arguments .arg .visual1{height:71px}
 #page-paywall .content .arguments .arg .visual2{height:76px}
 #page-paywall .content .arguments .arg h5{margin-bottom:10px;text-transform:uppercase;line-height:13px;font-size:14px;font-weight:400}
-#page-paywall .content .arguments .arg h4{margin-bottom:10px;font-size:13px;font-style:italic;font-weight:400}
-#page-paywall .content .arguments .arg .price{float:right;width:120px;font-size:13px;margin-bottom:15px}
+#page-paywall .content .arguments .arg h4{margin-bottom:10px;font-family:Georgia,"Times New Roman",Times,serif;font-size:13px;font-style:italic;font-weight:400}
+#page-paywall .content .arguments .arg .price{float:right;width:120px;font-family:Georgia,"Times New Roman",Times,serif;font-size:13px;margin-bottom:15px}
 #page-paywall .content .arguments .arg .price strong{display:block;float:left;margin-right:10px;line-height:25px;font-family:Verdana,sans-serif;font-size:30px;letter-spacing:-3px}
 #page-paywall .content .arguments .arg .price strong .currency,#page-paywall .content .arguments .arg .price strong .decimals{font-size:20px}
 #page-paywall .content .arguments .arg .price strong .currency{padding-left:3px}
index 3c037e2..db9db19 100644 (file)
@@ -19,6 +19,7 @@ sub{bottom:-.25em}
 img{vertical-align:middle}
 svg:not(:root){overflow:hidden}
 hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}
+*,:after,:before,input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box}
 pre,textarea{overflow:auto}
 code,kbd,pre,samp{font-size:1em}
 button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}
@@ -28,7 +29,7 @@ button,select{text-transform:none}
 button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
 button[disabled],html input[disabled]{cursor:default}
 button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}
-input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}
+input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}
 input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}
 input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}
 table{border-spacing:0;border-collapse:collapse}
@@ -38,7 +39,7 @@ td,th{padding:0}
 a,a:visited{text-decoration:underline}
 a[href]:after{content:" (" attr(href) ")"}
 abbr[title]:after{content:" (" attr(title) ")"}
-a[href^="javascript:"]:after,a[href^="#"]:after{content:""}
+a[href^="#"]:after,a[href^=javascript:]:after{content:""}
 blockquote,pre{border:1px solid #999}
 thead{display:table-header-group}
 img{max-width:100%!important}
@@ -315,7 +316,7 @@ select{background:#fff!important}
 .glyphicon-menu-right:before{content:"\e258"}
 .glyphicon-menu-down:before{content:"\e259"}
 .glyphicon-menu-up:before{content:"\e260"}
-*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}
+*,:after,:before{box-sizing:border-box}
 html{font-size:10px;-webkit-tap-highlight-color:transparent}
 body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333}
 button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}
@@ -670,7 +671,7 @@ input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-heig
 input[type=file]{display:block}
 input[type=range]{display:block;width:100%}
 select[multiple],select[size]{height:auto}
-input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}
+input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}
 output{padding-top:7px}
 .form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}
 .form-group-sm .form-control,.input-sm{font-size:12px;border-radius:3px;padding:5px 10px}
@@ -681,9 +682,9 @@ output{padding-top:7px}
 .has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .form-control-feedback,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}
 .form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}
 textarea.form-control{height:auto}
-@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}
-.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}
-.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}
+@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=datetime-local],input[type=month],input[type=time]{line-height:34px}
+.input-group-sm input[type=date],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],.input-group-sm input[type=time],input[type=date].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,input[type=time].input-sm{line-height:30px}
+.input-group-lg input[type=date],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],.input-group-lg input[type=time],input[type=date].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,input[type=time].input-lg{line-height:46px}
 }
 .form-group{margin-bottom:15px}
 .checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}
@@ -1312,13 +1313,12 @@ button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;bor
 .popover>.arrow:after{content:"";border-width:10px}
 .popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}
 .popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}
-.popover.left>.arrow:after,.popover.right>.arrow:after{bottom:-10px;content:" "}
 .popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}
-.popover.right>.arrow:after{left:1px;border-right-color:#fff;border-left-width:0}
+.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}
 .popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}
 .popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}
 .popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}
-.popover.left>.arrow:after{right:1px;border-right-width:0;border-left-color:#fff}
+.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}
 .carousel-inner{width:100%;overflow:hidden}
 .carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}
 .carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}
index 4a2913e..a4f6d42 100644 (file)
@@ -23,8 +23,9 @@
  *  Twitter: http://twitter.com/davegandy
  *  Work: Lead Product Designer @ Kyruus - http://kyruus.com
  */
-.icon-large{font-size:1.3333333333333333em;margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px;vertical-align:middle}
-.nav [class*=" icon-"],.nav [class^=icon-]{vertical-align:inherit;margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px}
+.icon-large,.nav [class*=" icon-"],.nav [class^=icon-]{margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px}
+.icon-large{font-size:1.3333333333333333em;vertical-align:middle}
+.nav [class*=" icon-"],.nav [class^=icon-]{vertical-align:inherit}
 .nav [class*=" icon-"].icon-large,.nav [class^=icon-].icon-large{vertical-align:-25%}
 .nav-pills [class*=" icon-"].icon-large,.nav-pills [class^=icon-].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-tabs [class^=icon-].icon-large{line-height:.75em;margin-top:-7px;padding-top:5px;margin-bottom:-5px;padding-bottom:4px}
 .btn [class*=" icon-"].pull-left,.btn [class*=" icon-"].pull-right,.btn [class^=icon-].pull-left,.btn [class^=icon-].pull-right{vertical-align:inherit}
index db80a90..fa78f3b 100644 (file)
@@ -1 +1 @@
-p{background-image:url(/*)}
+p{background-image:url('/*')}
\ No newline at end of file
index c422021..a19d5e4 100644 (file)
@@ -396,7 +396,7 @@ vows.describe('integration tests')
       ],
       'inside url': [
         'p{background-image:url(\'/*\')}/* */',
-        'p{background-image:url(/*)}'
+        'p{background-image:url(\'/*\')}'
       ],
       'inside url twice': [
         'p{background-image:url(\'/* */\" /*\')}/* */',
@@ -404,7 +404,7 @@ vows.describe('integration tests')
       ],
       'inside url with more quotation': [
         'p{background-image:url(\'/*\');content:""/* */}',
-        'p{background-image:url(/*);content:""}'
+        'p{background-image:url(\'/*\');content:""}'
       ],
       'with quote marks': [
         '/*"*//* */',
@@ -524,7 +524,7 @@ vows.describe('integration tests')
       ],
       'open ended (broken)': [
         'a{width:expression(this.parentNode.innerText == }',
-        'a{width:expression(this.parentNode.innerText == }'
+        'a{width:expression(this.parentNode.innerText ==}'
       ],
       'function call & advanced': [
         'a{zoom:expression(function (el){el.style.zoom="1"}(this))}',
@@ -1250,10 +1250,6 @@ vows.describe('integration tests')
         'a{background:url(/images/blank.png)}',
         'a{background:url(/images/blank.png)}'
       ],
-      'keep non-encoded data URI unchanged': [
-        '.icon-logo{background-image:url(\'data:image/svg+xml;charset=US-ASCII\')}',
-        '.icon-logo{background-image:url(\'data:image/svg+xml;charset=US-ASCII\')}'
-      ],
       'strip quotes from base64 encoded PNG data URI': [
         '.icon-logo{background-image:url(\'data:image/png;base64,iVBORw0\')}',
         '.icon-logo{background-image:url(data:image/png;base64,iVBORw0)}'
@@ -1264,11 +1260,11 @@ vows.describe('integration tests')
       ],
       'cut off url content on selector level': [
         'a{background:url(image/}',
-        'a{background:url(image/}'
+        ''
       ],
       'cut off url content on block level': [
         '@font-face{src:url(data:application/x-font-woff;base64,d09GRk9UVE8AAENAAA0AAAAA}',
-        '@font-face{src:url(data:application/x-font-woff;base64,d09GRk9UVE8AAENAAA0AAAAA}'
+        ''
       ],
       'cut off url content on top level': [
         '@font-face{src:url(data:application/x-font-woff;base64,d09GRk9UVE8AAENAAA0AAAAA',
@@ -1368,11 +1364,11 @@ vows.describe('integration tests')
       ],
       'keep quoting if whitespace': [
         'a{background:url("/images/blank image.png")}',
-        'a{background:url(\'/images/blank image.png\')}'
+        'a{background:url("/images/blank image.png")}'
       ],
       'keep quoting if whitespace inside @font-face': [
         '@font-face{src:url("/Helvetica Neue.eot")}',
-        '@font-face{src:url(\'/Helvetica Neue.eot\')}'
+        '@font-face{src:url("/Helvetica Neue.eot")}'
       ],
       'keep SVG data URI unchanged': [
         'div{background:url(data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2018%2018%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Crect%20width%3D%2214%22%20height%3D%2214%22%2Fsvg%3E)}',
@@ -1838,12 +1834,12 @@ vows.describe('integration tests')
         'a[href="/version-0.01.html"]{display:block}',
         'a[href="/version-0.01.html"]{display:block}'
       ],
-      'should strip new lines inside attributes': [
-        '.test[title="my very long ' + lineBreak + 'title"]{display:block}',
+      'should strip escaped new lines inside attributes': [
+        '.test[title="my very long \\' + lineBreak + 'title"]{display:block}',
         '.test[title="my very long title"]{display:block}'
       ],
-      'should strip new lines inside attributes which can be unquoted': [
-        '.test[title="my_very_long_' + lineBreak + 'title"]{display:block}',
+      'should strip escaped new lines inside attributes which can be unquoted': [
+        '.test[title="my_very_long_\\' + lineBreak + 'title"]{display:block}',
         '.test[title=my_very_long_title]{display:block}'
       ],
       'should strip whitespace between square brackets': [
@@ -2675,10 +2671,6 @@ vows.describe('integration tests')
         'a{--my-toolbar: { margin:10px; margin-top:10px };}',
         'a{--my-toolbar:{margin:10px};}'
       ],
-      'Polymer mixins - not fitting into a single chunk of 128 bytes': [
-        ':host{--live-head-theme: { line-height: 40px !important; vertical-align: middle; background: transparent; height: 40px; z-index: 999; }; }',
-        ':host{--live-head-theme:{line-height:40px!important;vertical-align:middle;background:0 0;height:40px;z-index:999};}'
-      ],
       'Polymer mixins - inlined variables': [
         '.spinner{-webkit-animation:container-rotate var(--paper-spinner-container-rotation-duration) linear infinite}',
         '.spinner{-webkit-animation:container-rotate var(--paper-spinner-container-rotation-duration) linear infinite}'
index 695ac22..2f97d1f 100644 (file)
@@ -474,22 +474,6 @@ vows.describe('module tests').addBatch({
         'should give right output': function (minified) {
           assert.equal(minified.styles, '@import url(one.css);@import url(extra/three.css);@import url(./extra/four.css);.two{color:#fff}');
         }
-      },
-      'off - many files': {
-        'topic': function () {
-          return new CleanCSS({ processImport: false }).minify(['./test/fixtures/partials/remote.css', './test/fixtures/partials-absolute/base.css']);
-        },
-        'should give right output': function (minified) {
-          assert.equal(minified.styles, '@import url(http://jakubpawlowicz.com/styles.css);@import url(./extra/sub.css);.base{margin:0}');
-        }
-      },
-      'off - many files with content': {
-        'topic': function () {
-          return new CleanCSS({ processImport: false }).minify(['./test/fixtures/partials/two.css', './test/fixtures/partials-absolute/base.css']);
-        },
-        'should give right output': function (minified) {
-          assert.equal(minified.styles, '@import url(one.css);@import url(extra/three.css);@import url(./extra/four.css);.two{color:#fff}.base{margin:0}');
-        }
       }
     }
   },
similarity index 57%
rename from test/selectors/simple-test.js
rename to test/optimizer/basic-test.js
index edcd8d8..5393ee9 100644 (file)
 var vows = require('vows');
 
-var selectorContext = require('../test-helper').selectorContext;
-var propertyContext = require('../test-helper').propertyContext;
+var optimizerContext = require('../test-helper').optimizerContext;
 
 vows.describe('simple optimizations')
   .addBatch(
-    selectorContext('default', {
+    optimizerContext('selectors', {
       'optimized': [
         'a{}',
-        null
+        ''
       ],
       'whitespace': [
         ' div  > span{color:red}',
-        [['div>span']]
+        'div>span{color:red}'
       ],
       'line breaks': [
         ' div  >\n\r\n span{color:red}',
-        [['div>span']]
+        'div>span{color:red}'
       ],
       'more line breaks': [
         '\r\ndiv\n{color:red}',
-        [['div']]
+        'div{color:red}'
       ],
       '+html': [
-        '*+html .foo{display:inline}',
-        null
+        '*+html .foo{color:red}',
+        ''
       ],
       'adjacent nav': [
         'div + nav{color:red}',
-        [['div+nav']]
+        'div+nav{color:red}'
       ],
       'heading & trailing': [
         ' a {color:red}',
-        [['a']]
+        'a{color:red}'
       ],
       'descendant selector': [
         'div > a{color:red}',
-        [['div>a']]
+        'div>a{color:red}'
       ],
       'next selector': [
         'div + a{color:red}',
-        [['div+a']]
+        'div+a{color:red}'
       ],
       'sibling selector': [
         'div  ~ a{color:red}',
-        [['div~a']]
+        'div~a{color:red}'
       ],
       'pseudo classes': [
         'div  :first-child{color:red}',
-        [['div :first-child']]
+        'div :first-child{color:red}'
       ],
       'tabs': [
         'div\t\t{color:red}',
-        [['div']]
+        'div{color:red}'
       ],
       'universal selector - id, class, and property': [
         '* > *#id > *.class > *[property]{color:red}',
-        [['*>#id>.class>[property]']]
+        '*>#id>.class>[property]{color:red}'
       ],
       'universal selector - pseudo': [
         '*:first-child{color:red}',
-        [[':first-child']]
+        ':first-child{color:red}'
       ],
       'universal selector - standalone': [
         'label ~ * + span{color:red}',
-        [['label~*+span']]
+        'label~*+span{color:red}'
       ],
       'order': [
         'b,div,a{color:red}',
-        [['a'], ['b'], ['div']]
+        'a,b,div{color:red}'
       ],
       'duplicates': [
         'a,div,.class,.class,a ,div > a{color:red}',
-        [['.class'], ['a'], ['div'], ['div>a']]
+        '.class,a,div,div>a{color:red}',
       ],
       'mixed': [
         ' label   ~  \n*  +  span , div>*.class, section\n\n{color:red}',
-        [['div>.class'], ['label~*+span'], ['section']]
+        'div>.class,label~*+span,section{color:red}'
       ],
       'escaped joining character #1': [
-        '.class\\~ div{color: red}',
-        [['.class\\~ div']]
+        '.class\\~ div{color:red}',
+        '.class\\~ div{color:red}'
       ],
       'escaped joining character #2': [
-        '.class\\+\\+ div{color: red}',
-        [['.class\\+\\+ div']]
+        '.class\\+\\+ div{color:red}',
+        '.class\\+\\+ div{color:red}'
       ],
       'escaped joining character #3': [
-        '.class\\>  \\~div{color: red}',
-        [['.class\\> \\~div']]
+        '.class\\>  \\~div{color:red}',
+        '.class\\> \\~div{color:red}'
       ],
       'escaped characters': [
-        '.a\\+\\+b{color: red}',
-        [['.a\\+\\+b']]
+        '.a\\+\\+b{color:red}',
+        '.a\\+\\+b{color:red}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    selectorContext('ie8', {
+    optimizerContext('selectors - ie8', {
       '+html': [
-        '*+html .foo{display:inline}',
-        null
+        '*+html .foo{color:red}',
+        ''
       ],
       '+first-child html': [
-        '*:first-child+html .foo{display:inline}',
-        null
+        '*:first-child+html .foo{color:red}',
+        ''
       ],
       '+html - complex': [
-        '*+html .foo,.bar{display:inline}',
-        [['.bar']]
+        '*+html .foo,.bar{color:red}',
+        '.bar{color:red}'
       ]
-    }, { compatibility: 'ie8' })
+    }, { advanced: false, compatibility: 'ie8' })
   )
   .addBatch(
-    selectorContext('ie7', {
+    optimizerContext('selectors - ie7', {
       '+html': [
-        '*+html .foo{display:inline}',
-        [['*+html .foo']]
+        '*+html .foo{color:red}',
+        '*+html .foo{color:red}'
       ],
       '+html - complex': [
-        '*+html .foo,.bar{display:inline}',
-        [['*+html .foo'], ['.bar']]
+        '*+html .foo,.bar{color:red}',
+        '*+html .foo,.bar{color:red}'
       ]
-    }, { compatibility: 'ie7' })
+    }, { advanced: false, compatibility: 'ie7' })
   )
   .addBatch(
-    selectorContext('+adjacentSpace', {
+    optimizerContext('selectors - adjacent space', {
       'with whitespace': [
         'div + nav{color:red}',
-        [['div+ nav']]
+        'div+ nav{color:red}'
       ],
       'without whitespace': [
         'div+nav{color:red}',
-        [['div+ nav']]
+        'div+ nav{color:red}'
       ]
-    }, { compatibility: { selectors: { adjacentSpace: true } } })
+    }, { advanced: false, compatibility: { selectors: { adjacentSpace: true } } })
   )
   .addBatch(
-    propertyContext('@background', {
+    optimizerContext('background', {
       'none to 0 0': [
         'a{background:none}',
-        [['background', '0 0']]
+        'a{background:0 0}'
       ],
       'transparent to 0 0': [
         'a{background:transparent}',
-        [['background', '0 0']]
+        'a{background:0 0}'
       ],
       'any other': [
         'a{background:red}',
-        [['background', 'red']]
+        'a{background:red}'
       ],
       'none to other': [
         'a{background:transparent no-repeat}',
-        [['background', 'transparent', 'no-repeat']]
+        'a{background:transparent no-repeat}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('@border-*-radius', {
+    optimizerContext('border-*-radius', {
       'spaces around /': [
         'a{border-radius:2em  /  1em}',
-        [['border-radius', '2em', '/', '1em']]
+        'a{border-radius:2em/1em}'
       ],
       'symmetric expanded to shorthand': [
         'a{border-radius:1em 2em 3em 4em / 1em 2em 3em 4em}',
-        [['border-radius', '1em', '2em', '3em', '4em']]
+        'a{border-radius:1em 2em 3em 4em}'
       ],
       'asymmetric kept as is': [
         'a{border-top-left-radius:1em 2em}',
-        [['border-top-left-radius', '1em', '2em']]
+        'a{border-top-left-radius:1em 2em}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('@box-shadow', {
+    optimizerContext('box-shadow', {
       'four zeros': [
         'a{box-shadow:0 0 0 0}',
-        [['box-shadow', '0', '0']]
+        'a{box-shadow:0 0}'
       ],
       'four zeros in vendor prefixed': [
         'a{-webkit-box-shadow:0 0 0 0}',
-        [['-webkit-box-shadow', '0', '0']]
+        'a{-webkit-box-shadow:0 0}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('colors', {
+    optimizerContext('colors', {
       'rgb to hex': [
         'a{color:rgb(255,254,253)}',
-        [['color', '#fffefd']]
+        'a{color:#fffefd}'
       ],
       'rgba not to hex': [
         'a{color:rgba(255,254,253,.5)}',
-        [['color', 'rgba(255,254,253,.5)']]
+        'a{color:rgba(255,254,253,.5)}'
       ],
       'hsl to hex': [
         'a{color:hsl(240,100%,50%)}',
-        [['color', '#00f']]
+        'a{color:#00f}'
       ],
       'hsla not to hex': [
         'a{color:hsla(240,100%,50%,.5)}',
-        [['color', 'hsla(240,100%,50%,.5)']]
+        'a{color:hsla(240,100%,50%,.5)}'
       ],
       'long hex to short hex': [
         'a{color:#ff00ff}',
-        [['color', '#f0f']]
+        'a{color:#f0f}'
       ],
       'hex to name': [
         'a{color:#f00}',
-        [['color', 'red']]
+        'a{color:red}'
       ],
       'name to hex': [
         'a{color:white}',
-        [['color', '#fff']]
+        'a{color:#fff}'
       ],
       'transparent black rgba to transparent': [
         'a{color:rgba(0,0,0,0)}',
-        [['color', 'transparent']]
+        'a{color:transparent}'
       ],
       'transparent non-black rgba': [
         'a{color:rgba(255,0,0,0)}',
-        [['color', 'rgba(255,0,0,0)']]
+        'a{color:rgba(255,0,0,0)}'
       ],
       'transparent black hsla to transparent': [
         'a{color:hsla(0,0%,0%,0)}',
-        [['color', 'transparent']]
+        'a{color:transparent}'
       ],
       'transparent non-black hsla': [
         'a{color:rgba(240,0,0,0)}',
-        [['color', 'rgba(240,0,0,0)']]
+        'a{color:rgba(240,0,0,0)}'
       ],
       'partial hex to name': [
         'a{color:#f00000}',
-        [['color', '#f00000']]
+        'a{color:#f00000}'
       ],
       'partial hex further down to name': [
         'a{background:url(test.png) #f00000}',
-        [['background', 'url(test.png)', '#f00000']]
+        'a{background:url(test.png) #f00000}'
       ],
       'partial name to hex': [
         'a{color:greyish}',
-        [['color', 'greyish']]
+        'a{color:greyish}'
       ],
       'partial name further down to hex': [
         'a{background:url(test.png) blueish}',
-        [['background', 'url(test.png)', 'blueish']]
+        'a{background:url(test.png) blueish}'
       ],
       'partial name as a suffix': [
         'a{font-family:alrightsanslp-black}',
-        [['font-family', 'alrightsanslp-black']]
+        'a{font-family:alrightsanslp-black}'
       ],
       'invalid rgba declaration - color': [
         'a{color:rgba(255 0 0)}',
-        [['color', 'rgba(255 0 0)']]
+        'a{color:rgba(255 0 0)}'
       ],
       'invalid rgba declaration - background': [
         'a{background:rgba(255 0 0)}',
-        [['background', 'rgba(255 0 0)']]
+        'a{background:rgba(255 0 0)}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('colors - ie8 compatibility', {
+    optimizerContext('colors - ie8 compatibility', {
       'transparent black rgba': [
         'a{color:rgba(0,0,0,0)}',
-        [['color', 'rgba(0,0,0,0)']]
+        'a{color:rgba(0,0,0,0)}'
       ],
       'transparent non-black rgba': [
         'a{color:rgba(255,0,0,0)}',
-        [['color', 'rgba(255,0,0,0)']]
+        'a{color:rgba(255,0,0,0)}'
       ],
       'transparent black hsla': [
         'a{color:hsla(0,0%,0%,0)}',
-        [['color', 'hsla(0,0%,0%,0)']]
+        'a{color:hsla(0,0%,0%,0)}'
       ],
       'transparent non-black hsla': [
         'a{color:rgba(240,0,0,0)}',
-        [['color', 'rgba(240,0,0,0)']]
+        'a{color:rgba(240,0,0,0)}'
       ]
-    }, { compatibility: 'ie8' })
+    }, { advanced: false, compatibility: 'ie8' })
   )
   .addBatch(
-    propertyContext('colors - no optimizations', {
+    optimizerContext('colors - no optimizations', {
       'long hex into short': [
         'a{color:#ff00ff}',
-        [['color', '#ff00ff']]
+        'a{color:#ff00ff}'
       ],
       'short hex into name': [
         'a{color:#f00}',
-        [['color', '#f00']]
+        'a{color:#f00}'
       ],
       'name into hex': [
         'a{color:white}',
-        [['color', 'white']]
+        'a{color:white}'
       ]
-    }, { compatibility: { properties: { colors: false } } })
+    }, { advanced: false, compatibility: { properties: { colors: false } } })
   )
   .addBatch(
-    propertyContext('@filter', {
+    optimizerContext('filter', {
       'spaces after comma': [
         'a{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#cccccc\',endColorstr=\'#000000\', enabled=true)}',
-        [['filter', 'progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#cccccc\', endColorstr=\'#000000\', enabled=true)']]
+        'a{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=\'#cccccc\', endColorstr=\'#000000\', enabled=true)}'
       ],
       'single Alpha filter': [
         'a{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80)}',
-        [['filter', 'alpha(Opacity=80)']]
+        'a{filter:alpha(Opacity=80)}'
       ],
       'single Chroma filter': [
         'a{filter:progid:DXImageTransform.Microsoft.Chroma(color=#919191)}',
-        [['filter', 'chroma(color=#919191)']]
+        'a{filter:chroma(color=#919191)}'
       ],
       'multiple filters': [
         'a{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80) progid:DXImageTransform.Microsoft.Chroma(color=#919191)}',
-        [['filter', 'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)', 'progid:DXImageTransform.Microsoft.Chroma(color=#919191)']]
+        'a{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80) progid:DXImageTransform.Microsoft.Chroma(color=#919191)}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('@font', {
+    optimizerContext('font', {
       'in shorthand': [
         'a{font:normal 13px/20px sans-serif}',
-        [['font', '400', '13px', '/', '20px', 'sans-serif']]
+        'a{font:400 13px/20px sans-serif}'
       ],
       'in shorthand with fractions': [
         'a{font:bold .9em sans-serif}',
-        [['font', '700', '.9em', 'sans-serif']]
+        'a{font:700 .9em sans-serif}'
       ],
       'with font wariant and style': [
         'a{font:normal normal normal 13px/20px sans-serif}',
-        [['font', 'normal', 'normal', 'normal', '13px', '/', '20px', 'sans-serif']]
+        'a{font:normal normal normal 13px/20px sans-serif}'
       ],
       'with mixed order of variant and style': [
         'a{font:normal 300 normal 13px/20px sans-serif}',
-        [['font', 'normal', '300', 'normal', '13px', '/', '20px', 'sans-serif']]
+        'a{font:normal 300 normal 13px/20px sans-serif}'
       ],
       'with mixed normal and weight': [
-        'a{font: normal small-caps 400 medium Georgia, sans-serif;}',
-        [['font', 'normal', 'small-caps', '400', 'medium', 'Georgia', ',', 'sans-serif']]
+        'a{font:normal small-caps 400 medium Georgia,sans-serif}',
+        'a{font:normal small-caps 400 medium Georgia,sans-serif}'
       ],
       'with line height': [
-        'a{font: 11px/normal sans-serif}',
-        [['font', '11px', '/', 'normal', 'sans-serif']]
+        'a{font:11px/normal sans-serif}',
+        'a{font:11px/normal sans-serif}'
       ],
       'with mixed bold weight and variant #1': [
         'a{font:normal bold 17px sans-serif}',
-        [['font', 'normal', '700', '17px', 'sans-serif']]
+        'a{font:normal 700 17px sans-serif}'
       ],
       'with mixed bold weight and variant #2': [
         'a{font:bold normal 17px sans-serif}',
-        [['font', '700', 'normal', '17px', 'sans-serif']]
+        'a{font:700 normal 17px sans-serif}'
       ],
       'with mixed bold weight and variant #3': [
         'a{font:bold normal normal 17px sans-serif}',
-        [['font', 'bold', 'normal', 'normal', '17px', 'sans-serif']] // pending #254
+        'a{font:bold normal normal 17px sans-serif}' // pending #254
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('@font-weight', {
+    optimizerContext('font-weight', {
       'normal to 400': [
         'a{font-weight:normal}',
-        [['font-weight', '400']]
+        'a{font-weight:400}'
       ],
       'bold to 700': [
         'a{font-weight:bold}',
-        [['font-weight', '700']]
+        'a{font-weight:700}'
       ],
       'any other': [
         'a{font-weight:bolder}',
-        [['font-weight', 'bolder']]
+        'a{font-weight:bolder}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('ie hacks', {
+    optimizerContext('ie hacks', {
       'underscore': [
         'a{_width:101px}',
-        null
+        ''
       ],
       'star': [
         'a{*width:101px}',
-        null
+        ''
       ],
       'backslash': [
         'a{width:101px\\9}',
-        [['width', '101px\\9']]
+        'a{width:101px\\9}'
       ],
       'bang': [
         'a{color:red !ie}',
-        null
+        ''
       ],
       'before content': [
         'a{*width:101px;color:red!important}',
-        [['color', 'red!important']]
+        'a{color:red!important}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('ie hacks in IE8 mode', {
+    optimizerContext('ie hacks in IE8 mode', {
       'underscore': [
         'a{_width:101px}',
-        [['_width', '101px']]
+        'a{_width:101px}'
       ],
       'star': [
         'a{*width:101px}',
-        [['*width', '101px']]
+        'a{*width:101px}'
       ],
       'backslash': [
         'a{width:101px\\9}',
-        [['width', '101px\\9']]
+        'a{width:101px\\9}'
       ],
       'bang': [
         'a{color:red !ie}',
-        null
+        ''
       ]
-    }, { compatibility: 'ie8' })
+    }, { advanced: false, compatibility: 'ie8' })
   )
   .addBatch(
-    propertyContext('ie hacks in IE7 mode', {
+    optimizerContext('ie hacks in IE7 mode', {
       'underscore': [
         'a{_width:101px}',
-        [['_width', '101px']]
+        'a{_width:101px}'
       ],
       'star': [
         'a{*width:101px}',
-        [['*width', '101px']]
+        'a{*width:101px}'
       ],
       'backslash': [
         'a{width:101px\\9}',
-        [['width', '101px\\9']]
+        'a{width:101px\\9}'
       ],
       'bang': [
         'a{color:red !ie}',
-        [['color', 'red !ie']]
+        'a{color:red !ie}'
       ]
-    }, { compatibility: 'ie7' })
+    }, { advanced: false, compatibility: 'ie7' })
   )
   .addBatch(
-    propertyContext('important', {
+    optimizerContext('important', {
       'minified': [
         'a{color:red!important}',
-        [['color', 'red!important']]
+        'a{color:red!important}'
       ],
       'space before !': [
         'a{color:red !important}',
-        [['color', 'red!important']]
+        'a{color:red!important}',
       ],
       'space after !': [
         'a{color:red! important}',
-        [['color', 'red!important']]
+        'a{color:red!important}'
       ]
-    }, { compatibility: 'ie8' })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('@outline', {
+    optimizerContext('outline', {
       'none to 0': [
         'a{outline:none}',
-        [['outline', '0']]
+        'a{outline:0}'
       ],
       'any other': [
         'a{outline:10px}',
-        [['outline', '10px']]
+        'a{outline:10px}'
       ],
       'none and any other': [
         'a{outline:none solid 1px}',
-        [['outline', 'none', 'solid', '1px']]
+        'a{outline:none solid 1px}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('rounding', {
+    optimizerContext('rounding', {
       'pixels': [
         'a{transform:translateY(123.31135px)}',
-        [['transform', 'translateY(123.311px)']]
+        'a{transform:translateY(123.311px)}'
       ],
       'percents': [
         'a{left:20.1231%}',
-        [['left', '20.1231%']]
+        'a{left:20.1231%}'
       ],
       'ems': [
         'a{left:1.1231em}',
-        [['left', '1.1231em']]
+        'a{left:1.1231em}'
       ]
-    }, { roundingPrecision: 3 })
+    }, { advanced: false, roundingPrecision: 3 })
   )
   .addBatch(
-    propertyContext('rounding disabled', {
+    optimizerContext('rounding disabled', {
       'pixels': [
         'a{transform:translateY(123.31135px)}',
-        [['transform', 'translateY(123.31135px)']]
+        'a{transform:translateY(123.31135px)}'
       ],
       'percents': [
         'a{left:20.1231%}',
-        [['left', '20.1231%']]
+        'a{left:20.1231%}'
       ],
       'ems': [
         'a{left:1.1231em}',
-        [['left', '1.1231em']]
+        'a{left:1.1231em}'
       ]
-    }, { roundingPrecision: -1 })
+    }, { advanced: false, roundingPrecision: -1 })
   )
   .addBatch(
-    propertyContext('rounding disabled when option value not castable to int', {
+    optimizerContext('rounding disabled when option value not castable to int', {
       'pixels': [
         'a{transform:translateY(123.31135px)}',
-        [['transform', 'translateY(123.31135px)']]
+        'a{transform:translateY(123.31135px)}'
       ],
       'percents': [
         'a{left:20.1231%}',
-        [['left', '20.1231%']]
+        'a{left:20.1231%}'
       ],
       'ems': [
         'a{left:1.1231em}',
-        [['left', '1.1231em']]
+        'a{left:1.1231em}'
       ]
-    }, { roundingPrecision: '\'-1\'' })
+    }, { advanced: false, roundingPrecision: '\'-1\'' })
   )
   .addBatch(
-    propertyContext('units', {
+    optimizerContext('units', {
       'pixels': [
         'a{width:0px}',
-        [['width', '0']]
+        'a{width:0}'
       ],
       'degrees': [
         'div{background:linear-gradient(0deg,red,#fff)}',
-        [['background', 'linear-gradient(0deg,red,#fff)']]
+        'div{background:linear-gradient(0deg,red,#fff)}'
       ],
       'degrees when not mixed': [
         'div{transform:rotate(0deg) skew(0deg)}',
-        [['transform', 'rotate(0)', 'skew(0)']]
+        'div{transform:rotate(0) skew(0)}'
       ],
       'non-zero degrees when not mixed': [
         'div{transform:rotate(10deg) skew(.5deg)}',
-        [['transform', 'rotate(10deg)', 'skew(.5deg)']]
+        'div{transform:rotate(10deg) skew(.5deg)}'
       ],
       'ch': [
         'div{width:0ch;height:0ch}',
-        [['width', '0'], ['height', '0']]
+        'div{width:0;height:0}'
       ],
       'rem': [
         'div{width:0rem;height:0rem}',
-        [['width', '0'], ['height', '0']]
+        'div{width:0;height:0}'
       ],
       'vh': [
         'div{width:0vh;height:0vh}',
-        [['width', '0'], ['height', '0']]
+        'div{width:0;height:0}'
       ],
       'vm': [
         'div{width:0vm;height:0vm}',
-        [['width', '0'], ['height', '0']]
+        'div{width:0;height:0}'
       ],
       'vmax': [
         'div{width:0vmax;height:0vmax}',
-        [['width', '0'], ['height', '0']]
+        'div{width:0;height:0}'
       ],
       'vmin': [
         'div{width:0vmin;height:0vmin}',
-        [['width', '0'], ['height', '0']]
+        'div{width:0;height:0}'
       ],
       'vw': [
         'div{width:0vw;height:0vw}',
-        [['width', '0'], ['height', '0']]
+        'div{width:0;height:0}'
       ],
       'mixed units': [
         'a{margin:0em 0rem 0px 0pt}',
-        [['margin', '0']]
+        'a{margin:0}'
       ],
       'mixed values #1': [
         'a{padding:10px 0em 30% 0rem}',
-        [['padding', '10px', '0', '30%', '0']]
+        'a{padding:10px 0 30% 0}'
       ],
       'mixed values #2': [
         'a{padding:10ch 0vm 30vmin 0vw}',
-        [['padding', '10ch', '0', '30vmin', '0']]
+        'a{padding:10ch 0 30vmin 0}'
       ],
       'inside calc': [
         'a{font-size:calc(100% + 0px)}',
-        [['font-size', 'calc(100% + 0px)']]
+        'a{font-size:calc(100% + 0px)}'
       ],
       'flex': [
-        'a{flex: 1 0 0%}',
-        [['flex', '1', '0', '0%']]
+        'a{flex:1 0 0%}',
+        'a{flex:1 0 0%}'
       ],
       'flex–basis': [
         'a{flex-basis:0%}',
-        [['flex-basis', '0%']]
+        'a{flex-basis:0%}'
       ],
       'prefixed flex': [
-        'a{-ms-flex:1 0 0px;-webkit-flex:1 0 0px;}',
-        [['-ms-flex', '1', '0', '0px'], ['-webkit-flex', '1', '0', '0px']]
+        'a{-ms-flex:1 0 0px;-webkit-flex:1 0 0px}',
+        'a{-ms-flex:1 0 0px;-webkit-flex:1 0 0px}'
       ],
       'prefixed flex–basis': [
         'a{-webkit-flex-basis:0px}',
-        [['-webkit-flex-basis', '0px']]
+        'a{-webkit-flex-basis:0px}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('units in compatibility mode', {
+    optimizerContext('units in compatibility mode', {
       'pixels': [
         'a{width:0px}',
-        [['width', '0']]
+        'a{width:0}'
       ],
       'mixed units': [
         'a{margin:0em 0rem 0px 0pt}',
-        [['margin', '0', '0rem', '0', '0']]
+        'a{margin:0 0rem 0 0}'
       ],
       'mixed values #1': [
         'a{padding:10px 0em 30% 0rem}',
-        [['padding', '10px', '0', '30%', '0rem']]
+        'a{padding:10px 0 30% 0rem}'
       ],
       'mixed values #2': [
         'a{padding:10ch 0vm 30vmin 0vw}',
-        [['padding', '10ch', '0vm', '30vmin', '0vw']]
+        'a{padding:10ch 0vm 30vmin 0vw}'
       ]
-    }, { compatibility: 'ie8' })
+    }, { advanced: false, compatibility: 'ie8' })
   )
   .addBatch(
-    propertyContext('zeros', {
+    optimizerContext('zeros', {
       '-0 to 0': [
         'a{margin:-0}',
-        [['margin', '0']]
+        'a{margin:0}'
       ],
       '-0px to 0': [
         'a{margin:-0px}',
-        [['margin', '0']]
+        'a{margin:0}'
       ],
       '-0% to 0': [
         'a{width:-0%}',
-        [['width', '0']]
+        'a{width:0}'
       ],
       'missing': [
         'a{opacity:1.}',
-        [['opacity', '1']]
+        'a{opacity:1}'
       ],
       'multiple': [
         'a{margin:-0 -0 -0 -0}',
-        [['margin', '0']]
+        'a{margin:0}'
       ],
       'keeps negative non-zero': [
         'a{margin:-0.5em}',
-        [['margin', '-.5em']]
+        'a{margin:-.5em}'
       ],
       'inside names #1': [
         'div{animation-name:test-0-bounce}',
-        [['animation-name', 'test-0-bounce']]
+        'div{animation-name:test-0-bounce}'
       ],
       'inside names #2': [
         'div{animation-name:test-0bounce}',
-        [['animation-name', 'test-0bounce']]
+        'div{animation-name:test-0bounce}'
       ],
       'inside names #3': [
         'div{animation-name:test-0px}',
-        [['animation-name', 'test-0px']]
+        'div{animation-name:test-0px}'
       ],
       'strips leading from value': [
         'a{padding:010px 0015px}',
-        [['padding', '10px', '15px']]
+        'a{padding:10px 15px}'
       ],
       'strips leading from fractions': [
         'a{margin:-0.5em}',
-        [['margin', '-.5em']]
+        'a{margin:-.5em}'
       ],
       'strips trailing from opacity': [
         'a{opacity:1.0}',
-        [['opacity', '1']]
+        'a{opacity:1}'
       ],
       '.0 to 0': [
         'a{margin:.0 .0 .0 .0}',
-        [['margin', '0']]
+        'a{margin:0}'
       ],
       'fraction zeros': [
         'a{margin:10.0em 15.50em 10.01em 0.0em}',
-        [['margin', '10em', '15.5em', '10.01em', '0']]
+        'a{margin:10em 15.5em 10.01em 0}'
       ],
       'fraction zeros after rounding': [
         'a{margin:10.0010px}',
-        [['margin', '10px']]
+        'a{margin:10px}'
       ],
       'four zeros into one': [
         'a{margin:0 0 0 0}',
-        [['margin', '0']]
+        'a{margin:0}'
       ],
       'rect zeros': [
         'a{clip:rect(0px 0px 0px 0px)}',
-        [['clip', 'rect(0 0 0 0)']]
+        'a{clip:rect(0 0 0 0)}'
       ],
       'rect zeros with non-zero value': [
         'a{clip:rect(0.5% 0px  0px 0px)}',
-        [['clip', 'rect(.5% 0 0 0)']]
+        'a{clip:rect(.5% 0 0 0)}'
       ],
       'rect zeros with commas': [
         'a{clip:rect(0px, 0px, 0px, 0px)}',
-        [['clip', 'rect(0,0,0,0)']]
+        'a{clip:rect(0,0,0,0)}'
       ],
       'height': [
         'a{height:0%}',
-        [['height', '0%']]
+        'a{height:0%}'
       ],
       'min-height': [
         'a{min-height:0%}',
-        [['min-height', '0']]
+        'a{min-height:0}'
       ],
       'max-height': [
         'a{max-height:0%}',
-        [['max-height', '0%']]
+        'a{max-height:0%}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('zeros with disabled zeroUnits', {
+    optimizerContext('zeros with disabled zeroUnits', {
       '10.0em': [
         'a{margin:10.0em}',
-        [['margin', '10em']]
+        'a{margin:10em}'
       ],
       '0px': [
         'a{margin:0px}',
-        [['margin', '0px']]
+        'a{margin:0px}'
       ],
       '0px 0px': [
         'a{margin:0px 0px}',
-        [['margin', '0px', '0px']]
+        'a{margin:0px 0px}'
       ],
       '0deg': [
         'div{transform:rotate(0deg) skew(0deg)}',
-        [['transform', 'rotate(0deg)', 'skew(0deg)']]
+        'div{transform:rotate(0deg) skew(0deg)}'
       ],
       '0%': [
         'a{height:0%}',
-        [['height', '0%']]
+        'a{height:0%}'
       ],
       '10%': [
         'a{width:10%}',
-        [['width', '10%']]
+        'a{width:10%}'
       ]
-    }, { compatibility: { properties: { zeroUnits: false } } })
+    }, { advanced: false, compatibility: { properties: { zeroUnits: false } } })
   )
   .addBatch(
-    propertyContext('comments', {
+    optimizerContext('comments', {
       'comment': [
-        'a{__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__color:red__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__}',
-        ['__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS0__', '__ESCAPED_COMMENT_SPECIAL_CLEAN_CSS1__', ['color', 'red']]
+        'a{/*! comment 1 */color:red/*! comment 2 */}',
+        'a{/*! comment 1 */color:red/*! comment 2 */}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('whitespace', {
+    optimizerContext('whitespace', {
       'stripped spaces': [
         'div{text-shadow:rgba(255,1,1,.5) 1px}',
-        [['text-shadow', 'rgba(255,1,1,.5)', '1px']]
+        'div{text-shadow:rgba(255,1,1,.5) 1px}'
       ],
       'calc': [
         'a{width:-moz-calc(100% - 1em);width:calc(100% - 1em)}',
-        [['width', '-moz-calc(100% - 1em)'], ['width', 'calc(100% - 1em)']]
+        'a{width:-moz-calc(100% - 1em);width:calc(100% - 1em)}'
       ],
       'empty body': [
         'a{}',
-        null
+        ''
       ],
       'in a body': [
         'a{   \n }',
-        null
+        ''
       ],
       'after calc()': [
         'div{margin:calc(100% - 21px) 1px}',
-        [['margin', 'calc(100% - 21px)', '1px']]
+        'div{margin:calc(100% - 21px) 1px}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('time units', {
+    optimizerContext('time units', {
       'positive miliseconds to seconds': [
         'div{transition-duration:500ms}',
-        [['transition-duration', '.5s']]
+        'div{transition-duration:.5s}'
       ],
       'negative miliseconds to seconds': [
         'div{transition-duration:-500ms}',
-        [['transition-duration', '-.5s']]
+        'div{transition-duration:-.5s}'
       ],
       'miliseconds to seconds when results in a too long value': [
         'div{transition-duration:1515ms}',
-        [['transition-duration', '1515ms']]
+        'div{transition-duration:1515ms}'
       ],
       'zero miliseconds to seconds': [
         'div{transition-duration:0ms}',
-        [['transition-duration', '0s']]
+        'div{transition-duration:0s}'
       ],
       'positive seconds to miliseconds': [
         'div{transition-duration:0.005s}',
-        [['transition-duration', '5ms']]
+        'div{transition-duration:5ms}'
       ],
       'negative seconds to miliseconds': [
         'div{transition-duration:-0.005s}',
-        [['transition-duration', '-5ms']]
+        'div{transition-duration:-5ms}'
       ],
       'seconds to miliseconds when results in a too long value': [
         'div{transition-duration:1.2s}',
-        [['transition-duration', '1.2s']]
+        'div{transition-duration:1.2s}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('length units', {
+    optimizerContext('length units', {
       'px to in': [
         'div{left:480px}',
-        [['left', '480px']]
+        'div{left:480px}'
       ],
       'px to pc': [
         'div{left:32px}',
-        [['left', '32px']]
+        'div{left:32px}'
       ],
       'px to pt': [
         'div{left:120px}',
-        [['left', '120px']]
+        'div{left:120px}'
       ]
-    })
+    }, { advanced: false })
   )
   .addBatch(
-    propertyContext('length units in compatibility mode', {
+    optimizerContext('length units in compatibility mode', {
       'px to in': [
         'div{left:480px}',
-        [['left', '480px']]
+        'div{left:480px}'
       ],
       'px to pc': [
         'div{left:32px}',
-        [['left', '32px']]
+        'div{left:32px}'
       ],
       'px to pt': [
         'div{left:120px}',
-        [['left', '120px']]
+        'div{left:120px}'
       ]
-    }, { compatibility: 'ie8' })
+    }, { advanced: false, compatibility: 'ie8' })
   )
   .addBatch(
-    propertyContext('length units when turned on', {
+    optimizerContext('length units when turned on', {
       'positive px to in': [
         'div{left:480px}',
-        [['left', '5in']]
+        'div{left:5in}'
       ],
       'negative px to in': [
         'div{left:-96px}',
-        [['left', '-1in']]
+        'div{left:-1in}'
       ],
       'positive px to pc': [
         'div{left:32px}',
-        [['left', '2pc']]
+        'div{left:2pc}'
       ],
       'negative px to pc': [
         'div{left:-160px}',
-        [['left', '-10pc']]
+        'div{left:-10pc}'
       ],
       'positive px to pt': [
         'div{left:120px}',
-        [['left', '90pt']]
+        'div{left:90pt}'
       ],
       'negative px to pt': [
         'div{left:-120px}',
-        [['left', '-90pt']]
+        'div{left:-90pt}'
       ],
       'in calc': [
         'div{left:calc(100% - 480px)}',
-        [['left', 'calc(100% - 5in)']]
+        'div{left:calc(100% - 5in)}'
       ],
       'in transform': [
         'div{transform:translateY(32px)}',
-        [['transform', 'translateY(2pc)']]
+        'div{transform:translateY(2pc)}'
       ]
-    }, { compatibility: { properties: { shorterLengthUnits: true } } })
+    }, { advanced: false, compatibility: { properties: { shorterLengthUnits: true } } })
   )
   .addBatch(
-    propertyContext('length units when turned on selectively', {
+    optimizerContext('length units when turned on selectively', {
       'px to in': [
         'div{left:480px}',
-        [['left', '30pc']]
+        'div{left:30pc}'
       ],
       'px to pc': [
         'div{left:32px}',
-        [['left', '2pc']]
+        'div{left:2pc}'
       ],
       'px to pt': [
         'div{left:120px}',
-        [['left', '120px']]
+        'div{left:120px}'
       ]
-    }, { compatibility: { properties: { shorterLengthUnits: true }, units: { in: false, pt: false } } })
+    }, { advanced: false, compatibility: { properties: { shorterLengthUnits: true }, units: { in: false, pt: false } } })
   )
   .export(module);
diff --git a/test/optimizer/extract-properties-test.js b/test/optimizer/extract-properties-test.js
new file mode 100644 (file)
index 0000000..9bef4d6
--- /dev/null
@@ -0,0 +1,360 @@
+var vows = require('vows');
+var assert = require('assert');
+var tokenize = require('../../lib/tokenizer/tokenize');
+var extractProperties = require('../../lib/optimizer/extract-properties');
+
+vows.describe(extractProperties)
+  .addBatch({
+    'no properties': {
+      'topic': function () {
+        return extractProperties(tokenize('a{}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, []);
+      }
+    },
+    'no valid properties': {
+      'topic': function () {
+        return extractProperties(tokenize('a{:red}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, []);
+      }
+    },
+    'one property': {
+      'topic': function () {
+        return extractProperties(tokenize('a{color:red}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, [
+          [
+            'color',
+            'red',
+            'color',
+            ['property', ['property-name', 'color', [[1, 2, undefined]]], ['property-value', 'red', [[1, 8, undefined]]]],
+            'color:red',
+            [['a', [[1, 0, undefined]]]],
+            true
+          ]
+        ]);
+      }
+    },
+    'one important property': {
+      'topic': function () {
+        return extractProperties(tokenize('a{color:red!important}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, [
+          [
+            'color',
+            'red!important',
+            'color',
+            ['property', ['property-name', 'color', [[1, 2, undefined]]], ['property-value', 'red!important', [[1, 8, undefined]]]],
+            'color:red!important',
+            [['a', [[1, 0, undefined]]]],
+            true
+          ]
+        ]);
+      }
+    },
+    'one property - simple selector': {
+      'topic': function () {
+        return extractProperties(tokenize('#one span{color:red}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, [
+          [
+            'color',
+            'red',
+            'color',
+            ['property', ['property-name', 'color', [[1, 10, undefined]]], ['property-value', 'red', [[1, 16, undefined]]]],
+            'color:red',
+            [['#one span', [[1, 0, undefined]]]],
+            true
+          ]
+        ]);
+      }
+    },
+    'one property - variable': {
+      'topic': function () {
+        return extractProperties(tokenize('#one span{--color:red}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, []);
+      }
+    },
+    'one property - block variable': {
+      'topic': function () {
+        return extractProperties(tokenize('#one span{--color:{color:red;display:block};}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, []);
+      }
+    },
+    'one property - complex selector': {
+      'topic': function () {
+        return extractProperties(tokenize('.one{color:red}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, [
+          [
+            'color',
+            'red',
+            'color',
+            ['property', ['property-name', 'color', [[1, 5, undefined]]], ['property-value', 'red', [[1, 11, undefined]]]],
+            'color:red',
+            [['.one', [[1, 0, undefined]]]],
+            false
+          ]
+        ]);
+      }
+    },
+    'two properties': {
+      'topic': function () {
+        return extractProperties(tokenize('a{color:red;display:block}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, [
+          [
+            'color',
+            'red',
+            'color',
+            ['property', ['property-name', 'color', [[1, 2, undefined]]], ['property-value', 'red', [[1, 8, undefined]]]],
+            'color:red',
+            [['a', [[1, 0, undefined]]]],
+            true
+          ],
+          [
+            'display',
+            'block',
+            'display',
+            ['property', ['property-name', 'display', [[1, 12, undefined]]], ['property-value', 'block', [[1, 20, undefined]]]],
+            'display:block',
+            [['a', [[1, 0, undefined]]]],
+            true
+          ]
+        ]);
+      }
+    },
+    'from @media': {
+      'topic': function () {
+        return extractProperties(tokenize('@media{a{color:red;display:block}p{color:red}}', {})[0]);
+      },
+      'has no properties': function (tokens) {
+        assert.deepEqual(tokens, [
+          [
+            'color',
+            'red',
+            'color',
+            ['property', ['property-name', 'color', [[1, 9, undefined]]], ['property-value', 'red', [[1, 15, undefined]]]],
+            'color:red',
+            [['a', [[1, 7, undefined]]]],
+            true
+          ],
+          [
+            'display',
+            'block',
+            'display',
+            ['property', ['property-name', 'display', [[1, 19, undefined]]], ['property-value', 'block', [[1, 27, undefined]]]],
+            'display:block',
+            [['a', [[1, 7, undefined]]]],
+            true
+          ],
+          [
+            'color',
+            'red',
+            'color',
+            ['property', ['property-name', 'color', [[1, 35, undefined]]], ['property-value', 'red', [[1, 41, undefined]]]],
+            'color:red',
+            [['p', [[1, 33, undefined]]]],
+            true
+          ]
+        ]);
+      }
+    }
+  })
+  .addBatch({
+    'name root special cases': {
+      'vendor prefix': {
+        'topic': function () {
+          return extractProperties(tokenize('a{-moz-transform:none}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              '-moz-transform',
+              'none',
+              'transform',
+              ['property', ['property-name', '-moz-transform', [[1, 2, undefined]]], ['property-value', 'none', [[1, 17, undefined]]]],
+              '-moz-transform:none',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'list-style': {
+        'topic': function () {
+          return extractProperties(tokenize('a{list-style:none}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              'list-style',
+              'none',
+              'list-style',
+              ['property', ['property-name', 'list-style', [[1, 2, undefined]]], ['property-value', 'none', [[1, 13, undefined]]]],
+              'list-style:none',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'border-radius': {
+        'topic': function () {
+          return extractProperties(tokenize('a{border-top-left-radius:none}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              'border-top-left-radius',
+              'none',
+              'border-radius',
+              ['property', ['property-name', 'border-top-left-radius', [[1, 2, undefined]]], ['property-value', 'none', [[1, 25, undefined]]]],
+              'border-top-left-radius:none',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'vendor prefixed border-radius': {
+        'topic': function () {
+          return extractProperties(tokenize('a{-webkit-border-top-left-radius:none}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              '-webkit-border-top-left-radius',
+              'none',
+              'border-radius',
+              ['property', ['property-name', '-webkit-border-top-left-radius', [[1, 2, undefined]]], ['property-value', 'none', [[1, 33, undefined]]]],
+              '-webkit-border-top-left-radius:none',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'border-image-width': {
+        'topic': function () {
+          return extractProperties(tokenize('a{border-image-width:2px}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              'border-image-width',
+              '2px',
+              'border-image',
+              ['property', ['property-name', 'border-image-width', [[1, 2, undefined]]], ['property-value', '2px', [[1, 21, undefined]]]],
+              'border-image-width:2px',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'border-color': {
+        'topic': function () {
+          return extractProperties(tokenize('a{border-color:red}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              'border-color',
+              'red',
+              'border',
+              ['property', ['property-name', 'border-color', [[1, 2, undefined]]], ['property-value', 'red', [[1, 15, undefined]]]],
+              'border-color:red',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'border-top-style': {
+        'topic': function () {
+          return extractProperties(tokenize('a{border-top-style:none}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              'border-top-style',
+              'none',
+              'border-top',
+              ['property', ['property-name', 'border-top-style', [[1, 2, undefined]]], ['property-value', 'none', [[1, 19, undefined]]]],
+              'border-top-style:none',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'border-top': {
+        'topic': function () {
+          return extractProperties(tokenize('a{border-top:none}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              'border-top',
+              'none',
+              'border',
+              ['property', ['property-name', 'border-top', [[1, 2, undefined]]], ['property-value', 'none', [[1, 13, undefined]]]],
+              'border-top:none',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'border-collapse': {
+        'topic': function () {
+          return extractProperties(tokenize('a{border-collapse:collapse}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              'border-collapse',
+              'collapse',
+              'border-collapse',
+              ['property', ['property-name', 'border-collapse', [[1, 2, undefined]]], ['property-value', 'collapse', [[1, 18, undefined]]]],
+              'border-collapse:collapse',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      },
+      'text-shadow': {
+        'topic': function () {
+          return extractProperties(tokenize('a{text-shadow:none}', {})[0]);
+        },
+        'has no properties': function (tokens) {
+          assert.deepEqual(tokens, [
+            [
+              'text-shadow',
+              'none',
+              'text-shadow',
+              ['property', ['property-name', 'text-shadow', [[1, 2, undefined]]], ['property-value', 'none', [[1, 14, undefined]]]],
+              'text-shadow:none',
+              [['a', [[1, 0, undefined]]]],
+              true
+            ]
+          ]);
+        }
+      }
+    }
+  })
+  .export(module);
similarity index 98%
rename from test/selectors/reorderable-test.js
rename to test/optimizer/reorderable-test.js
index 07c8887..5a291ef 100644 (file)
@@ -2,9 +2,9 @@ var vows = require('vows');
 var assert = require('assert');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
-var extractProperties = require('../../lib/selectors/extractor');
-var canReorder = require('../../lib/selectors/reorderable').canReorder;
-var canReorderSingle = require('../../lib/selectors/reorderable').canReorderSingle;
+var extractProperties = require('../../lib/optimizer/extract-properties');
+var canReorder = require('../../lib/optimizer/reorderable').canReorder;
+var canReorderSingle = require('../../lib/optimizer/reorderable').canReorderSingle;
 
 function propertiesIn(source) {
   return extractProperties(tokenize(source, { options: {} })[0]);
index 4aed598..df1ad5c 100644 (file)
@@ -4,12 +4,12 @@ var assert = require('assert');
 var wrapForOptimizing = require('../../lib/properties/wrap-for-optimizing').all;
 var populateComponents = require('../../lib/properties/populate-components');
 var Validator = require('../../lib/properties/validator');
-var Compatibility = require('../../lib/utils/compatibility');
+var compatibility = require('../../lib/utils/compatibility');
 
 var breakUp = require('../../lib/properties/break-up');
 
 function _breakUp(properties) {
-  var validator = new Validator(new Compatibility().toOptions());
+  var validator = new Validator(compatibility());
   var wrapped = wrapForOptimizing(properties);
   populateComponents(wrapped, validator, []);
 
@@ -21,363 +21,508 @@ vows.describe(breakUp)
     'background': {
       'inherit': {
         'topic': function () {
-          return _breakUp([[['background'], ['inherit']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'inherit']
+            ]
+          ]);
         },
         'has 8 components': function (components) {
           assert.lengthOf(components, 8);
         },
         'has background-image': function (components) {
           assert.deepEqual(components[0].name, 'background-image');
-          assert.deepEqual(components[0].value, [['inherit']]);
+          assert.deepEqual(components[0].value, [['property-value', 'inherit']]);
         },
         'has background-position': function (components) {
           assert.deepEqual(components[1].name, 'background-position');
-          assert.deepEqual(components[1].value, [['inherit']]);
+          assert.deepEqual(components[1].value, [['property-value', 'inherit']]);
         },
         'has background-size': function (components) {
           assert.deepEqual(components[2].name, 'background-size');
-          assert.deepEqual(components[2].value, [['inherit']]);
+          assert.deepEqual(components[2].value, [['property-value', 'inherit']]);
         },
         'has background-repeat': function (components) {
           assert.deepEqual(components[3].name, 'background-repeat');
-          assert.deepEqual(components[3].value, [['inherit']]);
+          assert.deepEqual(components[3].value, [['property-value', 'inherit']]);
         },
         'has background-attachment': function (components) {
           assert.deepEqual(components[4].name, 'background-attachment');
-          assert.deepEqual(components[4].value, [['scroll']]);
+          assert.deepEqual(components[4].value, [['property-value', 'scroll']]);
         },
         'has background-origin': function (components) {
           assert.deepEqual(components[5].name, 'background-origin');
-          assert.deepEqual(components[5].value, [['inherit']]);
+          assert.deepEqual(components[5].value, [['property-value', 'inherit']]);
         },
         'has background-clip': function (components) {
           assert.deepEqual(components[6].name, 'background-clip');
-          assert.deepEqual(components[6].value, [['inherit']]);
+          assert.deepEqual(components[6].value, [['property-value', 'inherit']]);
         },
         'has background-color': function (components) {
           assert.deepEqual(components[7].name, 'background-color');
-          assert.deepEqual(components[7].value, [['inherit']]);
+          assert.deepEqual(components[7].value, [['property-value', 'inherit']]);
         }
       },
       'all': {
         'topic': function () {
-          return _breakUp([[['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['repeat'], ['no-repeat'], ['2px'], ['3px'], ['/'], ['50%'], ['60%'], ['fixed'], ['padding-box'], ['border-box'], ['red']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'repeat'],
+              ['property-value', 'no-repeat'],
+              ['property-value', '2px'],
+              ['property-value', '3px'],
+              ['property-value', '/'],
+              ['property-value', '50%'],
+              ['property-value', '60%'],
+              ['property-value', 'fixed'],
+              ['property-value', 'padding-box'],
+              ['property-value', 'border-box'],
+              ['property-value', 'red']
+            ]
+          ]);
         },
         'has 8 components': function (components) {
           assert.lengthOf(components, 8);
         },
         'has background-image': function (components) {
           assert.deepEqual(components[0].name, 'background-image');
-          assert.deepEqual(components[0].value, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+          assert.deepEqual(components[0].value, [['property-value', 'url(image.png)']]);
         },
         'has background-position': function (components) {
           assert.deepEqual(components[1].name, 'background-position');
-          assert.deepEqual(components[1].value, [['2px'], ['3px']]);
+          assert.deepEqual(components[1].value, [['property-value', '2px'], ['property-value', '3px']]);
         },
         'has background-size': function (components) {
           assert.deepEqual(components[2].name, 'background-size');
-          assert.deepEqual(components[2].value, [['50%'], ['60%']]);
+          assert.deepEqual(components[2].value, [['property-value', '50%'], ['property-value', '60%']]);
         },
         'has background-repeat': function (components) {
           assert.deepEqual(components[3].name, 'background-repeat');
-          assert.deepEqual(components[3].value, [['repeat'], ['no-repeat']]);
+          assert.deepEqual(components[3].value, [['property-value', 'repeat'], ['property-value', 'no-repeat']]);
         },
         'has background-attachment': function (components) {
           assert.deepEqual(components[4].name, 'background-attachment');
-          assert.deepEqual(components[4].value, [['fixed']]);
+          assert.deepEqual(components[4].value, [['property-value', 'fixed']]);
         },
         'has background-origin': function (components) {
           assert.deepEqual(components[5].name, 'background-origin');
-          assert.deepEqual(components[5].value, [['padding-box']]);
+          assert.deepEqual(components[5].value, [['property-value', 'padding-box']]);
         },
         'has background-clip': function (components) {
           assert.deepEqual(components[6].name, 'background-clip');
-          assert.deepEqual(components[6].value, [['border-box']]);
+          assert.deepEqual(components[6].value, [['property-value', 'border-box']]);
         },
         'has background-color': function (components) {
           assert.deepEqual(components[7].name, 'background-color');
-          assert.deepEqual(components[7].value, [['red']]);
+          assert.deepEqual(components[7].value, [['property-value', 'red']]);
         }
       },
       'no size': {
         'topic': function () {
-          return _breakUp([[['background'], ['bottom']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'bottom']
+            ]
+          ]);
         },
         'has 8 components': function (components) {
           assert.lengthOf(components, 8);
         },
         'has background-position': function (components) {
           assert.deepEqual(components[1].name, 'background-position');
-          assert.deepEqual(components[1].value, [['bottom']]);
+          assert.deepEqual(components[1].value, [['property-value', 'bottom']]);
         },
         'has background-size': function (components) {
           assert.deepEqual(components[2].name, 'background-size');
-          assert.deepEqual(components[2].value, [['auto']]);
+          assert.deepEqual(components[2].value, [['property-value', 'auto']]);
         }
       },
       'shorthand size & position': {
         'topic': function () {
-          return _breakUp([[['background'], ['2px'], ['/'], ['50px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', '2px'],
+              ['property-value', '/'],
+              ['property-value', '50px']
+            ]
+          ]);
         },
         'has 8 components': function (components) {
           assert.lengthOf(components, 8);
         },
         'has background-position': function (components) {
           assert.deepEqual(components[1].name, 'background-position');
-          assert.deepEqual(components[1].value, [['2px']]);
+          assert.deepEqual(components[1].value, [['property-value', '2px']]);
         },
         'has background-size': function (components) {
           assert.deepEqual(components[2].name, 'background-size');
-          assert.deepEqual(components[2].value, [['50px']]);
+          assert.deepEqual(components[2].value, [['property-value', '50px']]);
         }
       },
       'size & position joined together': {
         'topic': function () {
-          return _breakUp([[['background'], ['2px/50px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', '2px'],
+              ['property-value', '/'],
+              ['property-value', '50px']
+            ]
+          ]);
         },
         'has 8 components': function (components) {
           assert.lengthOf(components, 8);
         },
         'has background-position': function (components) {
           assert.deepEqual(components[1].name, 'background-position');
-          assert.deepEqual(components[1].value, [['2px']]);
+          assert.deepEqual(components[1].value, [['property-value', '2px']]);
         },
         'has background-size': function (components) {
           assert.deepEqual(components[2].name, 'background-size');
-          assert.deepEqual(components[2].value, [['50px']]);
+          assert.deepEqual(components[2].value, [['property-value', '50px']]);
         }
       },
       'size & position joined together with 4 values': {
         'topic': function () {
-          return _breakUp([[['background'], ['5px'], ['2px/50px'], ['30px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', '5px'],
+              ['property-value', '2px'],
+              ['property-value', '/'],
+              ['property-value', '50px'],
+              ['property-value', '30px']
+            ]
+          ]);
         },
         'has 8 components': function (components) {
           assert.lengthOf(components, 8);
         },
         'has background-position': function (components) {
           assert.deepEqual(components[1].name, 'background-position');
-          assert.deepEqual(components[1].value, [['5px'], ['2px']]);
+          assert.deepEqual(components[1].value, [['property-value', '5px'], ['property-value', '2px']]);
         },
         'has background-size': function (components) {
           assert.deepEqual(components[2].name, 'background-size');
-          assert.deepEqual(components[2].value, [['50px'], ['30px']]);
+          assert.deepEqual(components[2].value, [['property-value', '50px'], ['property-value', '30px']]);
         }
       },
       'clip to origin': {
         'topic': function () {
-          return _breakUp([[['background'], ['padding-box']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'padding-box']
+            ]
+          ]);
         },
         'has 8 components': function (components) {
           assert.lengthOf(components, 8);
         },
         'has background-origin': function (components) {
           assert.deepEqual(components[5].name, 'background-origin');
-          assert.deepEqual(components[5].value, [['padding-box']]);
+          assert.deepEqual(components[5].value, [['property-value', 'padding-box']]);
         },
         'has background-clip': function (components) {
           assert.deepEqual(components[6].name, 'background-clip');
-          assert.deepEqual(components[6].value, [['padding-box']]);
+          assert.deepEqual(components[6].value, [['property-value', 'padding-box']]);
         }
       }
     },
     'border': {
       'inherit': {
         'topic': function () {
-          return _breakUp([[['border'], ['inherit']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border'],
+              ['property-value', 'inherit']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-width': function (components) {
           assert.deepEqual(components[0].name, 'border-width');
-          assert.deepEqual(components[0].value, [['inherit']]);
+          assert.deepEqual(components[0].value, [['property-value', 'inherit']]);
         },
         'has border-style': function (components) {
           assert.deepEqual(components[1].name, 'border-style');
-          assert.deepEqual(components[1].value, [['inherit']]);
+          assert.deepEqual(components[1].value, [['property-value', 'inherit']]);
         },
         'has border-color': function (components) {
           assert.deepEqual(components[2].name, 'border-color');
-          assert.deepEqual(components[2].value, [['inherit']]);
+          assert.deepEqual(components[2].value, [['property-value', 'inherit']]);
         }
       },
       '3 inherits': {
         'topic': function () {
-          return _breakUp([[['border'], ['inherit'], ['inherit'], ['inherit']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border'],
+              ['property-value', 'inherit'],
+              ['property-value', 'inherit'],
+              ['property-value', 'inherit']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-width': function (components) {
           assert.deepEqual(components[0].name, 'border-width');
-          assert.deepEqual(components[0].value, [['inherit']]);
+          assert.deepEqual(components[0].value, [['property-value', 'inherit']]);
         },
         'has border-style': function (components) {
           assert.deepEqual(components[1].name, 'border-style');
-          assert.deepEqual(components[1].value, [['inherit']]);
+          assert.deepEqual(components[1].value, [['property-value', 'inherit']]);
         },
         'has border-color': function (components) {
           assert.deepEqual(components[2].name, 'border-color');
-          assert.deepEqual(components[2].value, [['inherit']]);
+          assert.deepEqual(components[2].value, [['property-value', 'inherit']]);
         }
       },
       'all values in correct order': {
         'topic': function () {
-          return _breakUp([[['border'], ['1px'], ['solid'], ['red']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border'],
+              ['property-value', '1px'],
+              ['property-value', 'solid'],
+              ['property-value', 'red']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-width': function (components) {
           assert.deepEqual(components[0].name, 'border-width');
-          assert.deepEqual(components[0].value, [['1px']]);
+          assert.deepEqual(components[0].value, [['property-value', '1px']]);
         },
         'has border-style': function (components) {
           assert.deepEqual(components[1].name, 'border-style');
-          assert.deepEqual(components[1].value, [['solid']]);
+          assert.deepEqual(components[1].value, [['property-value', 'solid']]);
         },
         'has border-color': function (components) {
           assert.deepEqual(components[2].name, 'border-color');
-          assert.deepEqual(components[2].value, [['red']]);
+          assert.deepEqual(components[2].value, [['property-value', 'red']]);
         }
       },
       'all values in wrong order': {
         'topic': function () {
-          return _breakUp([[['border'], ['red'], ['solid'], ['1px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border'],
+              ['property-value', 'red'],
+              ['property-value', 'solid'],
+              ['property-value', '1px']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-width': function (components) {
           assert.deepEqual(components[0].name, 'border-width');
-          assert.deepEqual(components[0].value, [['1px']]);
+          assert.deepEqual(components[0].value, [['property-value', '1px']]);
         },
         'has border-style': function (components) {
           assert.deepEqual(components[1].name, 'border-style');
-          assert.deepEqual(components[1].value, [['solid']]);
+          assert.deepEqual(components[1].value, [['property-value', 'solid']]);
         },
         'has border-color': function (components) {
           assert.deepEqual(components[2].name, 'border-color');
-          assert.deepEqual(components[2].value, [['red']]);
+          assert.deepEqual(components[2].value, [['property-value', 'red']]);
         }
       },
       'missing values': {
         'topic': function () {
-          return _breakUp([[['border'], ['red']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border'],
+              ['property-value', 'red']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-width': function (components) {
           assert.deepEqual(components[0].name, 'border-width');
-          assert.deepEqual(components[0].value, [['medium']]);
+          assert.deepEqual(components[0].value, [['property-value', 'medium']]);
         },
         'has border-style': function (components) {
           assert.deepEqual(components[1].name, 'border-style');
-          assert.deepEqual(components[1].value, [['none']]);
+          assert.deepEqual(components[1].value, [['property-value', 'none']]);
         },
         'has border-color': function (components) {
           assert.deepEqual(components[2].name, 'border-color');
-          assert.deepEqual(components[2].value, [['red']]);
+          assert.deepEqual(components[2].value, [['property-value', 'red']]);
         }
       },
       'missing width': {
         'topic': function () {
-          return _breakUp([[['border'], ['solid'], ['rgba(0,0,0,0)']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border'],
+              ['property-value', 'solid'],
+              ['property-value', 'rgba(0,0,0,0)']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-width': function (components) {
           assert.deepEqual(components[0].name, 'border-width');
-          assert.deepEqual(components[0].value, [['medium']]);
+          assert.deepEqual(components[0].value, [['property-value', 'medium']]);
         },
         'has border-style': function (components) {
           assert.deepEqual(components[1].name, 'border-style');
-          assert.deepEqual(components[1].value, [['solid']]);
+          assert.deepEqual(components[1].value, [['property-value', 'solid']]);
         },
         'has border-color': function (components) {
           assert.deepEqual(components[2].name, 'border-color');
-          assert.deepEqual(components[2].value, [['rgba(0,0,0,0)']]);
+          assert.deepEqual(components[2].value, [['property-value', 'rgba(0,0,0,0)']]);
         }
       }
     },
     'border radius': {
       'no horizontal vertical split': {
         'topic': function () {
-          return _breakUp([[['border-radius'], ['0px'], ['1px'], ['2px'], ['3px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px']
+            ]
+          ]);
         },
         'has 4 components': function (components) {
           assert.lengthOf(components, 4);
         },
         'has border-top-left-radius': function (components) {
           assert.equal(components[0].name, 'border-top-left-radius');
-          assert.deepEqual(components[0].value, [['0px'], ['0px']]);
+          assert.deepEqual(components[0].value, [['property-value', '0px'], ['property-value', '0px']]);
         },
         'has border-top-right-radius': function (components) {
           assert.equal(components[1].name, 'border-top-right-radius');
-          assert.deepEqual(components[1].value, [['1px'], ['1px']]);
+          assert.deepEqual(components[1].value, [['property-value', '1px'], ['property-value', '1px']]);
         },
         'has border-bottom-right-radius': function (components) {
           assert.equal(components[2].name, 'border-bottom-right-radius');
-          assert.deepEqual(components[2].value, [['2px'], ['2px']]);
+          assert.deepEqual(components[2].value, [['property-value', '2px'], ['property-value', '2px']]);
         },
         'has border-bottom-left': function (components) {
           assert.equal(components[3].name, 'border-bottom-left-radius');
-          assert.deepEqual(components[3].value, [['3px'], ['3px']]);
+          assert.deepEqual(components[3].value, [['property-value', '3px'], ['property-value', '3px']]);
         }
       },
       'horizontal vertical split': {
         'topic': function () {
-          return _breakUp([[['border-radius'], ['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['1px'], ['2px'], ['3px'], ['4px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px'],
+              ['property-value', '/'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px'],
+              ['property-value', '4px']
+            ]
+          ]);
         },
         'has 4 components': function (components) {
           assert.lengthOf(components, 4);
         },
         'has border-top-left-radius': function (components) {
           assert.equal(components[0].name, 'border-top-left-radius');
-          assert.deepEqual(components[0].value, [['0px'], ['1px']]);
+          assert.deepEqual(components[0].value, [['property-value', '0px'], ['property-value', '1px']]);
         },
         'has border-top-right-radius': function (components) {
           assert.equal(components[1].name, 'border-top-right-radius');
-          assert.deepEqual(components[1].value, [['1px'], ['2px']]);
+          assert.deepEqual(components[1].value, [['property-value', '1px'], ['property-value', '2px']]);
         },
         'has border-bottom-right-radius': function (components) {
           assert.equal(components[2].name, 'border-bottom-right-radius');
-          assert.deepEqual(components[2].value, [['2px'], ['3px']]);
+          assert.deepEqual(components[2].value, [['property-value', '2px'], ['property-value', '3px']]);
         },
         'has border-bottom-left': function (components) {
           assert.equal(components[3].name, 'border-bottom-left-radius');
-          assert.deepEqual(components[3].value, [['3px'], ['4px']]);
+          assert.deepEqual(components[3].value, [['property-value', '3px'], ['property-value', '4px']]);
         }
       },
       'vendor prefix asymmetrical horizontal vertical split': {
         'topic': function () {
-          return _breakUp([[['-webkit-border-radius'], ['0px'], ['1px'], ['2px'], ['/'], ['1px'], ['4px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', '-webkit-border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '/'],
+              ['property-value', '1px'],
+              ['property-value', '4px']
+            ]
+          ]);
         },
         'has 4 components': function (components) {
           assert.lengthOf(components, 4);
         },
         'has border-top-left-radius': function (components) {
           assert.equal(components[0].name, '-webkit-border-top-left-radius');
-          assert.deepEqual(components[0].value, [['0px'], ['1px']]);
+          assert.deepEqual(components[0].value, [['property-value', '0px'], ['property-value', '1px']]);
         },
         'has border-top-right-radius': function (components) {
           assert.equal(components[1].name, '-webkit-border-top-right-radius');
-          assert.deepEqual(components[1].value, [['1px'], ['4px']]);
+          assert.deepEqual(components[1].value, [['property-value', '1px'], ['property-value', '4px']]);
         },
         'has border-bottom-right-radius': function (components) {
           assert.equal(components[2].name, '-webkit-border-bottom-right-radius');
-          assert.deepEqual(components[2].value, [['2px'], ['1px']]);
+          assert.deepEqual(components[2].value, [['property-value', '2px'], ['property-value', '1px']]);
         },
         'has border-bottom-left': function (components) {
           assert.equal(components[3].name, '-webkit-border-bottom-left-radius');
-          assert.deepEqual(components[3].value, [['1px'], ['4px']]);
+          assert.deepEqual(components[3].value, [['property-value', '1px'], ['property-value', '4px']]);
         }
       },
       'with missing vertical value': {
         'topic': function () {
-          return _breakUp([[['border-radius'], ['0px'], ['/']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '/']
+            ]
+          ]);
         },
         'has no components': function (components) {
           assert.lengthOf(components, 0);
@@ -385,7 +530,14 @@ vows.describe(breakUp)
       },
       'with missing horizontal value': {
         'topic': function () {
-          return _breakUp([[['border-radius'], ['/'], ['0px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '/'],
+              ['property-value', '0px']
+            ]
+          ]);
         },
         'has no components': function (components) {
           assert.lengthOf(components, 0);
@@ -395,103 +547,138 @@ vows.describe(breakUp)
     'four values': {
       'four given': {
         'topic': function () {
-          return _breakUp([[['margin'], ['0px'], ['1px'], ['2px'], ['3px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'margin'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px']
+            ]
+          ]);
         },
         'has 4 components': function (components) {
           assert.lengthOf(components, 4);
         },
         'has margin-top': function (components) {
           assert.equal(components[0].name, 'margin-top');
-          assert.deepEqual(components[0].value, [['0px']]);
+          assert.deepEqual(components[0].value, [['property-value', '0px']]);
         },
         'has margin-right': function (components) {
           assert.equal(components[1].name, 'margin-right');
-          assert.deepEqual(components[1].value, [['1px']]);
+          assert.deepEqual(components[1].value, [['property-value', '1px']]);
         },
         'has margin-bottom': function (components) {
           assert.equal(components[2].name, 'margin-bottom');
-          assert.deepEqual(components[2].value, [['2px']]);
+          assert.deepEqual(components[2].value, [['property-value', '2px']]);
         },
         'has margin-left': function (components) {
           assert.equal(components[3].name, 'margin-left');
-          assert.deepEqual(components[3].value, [['3px']]);
+          assert.deepEqual(components[3].value, [['property-value', '3px']]);
         }
       },
       'three given': {
         'topic': function () {
-          return _breakUp([[['padding'], ['0px'], ['1px'], ['2px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'padding'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px']
+            ]
+          ]);
         },
         'has 4 components': function (components) {
           assert.lengthOf(components, 4);
         },
         'has padding-top': function (components) {
           assert.equal(components[0].name, 'padding-top');
-          assert.deepEqual(components[0].value, [['0px']]);
+          assert.deepEqual(components[0].value, [['property-value', '0px']]);
         },
         'has padding-right': function (components) {
           assert.equal(components[1].name, 'padding-right');
-          assert.deepEqual(components[1].value, [['1px']]);
+          assert.deepEqual(components[1].value, [['property-value', '1px']]);
         },
         'has padding-bottom': function (components) {
           assert.equal(components[2].name, 'padding-bottom');
-          assert.deepEqual(components[2].value, [['2px']]);
+          assert.deepEqual(components[2].value, [['property-value', '2px']]);
         },
         'has padding-left': function (components) {
           assert.equal(components[3].name, 'padding-left');
-          assert.deepEqual(components[3].value, [['1px']]);
+          assert.deepEqual(components[3].value, [['property-value', '1px']]);
         }
       },
       'two given': {
         'topic': function () {
-          return _breakUp([[['border-color'], ['red'], ['blue']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border-color'],
+              ['property-value', 'red'],
+              ['property-value', 'blue']
+            ]
+          ]);
         },
         'has 4 components': function (components) {
           assert.lengthOf(components, 4);
         },
         'has border-top-color': function (components) {
           assert.equal(components[0].name, 'border-top-color');
-          assert.deepEqual(components[0].value, [['red']]);
+          assert.deepEqual(components[0].value, [['property-value', 'red']]);
         },
         'has border-right-color': function (components) {
           assert.equal(components[1].name, 'border-right-color');
-          assert.deepEqual(components[1].value, [['blue']]);
+          assert.deepEqual(components[1].value, [['property-value', 'blue']]);
         },
         'has border-bottom-color': function (components) {
           assert.equal(components[2].name, 'border-bottom-color');
-          assert.deepEqual(components[2].value, [['red']]);
+          assert.deepEqual(components[2].value, [['property-value', 'red']]);
         },
         'has border-left-color': function (components) {
           assert.equal(components[3].name, 'border-left-color');
-          assert.deepEqual(components[3].value, [['blue']]);
+          assert.deepEqual(components[3].value, [['property-value', 'blue']]);
         }
       },
       'one given': {
         'topic': function () {
-          return _breakUp([[['border-style'], ['solid']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border-style'],
+              ['property-value', 'solid']
+            ]
+          ]);
         },
         'has 4 components': function (components) {
           assert.lengthOf(components, 4);
         },
         'has border-top-style': function (components) {
           assert.equal(components[0].name, 'border-top-style');
-          assert.deepEqual(components[0].value, [['solid']]);
+          assert.deepEqual(components[0].value, [['property-value', 'solid']]);
         },
         'has border-right-style': function (components) {
           assert.equal(components[1].name, 'border-right-style');
-          assert.deepEqual(components[1].value, [['solid']]);
+          assert.deepEqual(components[1].value, [['property-value', 'solid']]);
         },
         'has border-bottom-style': function (components) {
           assert.equal(components[2].name, 'border-bottom-style');
-          assert.deepEqual(components[2].value, [['solid']]);
+          assert.deepEqual(components[2].value, [['property-value', 'solid']]);
         },
         'has border-left-style': function (components) {
           assert.equal(components[3].name, 'border-left-style');
-          assert.deepEqual(components[3].value, [['solid']]);
+          assert.deepEqual(components[3].value, [['property-value', 'solid']]);
         }
       },
       'none given': {
         'topic': function () {
-          return _breakUp([[['border-style']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'border-style']
+            ]
+          ]);
         },
         'has 0 components': function (components) {
           assert.lengthOf(components, 0);
@@ -501,285 +688,396 @@ vows.describe(breakUp)
     'list style': {
       'inherit': {
         'topic': function () {
-          return _breakUp([[['list-style'], ['inherit']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'list-style'],
+              ['property-value', 'inherit']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-top-style': function (components) {
           assert.equal(components[0].name, 'list-style-type');
-          assert.deepEqual(components[0].value, [['inherit']]);
+          assert.deepEqual(components[0].value, [['property-value', 'inherit']]);
         },
         'has border-right-style': function (components) {
           assert.equal(components[1].name, 'list-style-position');
-          assert.deepEqual(components[1].value, [['inherit']]);
+          assert.deepEqual(components[1].value, [['property-value', 'inherit']]);
         },
         'has border-bottom-style': function (components) {
           assert.equal(components[2].name, 'list-style-image');
-          assert.deepEqual(components[2].value, [['inherit']]);
+          assert.deepEqual(components[2].value, [['property-value', 'inherit']]);
         }
       },
       'all values': {
         'topic': function () {
-          return _breakUp([[['list-style'], ['circle'], ['inside'], ['__ESCAPED_URL_CLEAN_CSS0__']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'list-style'],
+              ['property-value', 'circle'],
+              ['property-value', 'inside'],
+              ['property-value', 'url(image.png)']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-top-style': function (components) {
           assert.equal(components[0].name, 'list-style-type');
-          assert.deepEqual(components[0].value, [['circle']]);
+          assert.deepEqual(components[0].value, [['property-value', 'circle']]);
         },
         'has border-right-style': function (components) {
           assert.equal(components[1].name, 'list-style-position');
-          assert.deepEqual(components[1].value, [['inside']]);
+          assert.deepEqual(components[1].value, [['property-value', 'inside']]);
         },
         'has border-bottom-style': function (components) {
           assert.equal(components[2].name, 'list-style-image');
-          assert.deepEqual(components[2].value, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+          assert.deepEqual(components[2].value, [['property-value', 'url(image.png)']]);
         }
       },
       'some missing': {
         'topic': function () {
-          return _breakUp([[['list-style'], ['inside']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'list-style'],
+              ['property-value', 'inside']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has border-top-style': function (components) {
           assert.equal(components[0].name, 'list-style-type');
-          assert.deepEqual(components[0].value, [['__hack']]);
+          assert.deepEqual(components[0].value, [['property-value', '__hack']]);
         },
         'has border-right-style': function (components) {
           assert.equal(components[1].name, 'list-style-position');
-          assert.deepEqual(components[1].value, [['inside']]);
+          assert.deepEqual(components[1].value, [['property-value', 'inside']]);
         },
         'has border-bottom-style': function (components) {
           assert.equal(components[2].name, 'list-style-image');
-          assert.deepEqual(components[2].value, [['none']]);
+          assert.deepEqual(components[2].value, [['property-value', 'none']]);
         }
       },
       'fuzzy matching': {
         'topic': function () {
-          return _breakUp([[['list-style'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['outside'], ['none']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'list-style'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'outside'],
+              ['property-value', 'none']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has list-style-type': function (components) {
           assert.equal(components[0].name, 'list-style-type');
-          assert.deepEqual(components[0].value, [['none']]);
+          assert.deepEqual(components[0].value, [['property-value', 'none']]);
         },
         'has list-style-position': function (components) {
           assert.equal(components[1].name, 'list-style-position');
-          assert.deepEqual(components[1].value, [['outside']]);
+          assert.deepEqual(components[1].value, [['property-value', 'outside']]);
         },
         'has list-style-image': function (components) {
           assert.equal(components[2].name, 'list-style-image');
-          assert.deepEqual(components[2].value, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+          assert.deepEqual(components[2].value, [['property-value', 'url(image.png)']]);
         }
       }
     },
-    'multiple values': {
+    'multiple values 123': {
       'background': {
         'topic': function () {
-          return _breakUp([[['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['#fff'], [','], ['url(image2.png)'], ['repeat'], ['no-repeat'], ['2px'], ['3px'], ['/'], ['50%'], ['60%'], ['fixed'], ['content-box'], ['content-box'], ['red']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', '#fff'],
+              ['property-value', ','],
+              ['property-value', 'url(image2.png)'],
+              ['property-value', 'repeat'],
+              ['property-value', 'no-repeat'],
+              ['property-value', '2px'],
+              ['property-value', '3px'],
+              ['property-value', '/'],
+              ['property-value', '50%'],
+              ['property-value', '60%'],
+              ['property-value', 'fixed'],
+              ['property-value', 'content-box'],
+              ['property-value', 'content-box'],
+              ['property-value', 'red']
+            ]
+          ]);
         },
         'has 8 components': function (components) {
           assert.lengthOf(components, 8);
         },
         'has background-image': function (components) {
           assert.deepEqual(components[0].name, 'background-image');
-          assert.deepEqual(components[0].value, [['__ESCAPED_URL_CLEAN_CSS0__'], [','], ['url(image2.png)']]);
+          assert.deepEqual(components[0].value, [['property-value', 'url(image.png)'], ['property-value', ','], ['property-value', 'url(image2.png)']]);
           assert.isTrue(components[0].multiplex);
         },
         'has background-position': function (components) {
           assert.deepEqual(components[1].name, 'background-position');
-          assert.deepEqual(components[1].value, [['0'], ['0'], [','], ['2px'], ['3px']]);
+          assert.deepEqual(components[1].value, [['property-value', '0'], ['property-value', '0'], ['property-value', ','], ['property-value', '2px'], ['property-value', '3px']]);
           assert.isTrue(components[0].multiplex);
         },
         'has background-size': function (components) {
           assert.deepEqual(components[2].name, 'background-size');
-          assert.deepEqual(components[2].value, [['auto'], [','], ['50%'], ['60%']]);
+          assert.deepEqual(components[2].value, [['property-value', 'auto'], ['property-value', ','], ['property-value', '50%'], ['property-value', '60%']]);
           assert.isTrue(components[0].multiplex);
         },
         'has background-repeat': function (components) {
           assert.deepEqual(components[3].name, 'background-repeat');
-          assert.deepEqual(components[3].value, [['repeat'], [','], ['repeat'], ['no-repeat']]);
+          assert.deepEqual(components[3].value, [['property-value', 'repeat'], ['property-value', ','], ['property-value', 'repeat'], ['property-value', 'no-repeat']]);
           assert.isTrue(components[0].multiplex);
         },
         'has background-attachment': function (components) {
           assert.deepEqual(components[4].name, 'background-attachment');
-          assert.deepEqual(components[4].value, [['scroll'], [','], ['fixed']]);
+          assert.deepEqual(components[4].value, [['property-value', 'scroll'], ['property-value', ','], ['property-value', 'fixed']]);
           assert.isTrue(components[0].multiplex);
         },
         'has background-origin': function (components) {
           assert.deepEqual(components[5].name, 'background-origin');
-          assert.deepEqual(components[5].value, [['padding-box'], [','], ['content-box']]);
+          assert.deepEqual(components[5].value, [['property-value', 'padding-box'], ['property-value', ','], ['property-value', 'content-box']]);
           assert.isTrue(components[0].multiplex);
         },
         'has background-clip': function (components) {
           assert.deepEqual(components[6].name, 'background-clip');
-          assert.deepEqual(components[6].value, [['border-box'], [','], ['content-box']]);
+          assert.deepEqual(components[6].value, [['property-value', 'border-box'], ['property-value', ','], ['property-value', 'content-box']]);
           assert.isTrue(components[0].multiplex);
         },
         'has background-color': function (components) {
           assert.deepEqual(components[7].name, 'background-color');
-          assert.deepEqual(components[7].value, [['#fff'], [','], ['red']]);
+          assert.deepEqual(components[7].value, [['property-value', '#fff'], ['property-value', ','], ['property-value', 'red']]);
           assert.isTrue(components[0].multiplex);
         }
       },
       'background - clip & origin': {
         'topic': function () {
-          return _breakUp([[['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box'], [','], ['repeat'], ['red']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'no-repeat'],
+              ['property-value', 'padding-box'],
+              ['property-value', ','],
+              ['property-value', 'repeat'],
+              ['property-value', 'red']
+            ]
+          ]);
         },
         'has background-origin': function (components) {
-          assert.deepEqual(components[5].value, [['padding-box'], [','], ['padding-box']]);
+          assert.deepEqual(components[5].value, [['property-value', 'padding-box'], ['property-value', ','], ['property-value', 'padding-box']]);
         },
         'has background-clip': function (components) {
-          assert.deepEqual(components[6].value, [['padding-box'], [','], ['border-box']]);
+          assert.deepEqual(components[6].value, [['property-value', 'padding-box'], ['property-value', ','], ['property-value', 'border-box']]);
         }
       }
     },
     'outline': {
       'inherit': {
         'topic': function () {
-          return _breakUp([[['outline'], ['inherit']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', 'inherit']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has outline-color': function (components) {
           assert.deepEqual(components[0].name, 'outline-color');
-          assert.deepEqual(components[0].value, [['inherit']]);
+          assert.deepEqual(components[0].value, [['property-value', 'inherit']]);
         },
         'has outline-style': function (components) {
           assert.deepEqual(components[1].name, 'outline-style');
-          assert.deepEqual(components[1].value, [['inherit']]);
+          assert.deepEqual(components[1].value, [['property-value', 'inherit']]);
         },
         'has outline-width': function (components) {
           assert.deepEqual(components[2].name, 'outline-width');
-          assert.deepEqual(components[2].value, [['inherit']]);
+          assert.deepEqual(components[2].value, [['property-value', 'inherit']]);
         }
       },
       '3 inherits': {
         'topic': function () {
-          return _breakUp([[['outline'], ['inherit'], ['inherit'], ['inherit']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', 'inherit'],
+              ['property-value', 'inherit'],
+              ['property-value', 'inherit']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has outline-color': function (components) {
           assert.deepEqual(components[0].name, 'outline-color');
-          assert.deepEqual(components[0].value, [['inherit']]);
+          assert.deepEqual(components[0].value, [['property-value', 'inherit']]);
         },
         'has outline-style': function (components) {
           assert.deepEqual(components[1].name, 'outline-style');
-          assert.deepEqual(components[1].value, [['inherit']]);
+          assert.deepEqual(components[1].value, [['property-value', 'inherit']]);
         },
         'has outline-width': function (components) {
           assert.deepEqual(components[2].name, 'outline-width');
-          assert.deepEqual(components[2].value, [['inherit']]);
+          assert.deepEqual(components[2].value, [['property-value', 'inherit']]);
         }
       },
       'all values in correct order': {
         'topic': function () {
-          return _breakUp([[['outline'], ['red'], ['solid'], ['1px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', 'red'],
+              ['property-value', 'solid'],
+              ['property-value', '1px']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has outline-color': function (components) {
           assert.deepEqual(components[0].name, 'outline-color');
-          assert.deepEqual(components[0].value, [['red']]);
+          assert.deepEqual(components[0].value, [['property-value', 'red']]);
         },
         'has outline-style': function (components) {
           assert.deepEqual(components[1].name, 'outline-style');
-          assert.deepEqual(components[1].value, [['solid']]);
+          assert.deepEqual(components[1].value, [['property-value', 'solid']]);
         },
         'has outline-width': function (components) {
           assert.deepEqual(components[2].name, 'outline-width');
-          assert.deepEqual(components[2].value, [['1px']]);
+          assert.deepEqual(components[2].value, [['property-value', '1px']]);
         }
       },
       'all values in wrong order': {
         'topic': function () {
-          return _breakUp([[['outline'], ['1px'], ['dotted'], ['#fff']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', '1px'],
+              ['property-value', 'dotted'],
+              ['property-value', '#fff']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has outline-color': function (components) {
           assert.deepEqual(components[0].name, 'outline-color');
-          assert.deepEqual(components[0].value, [['#fff']]);
+          assert.deepEqual(components[0].value, [['property-value', '#fff']]);
         },
         'has outline-style': function (components) {
           assert.deepEqual(components[1].name, 'outline-style');
-          assert.deepEqual(components[1].value, [['dotted']]);
+          assert.deepEqual(components[1].value, [['property-value', 'dotted']]);
         },
         'has outline-width': function (components) {
           assert.deepEqual(components[2].name, 'outline-width');
-          assert.deepEqual(components[2].value, [['1px']]);
+          assert.deepEqual(components[2].value, [['property-value', '1px']]);
         }
       },
       'with auto style': {
         'topic': function () {
-          return _breakUp([[['outline'], ['#fff'], ['auto'], ['1px']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', '#fff'],
+              ['property-value', 'auto'],
+              ['property-value', '1px']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has outline-color': function (components) {
           assert.deepEqual(components[0].name, 'outline-color');
-          assert.deepEqual(components[0].value, [['#fff']]);
+          assert.deepEqual(components[0].value, [['property-value', '#fff']]);
         },
         'has outline-style': function (components) {
           assert.deepEqual(components[1].name, 'outline-style');
-          assert.deepEqual(components[1].value, [['auto']]);
+          assert.deepEqual(components[1].value, [['property-value', 'auto']]);
         },
         'has outline-width': function (components) {
           assert.deepEqual(components[2].name, 'outline-width');
-          assert.deepEqual(components[2].value, [['1px']]);
+          assert.deepEqual(components[2].value, [['property-value', '1px']]);
         }
       },
       'missing values': {
         'topic': function () {
-          return _breakUp([[['outline'], ['solid']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', 'solid']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has outline-color': function (components) {
           assert.deepEqual(components[0].name, 'outline-color');
-          assert.deepEqual(components[0].value, [['invert']]);
+          assert.deepEqual(components[0].value, [['property-value', 'invert']]);
         },
         'has outline-style': function (components) {
           assert.deepEqual(components[1].name, 'outline-style');
-          assert.deepEqual(components[1].value, [['solid']]);
+          assert.deepEqual(components[1].value, [['property-value', 'solid']]);
         },
         'has outline-width': function (components) {
           assert.deepEqual(components[2].name, 'outline-width');
-          assert.deepEqual(components[2].value, [['medium']]);
+          assert.deepEqual(components[2].value, [['property-value', 'medium']]);
         }
       },
       'default values': {
         'topic': function () {
-          return _breakUp([[['outline'], ['invert'], ['none'], ['medium']]]);
+          return _breakUp([
+            [
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', 'invert'],
+              ['property-value', 'none'],
+              ['property-value', 'medium']
+            ]
+          ]);
         },
         'has 3 components': function (components) {
           assert.lengthOf(components, 3);
         },
         'has outline-color': function (components) {
           assert.deepEqual(components[0].name, 'outline-color');
-          assert.deepEqual(components[0].value, [['invert']]);
+          assert.deepEqual(components[0].value, [['property-value', 'invert']]);
         },
         'has outline-style': function (components) {
           assert.deepEqual(components[1].name, 'outline-style');
-          assert.deepEqual(components[1].value, [['none']]);
+          assert.deepEqual(components[1].value, [['property-value', 'none']]);
         },
         'has outline-width': function (components) {
           assert.deepEqual(components[2].name, 'outline-width');
-          assert.deepEqual(components[2].value, [['medium']]);
+          assert.deepEqual(components[2].value, [['property-value', 'medium']]);
         }
       }
     }
index fef5949..eb1f55c 100644 (file)
@@ -4,20 +4,18 @@ var assert = require('assert');
 var optimize = require('../../lib/properties/optimizer');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
-var SourceTracker = require('../../lib/utils/source-tracker');
-var Compatibility = require('../../lib/utils/compatibility');
+var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
 function _optimize(source) {
   var tokens = tokenize(source, {
     options: {},
-    sourceTracker: new SourceTracker(),
     warnings: []
   });
 
-  var compatibility = new Compatibility().toOptions();
-  var validator = new Validator(compatibility);
-  optimize(tokens[0][1], tokens[0][2], false, true, { compatibility: compatibility, aggressiveMerging: true, shorthandCompacting: true }, { validator: validator });
+  var compat = compatibility();
+  var validator = new Validator(compat);
+  optimize(tokens[0][1], tokens[0][2], false, true, { options: { compatibility: compat, aggressiveMerging: true, shorthandCompacting: true }, validator: validator });
 
   return tokens[0][2];
 }
@@ -27,12 +25,12 @@ function longhandFirst(prefixedLonghand, prefixedShorthand, zeroValue) {
     'topic': function () {
       return _optimize('a{' + prefixedLonghand + ':inherit;' + prefixedShorthand + ':' + zeroValue + '}');
     },
-    'has one token': function (body) {
-      assert.lengthOf(body, 1);
+    'has one token': function (properties) {
+      assert.lengthOf(properties, 1);
     },
-    'has zero value only': function (body) {
-      assert.deepEqual(body[0][0], [prefixedShorthand]);
-      assert.deepEqual(body[0][1], [zeroValue]);
+    'has zero value only': function (properties) {
+      assert.deepEqual(properties[0][1][1], prefixedShorthand);
+      assert.deepEqual(properties[0][2][1], zeroValue);
     }
   };
 }
@@ -42,16 +40,16 @@ function shorthandFirst(prefixedLonghand, prefixedShorthand, zeroValue) {
     'topic': function () {
       return _optimize('a{' + prefixedShorthand + ':' + zeroValue + ';' + prefixedLonghand + ':inherit}');
     },
-    'has two tokens': function (body) {
-      assert.lengthOf(body, 2);
+    'has two tokens': function (properties) {
+      assert.lengthOf(properties, 2);
     },
-    'first is shorthand': function (body) {
-      assert.deepEqual(body[0][0], [prefixedShorthand]);
-      assert.deepEqual(body[0][1], [zeroValue]);
+    'first is shorthand': function (properties) {
+      assert.deepEqual(properties[0][1][1], prefixedShorthand);
+      assert.deepEqual(properties[0][2][1], zeroValue);
     },
-    'second is longhand': function (body) {
-      assert.deepEqual(body[1][0], [prefixedLonghand]);
-      assert.deepEqual(body[1][1], ['inherit']);
+    'second is longhand': function (properties) {
+      assert.deepEqual(properties[1][1][1], prefixedLonghand);
+      assert.deepEqual(properties[1][2][1], 'inherit');
     }
   };
 }
index 1cf25a2..ed5d693 100644 (file)
@@ -4,21 +4,19 @@ var assert = require('assert');
 var optimize = require('../../lib/properties/optimizer');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
-var SourceTracker = require('../../lib/utils/source-tracker');
-var Compatibility = require('../../lib/utils/compatibility');
+var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
 function _optimize(source, mergeAdjacent, aggressiveMerging, compatibilityOptions) {
-  var compatibility = new Compatibility(compatibilityOptions).toOptions();
-  var validator = new Validator(compatibility);
+  var compat = compatibility(compatibilityOptions);
+  var validator = new Validator(compat);
 
   var tokens = tokenize(source, {
     options: {},
-    sourceTracker: new SourceTracker(),
     warnings: []
   });
 
-  optimize(tokens[0][1], tokens[0][2], mergeAdjacent, true, { compatibility: compatibility, aggressiveMerging: aggressiveMerging }, { validator: validator });
+  optimize(tokens[0][1], tokens[0][2], mergeAdjacent, true, { options: { compatibility: compat, aggressiveMerging: aggressiveMerging }, validator: validator });
 
   return tokens[0][2];
 }
@@ -26,399 +24,890 @@ function _optimize(source, mergeAdjacent, aggressiveMerging, compatibilityOption
 vows.describe(optimize)
   .addBatch({
     'of two adjacent properties': {
-      'topic': 'a{display:-moz-inline-box;display:inline-block}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['display'], ['-moz-inline-box']],
-          [['display'], ['inline-block']]
+      'topic': function () {
+        return _optimize('a{display:-moz-inline-box;display:inline-block}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 2, undefined]]],
+            ['property-value', '-moz-inline-box', [[1, 10, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 26, undefined]]],
+            ['property-value', 'inline-block', [[1, 34, undefined]]]
+          ]
         ]);
       }
     },
     'of two properties ': {
-      'topic': 'a{display:inline-block;color:red;display:block}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['color'], ['red']],
-          [['display'], ['block']]
+      'topic': function () {
+        return _optimize('a{display:inline-block;color:red;display:block}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 23, undefined]]],
+            ['property-value', 'red', [[1, 29, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 33, undefined]]],
+            ['property-value', 'block', [[1, 41, undefined]]]
+          ]
         ]);
       }
     },
     'of two same properties with same value where latter is a hack': {
-      'topic': 'a{margin:0;_margin:0}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['margin'], ['0']],
-          [['_margin'], ['0']]
+      'topic': function () {
+        return _optimize('a{margin:0;_margin:0}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin', [[1, 2, undefined]]],
+            ['property-value', '0', [[1, 9, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '_margin', [[1, 11, undefined]]],
+            ['property-value', '0', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'of two same properties with same value where latter is !important': {
-      'topic': 'a{margin:0;margin:0 !important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['margin'], ['0']],
-          [['margin'], ['0!important']]
+      'topic': function () {
+        return _optimize('a{margin:0;margin:0 !important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin', [[1, 2, undefined]]],
+            ['property-value', '0', [[1, 9, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'margin', [[1, 11, undefined]]],
+            ['property-value', '0!important', [[1, 18, undefined]]]
+          ]
         ]);
       }
     },
     'of two properties where former is !important': {
-      'topic': 'a{display:inline-block!important;color:red;display:block}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['display'], ['inline-block!important']],
-          [['color'], ['red']]
+      'topic': function () {
+        return _optimize('a{display:inline-block!important;color:red;display:block}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 2, undefined]]],
+            ['property-value', 'inline-block!important', [[1, 10, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 33, undefined]]],
+            ['property-value', 'red', [[1, 39, undefined]]]
+          ]
         ]);
       }
     },
     'of two properties  where latter is !important': {
-      'topic': 'a{display:inline-block;color:red;display:block!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['color'], ['red']],
-          [['display'], ['block!important']]
+      'topic': function () {
+        return _optimize('a{display:inline-block;color:red;display:block!important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 23, undefined]]],
+            ['property-value', 'red', [[1, 29, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 33, undefined]]],
+            ['property-value', 'block!important', [[1, 41, undefined]]]
+          ]
         ]);
       }
     },
     'of two properties  where both are !important': {
-      'topic': 'a{display:inline-block!important;color:red;display:block!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['color'], ['red']],
-          [['display'], ['block!important']]
+      'topic': function () {
+        return _optimize('a{display:inline-block!important;color:red;display:block!important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 33, undefined]]],
+            ['property-value', 'red', [[1, 39, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 43, undefined]]],
+            ['property-value', 'block!important', [[1, 51, undefined]]]
+          ]
         ]);
       }
     },
     'of many properties': {
-      'topic': 'a{display:inline-block;color:red;font-weight:bolder;font-weight:700;display:block!important;color:#fff}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['font-weight'], ['bolder']],
-          [['font-weight'], ['700']],
-          [['display'], ['block!important']],
-          [['color'], ['#fff']]
+      'topic': function () {
+        return _optimize('a{display:inline-block;color:red;font-weight:bolder;font-weight:700;display:block!important;color:#fff}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'font-weight', [[1, 33, undefined]]],
+            ['property-value', 'bolder', [[1, 45, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'font-weight', [[1, 52, undefined]]],
+            ['property-value', '700', [[1, 64, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 68, undefined]]],
+            ['property-value', 'block!important', [[1, 76, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 92, undefined]]],
+            ['property-value', '#fff', [[1, 98, undefined]]]
+          ]
         ]);
       }
     },
     'both redefined': {
-      'topic': 'p{display:block;display:-moz-inline-box;color:red;display:table-cell}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['color'], ['red']],
-          [['display'], ['table-cell']]
+      'topic': function () {
+        return _optimize('p{display:block;display:-moz-inline-box;color:red;display:table-cell}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 40, undefined]]],
+            ['property-value', 'red', [[1, 46, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 50, undefined]]],
+            ['property-value', 'table-cell', [[1, 58, undefined]]]
+          ]
         ]);
       }
     },
     'filter treated as background': {
-      'topic': 'p{background:-moz-linear-gradient();background:-webkit-linear-gradient();filter:"progid:DXImageTransform";background:linear-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['background'], ['-moz-linear-gradient()']],
-          [['background'], ['-webkit-linear-gradient()']],
-          [['filter'], ['"progid:DXImageTransform"']],
-          [['background'], ['linear-gradient()']]
+      'topic': function () {
+        return _optimize('p{background:-moz-linear-gradient();background:-webkit-linear-gradient();filter:"progid:DXImageTransform";background:linear-gradient()}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', '-moz-linear-gradient()', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 36, undefined]]],
+            ['property-value', '-webkit-linear-gradient()', [[1, 47, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'filter', [[1, 73, undefined]]],
+            ['property-value', '"progid:DXImageTransform"', [[1, 80, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 106, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 117, undefined]]]
+          ]
         ]);
       }
     },
     'filter treated as background-image': {
-      'topic': 'p{background-image:-moz-linear-gradient();background-image:-webkit-linear-gradient();filter:"progid:DXImageTransform";background-image:linear-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['background-image'], ['-moz-linear-gradient()']],
-          [['background-image'], ['-webkit-linear-gradient()']],
-          [['filter'], ['"progid:DXImageTransform"']],
-          [['background-image'], ['linear-gradient()']]
+      'topic': function () {
+        return _optimize('p{background-image:-moz-linear-gradient();background-image:-webkit-linear-gradient();filter:"progid:DXImageTransform";background-image:linear-gradient()}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 2, undefined]]],
+            ['property-value', '-moz-linear-gradient()', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 42, undefined]]],
+            ['property-value', '-webkit-linear-gradient()', [[1, 59, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'filter', [[1, 85, undefined]]],
+            ['property-value', '"progid:DXImageTransform"', [[1, 92, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 118, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 135, undefined]]]
+          ]
         ]);
       }
     },
     '-ms-filter treated as background': {
-      'topic': 'p{background:-moz-linear-gradient();background:-webkit-linear-gradient();-ms-filter:"progid:DXImageTransform";background:linear-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['background'], ['-moz-linear-gradient()']],
-          [['background'], ['-webkit-linear-gradient()']],
-          [['-ms-filter'], ['"progid:DXImageTransform"']],
-          [['background'], ['linear-gradient()']]
+      'topic': function () {
+        return _optimize('p{background:-moz-linear-gradient();background:-webkit-linear-gradient();-ms-filter:"progid:DXImageTransform";background:linear-gradient()}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', '-moz-linear-gradient()', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 36, undefined]]],
+            ['property-value', '-webkit-linear-gradient()', [[1, 47, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '-ms-filter', [[1, 73, undefined]]],
+            ['property-value', '"progid:DXImageTransform"', [[1, 84, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 110, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 121, undefined]]]
+          ]
         ]);
       }
     },
     '-ms-filter treated as background-image': {
-      'topic': 'p{background-image:-moz-linear-gradient();background-image:-webkit-linear-gradient();-ms-filter:"progid:DXImageTransform";background-image:linear-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['background-image'], ['-moz-linear-gradient()']],
-          [['background-image'], ['-webkit-linear-gradient()']],
-          [['-ms-filter'], ['"progid:DXImageTransform"']],
-          [['background-image'], ['linear-gradient()']]
+      'topic': function () {
+        return _optimize('p{background-image:-moz-linear-gradient();background-image:-webkit-linear-gradient();-ms-filter:"progid:DXImageTransform";background-image:linear-gradient()}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 2, undefined]]],
+            ['property-value', '-moz-linear-gradient()', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 42, undefined]]],
+            ['property-value', '-webkit-linear-gradient()', [[1, 59, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '-ms-filter', [[1, 85, undefined]]],
+            ['property-value', '"progid:DXImageTransform"', [[1, 96, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 122, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 139, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand': {
-      'topic': 'p{border-left-style:solid;border:1px dotted red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['border'], ['1px'], ['dotted'], ['red']]
+      'topic': function () {
+        return _optimize('p{border-left-style:solid;border:1px dotted red}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 26, undefined]]],
+            ['property-value', '1px', [[1, 33, undefined]]],
+            ['property-value', 'dotted', [[1, 37, undefined]]],
+            ['property-value', 'red', [[1, 44, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand with important': {
-      'topic': 'p{border-left-style:solid!important;border:1px dotted red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['border-left-style'], ['solid!important']],
-          [['border'], ['1px'], ['dotted'], ['red']]
+      'topic': function () {
+        return _optimize('p{border-left-style:solid!important;border:1px dotted red}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-left-style', [[1, 2, undefined]]],
+            ['property-value', 'solid!important', [[1, 20, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border', [[1, 36, undefined]]],
+            ['property-value', '1px', [[1, 43, undefined]]],
+            ['property-value', 'dotted', [[1, 47, undefined]]],
+            ['property-value', 'red', [[1, 54, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand': {
-      'topic': 'p{background:url(image.png);background-image:#fff}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['background'], ['url(image.png)']],
-          [['background-image'], ['#fff']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background-image:#fff}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 28, undefined]]],
+            ['property-value', '#fff', [[1, 45, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'list-style fuzzy matching': {
-      'topic': 'p{list-style:inside none}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['list-style'], ['none'], ['inside']]
+      'topic': function () {
+        return _optimize('p{list-style:inside none}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'list-style', [[1, 2, undefined]]],
+            ['property-value', 'none', [[1, 20, undefined]]],
+            ['property-value', 'inside', [[1, 13, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'ie hacks - normal before hack': {
-      'topic': 'p{color:red;display:none;color:#fff\\9}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['color'], ['red']],
-          [['display'], ['none']],
-          [['color'], ['#fff\\9']]
+      'topic': function () {
+        return _optimize('p{color:red;display:none;color:#fff\\9}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 8, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 12, undefined]]],
+            ['property-value', 'none', [[1, 20, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 25, undefined]]],
+            ['property-value', '#fff\\9', [[1, 31, undefined]]]
+          ]
         ]);
       }
     },
     'ie hacks - normal after hack': {
-      'topic': 'p{color:red\\9;display:none;color:#fff}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['color'], ['red\\9']],
-          [['display'], ['none']],
-          [['color'], ['#fff']]
+      'topic': function () {
+        return _optimize('p{color:red\\9;display:none;color:#fff}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', 'red\\9', [[1, 8, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 14, undefined]]],
+            ['property-value', 'none', [[1, 22, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 27, undefined]]],
+            ['property-value', '#fff', [[1, 33, undefined]]]
+          ]
         ]);
       }
     },
     'ie hacks - hack after hack': {
-      'topic': 'p{color:red\\9;display:none;color:#fff\\9}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['display'], ['none']],
-          [['color'], ['#fff\\9']]
+      'topic': function () {
+        return _optimize('p{color:red\\9;display:none;color:#fff\\9}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 14, undefined]]],
+            ['property-value', 'none', [[1, 22, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 27, undefined]]],
+            ['property-value', '#fff\\9', [[1, 33, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'mergeAdjacent is true': {
-      'topic': 'p{display:block;display:inline-block}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, true, true), [
-          [['display'], ['inline-block']]
+      'topic': function () {
+        return _optimize('p{display:block;display:inline-block}', true, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 16, undefined]]],
+            ['property-value', 'inline-block', [[1, 24, undefined]]]
+          ]
         ]);
       }
     },
     'mergeAdjacent is false': {
-      'topic': 'p{display:block;display:inline-block}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['display'], ['block']],
-          [['display'], ['inline-block']]
+      'topic': function () {
+        return _optimize('p{display:block;display:inline-block}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 2, undefined]]],
+            ['property-value', 'block', [[1, 10, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 16, undefined]]],
+            ['property-value', 'inline-block', [[1, 24, undefined]]]
+          ]
         ]);
       }
     },
     'mergeAdjacent is an array with irrelevant join positions': {
-      'topic': 'p{display:block;display:inline-block;color:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, [2], true), [
-          [['display'], ['block']],
-          [['display'], ['inline-block']],
-          [['color'], ['red']]
+      'topic': function () {
+        return _optimize('p{display:block;display:inline-block;color:red}', [2], true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 2, undefined]]],
+            ['property-value', 'block', [[1, 10, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 16, undefined]]],
+            ['property-value', 'inline-block', [[1, 24, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 37, undefined]]],
+            ['property-value', 'red', [[1, 43, undefined]]]
+          ]
         ]);
       }
     },
     'mergeAdjacent is an array with relevant join positions': {
-      'topic': 'p{display:block;display:inline-block;color:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, [1], true), [
-          [['display'], ['inline-block']],
-          [['color'], ['red']]
+      'topic': function () {
+        return _optimize('p{display:block;display:inline-block;color:red}', [1], true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 16, undefined]]],
+            ['property-value', 'inline-block', [[1, 24, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 37, undefined]]],
+            ['property-value', 'red', [[1, 43, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'aggressive off - (yet) not overriddable': {
-      'topic': 'a{display:inline-block;color:red;display:-moz-block}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false), [
-          [['display'], ['inline-block']],
-          [['color'], ['red']],
-          [['display'], ['-moz-block']]
+      'topic': function () {
+        return _optimize('a{display:inline-block;color:red;display:-moz-block}', false);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 2, undefined]]],
+            ['property-value', 'inline-block', [[1, 10, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 23, undefined]]],
+            ['property-value', 'red', [[1, 29, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 33, undefined]]],
+            ['property-value', '-moz-block', [[1, 41, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'understandable - 2 properties, both !important, 2nd less understandable': {
-      'topic': 'a{color:red!important;display:block;color:rgba(0,255,0,.5)!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['color'], ['red!important']],
-          [['display'], ['block']],
-          [['color'], ['rgba(0,255,0,.5)!important']]
+      'topic': function () {
+        return _optimize('a{color:red!important;display:block;color:rgba(0,255,0,.5)!important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', 'red!important', [[1, 8, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 22, undefined]]],
+            ['property-value', 'block', [[1, 30, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 36, undefined]]],
+            ['property-value', 'rgba(0,255,0,.5)!important', [[1, 42, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - 2 properties, both !important, 2nd more understandable': {
-      'topic': 'a{color:rgba(0,255,0,.5)!important;display:block;color:red!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['display'], ['block']],
-          [['color'], ['red!important']]
+      'topic': function () {
+        return _optimize('a{color:rgba(0,255,0,.5)!important;display:block;color:red!important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 35, undefined]]],
+            ['property-value', 'block', [[1, 43, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 49, undefined]]],
+            ['property-value', 'red!important', [[1, 55, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - 2 adjacent properties, both !important, 2nd less understandable': {
-      'topic': 'a{background:red!important;background:rgba(0,255,0,.5)!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['background'], ['red!important']],
-          [['background'], ['rgba(0,255,0,.5)!important']]
+      'topic': function () {
+        return _optimize('a{background:red!important;background:rgba(0,255,0,.5)!important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'red!important', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 27, undefined]]],
+            ['property-value', 'rgba(0,255,0,.5)!important', [[1, 38, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - 2 adjacent properties, both !important, 2nd more understandable': {
-      'topic': 'a{background:rgba(0,255,0,.5)!important;background:red!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['background'], ['rgba(0,255,0,.5)!important']],
-          [['background'], ['red!important']]
+      'topic': function () {
+        return _optimize('a{background:rgba(0,255,0,.5)!important;background:red!important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'rgba(0,255,0,.5)!important', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 40, undefined]]],
+            ['property-value', 'red!important', [[1, 51, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - 2 adjacent -ms-transform with different values': {
-      'topic': 'div{-ms-transform:translate(0,0);-ms-transform:translate3d(0,0,0)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['-ms-transform'], ['translate(0,0)']],
-          [['-ms-transform'], ['translate3d(0,0,0)']]
+      'topic': function () {
+        return _optimize('div{-ms-transform:translate(0,0);-ms-transform:translate3d(0,0,0)}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', '-ms-transform', [[1, 4, undefined]]],
+            ['property-value', 'translate(0,0)', [[1, 18, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '-ms-transform', [[1, 33, undefined]]],
+            ['property-value', 'translate3d(0,0,0)', [[1, 47, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - 2 non-adjacent -ms-transform with different values': {
-      'topic': 'div{-ms-transform:translate(0,0);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['-ms-transform'], ['translate(0,0)']],
-          [['-webkit-transform'], ['translate3d(0,0,0)']],
-          [['-ms-transform'], ['translate3d(0,0,0)']]
+      'topic': function () {
+        return _optimize('div{-ms-transform:translate(0,0);-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', '-ms-transform', [[1, 4, undefined]]],
+            ['property-value', 'translate(0,0)', [[1, 18, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '-webkit-transform', [[1, 33, undefined]]],
+            ['property-value', 'translate3d(0,0,0)', [[1, 51, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '-ms-transform', [[1, 70, undefined]]],
+            ['property-value', 'translate3d(0,0,0)', [[1, 84, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - 2 adjacent transform with different values': {
-      'topic': 'div{transform:translate(0,0);transform:translate3d(0,0,0)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['transform'], ['translate(0,0)']],
-          [['transform'], ['translate3d(0,0,0)']]
+      'topic': function () {
+        return _optimize('div{transform:translate(0,0);transform:translate3d(0,0,0)}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'transform', [[1, 4, undefined]]],
+            ['property-value', 'translate(0,0)', [[1, 14, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'transform', [[1, 29, undefined]]],
+            ['property-value', 'translate3d(0,0,0)', [[1, 39, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - 2 non-adjacent transform with different values': {
-      'topic': 'div{transform:translate(0,0);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['transform'], ['translate(0,0)']],
-          [['-webkit-transform'], ['translate3d(0,0,0)']],
-          [['transform'], ['translate3d(0,0,0)']]
+      'topic': function () {
+        return _optimize('div{transform:translate(0,0);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'transform', [[1, 4, undefined]]],
+            ['property-value', 'translate(0,0)', [[1, 14, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '-webkit-transform', [[1, 29, undefined]]],
+            ['property-value', 'translate3d(0,0,0)', [[1, 47, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'transform', [[1, 66, undefined]]],
+            ['property-value', 'translate3d(0,0,0)', [[1, 76, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - border(hex) with border(rgba)': {
-      'topic': 'a{border:1px solid #fff;border:1px solid rgba(1,0,0,.5)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['border'], ['1px'], ['solid'], ['#fff']],
-          [['border'], ['1px'], ['solid'], ['rgba(1,0,0,.5)']]
+      'topic': function () {
+        return _optimize('a{border:1px solid #fff;border:1px solid rgba(1,0,0,.5)}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'solid', [[1, 13, undefined]]],
+            ['property-value', '#fff', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border', [[1, 24, undefined]]],
+            ['property-value', '1px', [[1, 31, undefined]]],
+            ['property-value', 'solid', [[1, 35, undefined]]],
+            ['property-value', 'rgba(1,0,0,.5)', [[1, 41, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - border(hex) with border(rgba !important)': {
-      'topic': 'a{border:1px solid #fff;border:1px solid rgba(1,0,0,.5)!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['border'], ['1px'], ['solid'], ['#fff']],
-          [['border'], ['1px'], ['solid'], ['rgba(1,0,0,.5)!important']]
+      'topic': function () {
+        return _optimize('a{border:1px solid #fff;border:1px solid rgba(1,0,0,.5)!important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'solid', [[1, 13, undefined]]],
+            ['property-value', '#fff', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border', [[1, 24, undefined]]],
+            ['property-value', '1px', [[1, 31, undefined]]],
+            ['property-value', 'solid', [[1, 35, undefined]]],
+            ['property-value', 'rgba(1,0,0,.5)!important', [[1, 41, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - border(hex !important) with border(hex)': {
-      'topic': 'a{border:1px solid #fff!important;display:block;border:1px solid #fff}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['border'], ['1px'], ['solid'], ['#fff!important']],
-          [['display'], ['block']]
+      'topic': function () {
+        return _optimize('a{border:1px solid #fff!important;display:block;border:1px solid #fff}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'solid', [[1, 13, undefined]]],
+            ['property-value', '#fff!important', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 34, undefined]]],
+            ['property-value', 'block', [[1, 42, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - border(hex) with border(hex !important)': {
-      'topic': 'a{border:1px solid #fff;display:block;border:1px solid #fff!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['display'], ['block']],
-          [['border'], ['1px'], ['solid'], ['#fff!important']]
+      'topic': function () {
+        return _optimize('a{border:1px solid #fff;display:block;border:1px solid #fff!important}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 24, undefined]]],
+            ['property-value', 'block', [[1, 32, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border', [[1, 38, undefined]]],
+            ['property-value', '1px', [[1, 45, undefined]]],
+            ['property-value', 'solid', [[1, 49, undefined]]],
+            ['property-value', '#fff!important', [[1, 55, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - unit with function with unit without one': {
-      'topic': 'a{border-top-width:calc(100%);display:block;border-top-width:1px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['display'], ['block']],
-          [['border-top-width'], ['1px']]
+      'topic': function () {
+        return _optimize('a{border-top-width:calc(100%);display:block;border-top-width:1px}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'display', [[1, 30, undefined]]],
+            ['property-value', 'block', [[1, 38, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-top-width', [[1, 44, undefined]]],
+            ['property-value', '1px', [[1, 61, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - unit without function with unit with one': {
-      'topic': 'a{border-top-width:1px;display:block;border-top-width:calc(100%)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['border-top-width'], ['1px']],
-          [['display'], ['block']],
-          [['border-top-width'], ['calc(100%)']]
+      'topic': function () {
+        return _optimize('a{border-top-width:1px;display:block;border-top-width:calc(100%)}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-top-width', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 23, undefined]]],
+            ['property-value', 'block', [[1, 31, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-top-width', [[1, 37, undefined]]],
+            ['property-value', 'calc(100%)', [[1, 54, undefined]]]
+          ]
         ]);
       }
     },
     'understandable - non adjacent units': {
-      'topic': 'a{margin-top:100px;padding-top:30px;margin-top:10vmin}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true), [
-          [['padding-top'], ['30px']],
-          [['margin-top'], ['10vmin']]
+      'topic': function () {
+        return _optimize('a{margin-top:80px;padding-top:30px;margin-top:10vmin}', false, true);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding-top', [[1, 18, undefined]]],
+            ['property-value', '30px', [[1, 30, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'margin-top', [[1, 35, undefined]]],
+            ['property-value', '10vmin', [[1, 46, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'understandable - non adjacent units in IE8 mode': {
-      'topic': 'a{margin-top:80px;padding-top:30px;margin-top:10vmin}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, false, true, 'ie8'), [
-          [['margin-top'], ['80px']],
-          [['padding-top'], ['30px']],
-          [['margin-top'], ['10vmin']]
+      'topic': function () {
+        return _optimize('a{margin-top:80px;padding-top:30px;margin-top:10vmin}', false, true, 'ie8');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin-top', [[1, 2, undefined]]],
+            ['property-value', '80px', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-top', [[1, 18, undefined]]],
+            ['property-value', '30px', [[1, 30, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'margin-top', [[1, 35, undefined]]],
+            ['property-value', '10vmin', [[1, 46, undefined]]]
+          ]
         ]);
       }
     }
index d9bafae..d75d9d0 100644 (file)
@@ -4,25 +4,23 @@ var assert = require('assert');
 var optimize = require('../../lib/properties/optimizer');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
-var SourceTracker = require('../../lib/utils/source-tracker');
-var Compatibility = require('../../lib/utils/compatibility');
+var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
-function _optimize(source, compatibility, aggressiveMerging) {
+function _optimize(source, compat, aggressiveMerging) {
   var tokens = tokenize(source, {
     options: {},
-    sourceTracker: new SourceTracker(),
     warnings: []
   });
-  compatibility = new Compatibility(compatibility).toOptions();
+  compat = compatibility(compat);
 
-  var validator = new Validator(compatibility);
+  var validator = new Validator(compat);
   var options = {
     aggressiveMerging: undefined === aggressiveMerging ? true : aggressiveMerging,
-    compatibility: compatibility,
+    compatibility: compat,
     shorthandCompacting: true
   };
-  optimize(tokens[0][1], tokens[0][2], false, true, options, { validator: validator });
+  optimize(tokens[0][1], tokens[0][2], false, true, { options: options, validator: validator });
 
   return tokens[0][2];
 }
@@ -30,726 +28,1529 @@ function _optimize(source, compatibility, aggressiveMerging) {
 vows.describe(optimize)
   .addBatch({
     'longhand then longhand - background colors as functions': {
-      'topic': 'p{background-color:-ms-linear-gradient(top,red,#000);background-color:linear-gradient(top,red,#000)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-color'], ['-ms-linear-gradient(top,red,#000)']],
-          [['background-color'], ['linear-gradient(top,red,#000)']]
+      'topic': function () {
+        return _optimize('p{background-color:-ms-linear-gradient(top,red,#000);background-color:linear-gradient(top,red,#000)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-color', [[1, 2, undefined]]],
+            ['property-value', '-ms-linear-gradient(top,red,#000)', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-color', [[1, 53, undefined]]],
+            ['property-value', 'linear-gradient(top,red,#000)', [[1, 70, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then longhand - background position as function': {
-      'topic': 'p{background-position:-moz-calc(100% - 1em) 0;background-position:calc(100% - 1em) 0}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-position'], ['-moz-calc(100% - 1em)'], ['0']],
-          [['background-position'], ['calc(100% - 1em)'], ['0']]
+      'topic': function () {
+        return _optimize('p{background-position:-moz-calc(100% - 1em) 0;background-position:calc(100% - 1em) 0}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-position', [[1, 2, undefined]]],
+            ['property-value', '-moz-calc(100% - 1em)', [[1, 22, undefined]]],
+            ['property-value', '0', [[1, 44, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-position', [[1, 46, undefined]]],
+            ['property-value', 'calc(100% - 1em)', [[1, 66, undefined]]],
+            ['property-value', '0', [[1, 83, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then longhand - background position as same function': {
-      'topic': 'p{background-position:calc(100% - 1em) 0;background-position:calc(100% - 1em) 1em}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-position'], ['calc(100% - 1em)'], ['1em']]
+      'topic': function () {
+        return _optimize('p{background-position:calc(100% - 1em) 0;background-position:calc(100% - 1em) 1em}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-position', [[1, 41, undefined]]],
+            ['property-value', 'calc(100% - 1em)', [[1, 61, undefined]]],
+            ['property-value', '1em', [[1, 78, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then longhand - background position as function by value': {
-      'topic': 'p{background-position:calc(100% - 1em) 0;background-position:1em 1em}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-position'], ['1em'], ['1em']]
+      'topic': function () {
+        return _optimize('p{background-position:calc(100% - 1em) 0;background-position:1em 1em}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-position', [[1, 41, undefined]]],
+            ['property-value', '1em', [[1, 61, undefined]]],
+            ['property-value', '1em', [[1, 65, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then longhand - background position as value by function': {
-      'topic': 'p{background-position:1em 0;background-position:calc(100% - 1em) 1em}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-position'], ['1em'], ['0']],
-          [['background-position'], ['calc(100% - 1em)'], ['1em']]
+      'topic': function () {
+        return _optimize('p{background-position:1em 0;background-position:calc(100% - 1em) 1em}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-position', [[1, 2, undefined]]],
+            ['property-value', '1em', [[1, 22, undefined]]],
+            ['property-value', '0', [[1, 26, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-position', [[1, 28, undefined]]],
+            ['property-value', 'calc(100% - 1em)', [[1, 48, undefined]]],
+            ['property-value', '1em', [[1, 65, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then longhand - background size as function': {
-      'topic': 'p{background-size:-moz-calc(100% - 1em) 0;background-size:calc(100% - 1em) 0}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-size'], ['-moz-calc(100% - 1em)'], ['0']],
-          [['background-size'], ['calc(100% - 1em)'], ['0']]
+      'topic': function () {
+        return _optimize('p{background-size:-moz-calc(100% - 1em) 0;background-size:calc(100% - 1em) 0}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-size', [[1, 2, undefined]]],
+            ['property-value', '-moz-calc(100% - 1em)', [[1, 18, undefined]]],
+            ['property-value', '0', [[1, 40, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-size', [[1, 42, undefined]]],
+            ['property-value', 'calc(100% - 1em)', [[1, 58, undefined]]],
+            ['property-value', '0', [[1, 75, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand': {
-      'topic': 'p{background-image:none;background:__ESCAPED_URL_CLEAN_CSS0__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+      'topic': function () {
+        return _optimize('p{background-image:none;background:url(image.png)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 24, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 35, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand - important then non-important': {
-      'topic': 'p{background-image:none!important;background:__ESCAPED_URL_CLEAN_CSS0__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-image'], ['none!important']],
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+      'topic': function () {
+        return _optimize('p{background-image:none!important;background:url(image.png)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 2, undefined]]],
+            ['property-value', 'none!important', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 34, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 45, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand - with vendor prefixed function': {
-      'topic': 'p{background-color:red;background:-ms-linear-gradient(top,red,#000)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-color'], ['red']],
-          [['background'], ['-ms-linear-gradient(top,red,#000)']],
+      'topic': function () {
+        return _optimize('p{background-color:red;background:-ms-linear-gradient(top,red,#000)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-color', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 23, undefined]]],
+            ['property-value', '-ms-linear-gradient(top,red,#000)', [[1, 34, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand - with same vendor prefixed function': {
-      'topic': 'p{background-image:-ms-linear-gradient(bottom,black,white);background:-ms-linear-gradient(top,red,#000)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['-ms-linear-gradient(top,red,#000)']],
+      'topic': function () {
+        return _optimize('p{background-image:-ms-linear-gradient(bottom,black,white);background:-ms-linear-gradient(top,red,#000)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 59, undefined]]],
+            ['property-value', '-ms-linear-gradient(top,red,#000)', [[1, 70, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand - with different vendor prefixed function': {
-      'topic': 'p{background-image:linear-gradient(bottom,black,white);background:-ms-linear-gradient(top,red,#000)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-image'], ['linear-gradient(bottom,black,white)']],
-          [['background'], ['-ms-linear-gradient(top,red,#000)']],
+      'topic': function () {
+        return _optimize('p{background-image:linear-gradient(bottom,black,white);background:-ms-linear-gradient(top,red,#000)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 2, undefined]]],
+            ['property-value', 'linear-gradient(bottom,black,white)', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 55, undefined]]],
+            ['property-value', '-ms-linear-gradient(top,red,#000)', [[1, 66, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand - with unprefixed function': {
-      'topic': 'p{background-color:red;background:linear-gradient(red,blue)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-color'], ['red']],
-          [['background'], ['linear-gradient(red,blue)']]
+      'topic': function () {
+        return _optimize('p{background-color:red;background:linear-gradient(red,blue)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-color', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 23, undefined]]],
+            ['property-value', 'linear-gradient(red,blue)', [[1, 34, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ repeat;background-repeat:no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png) repeat;background-repeat:no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]],
+            ['property-value', 'no-repeat', [[1, 53, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - important then non-important': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ repeat-x!important;background-repeat:no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['repeat-x!important']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png) repeat-x!important;background-repeat:no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]],
+            ['property-value', 'repeat-x!important', [[1, 28, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - non-important then important': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ repeat;background-repeat:no-repeat!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background-repeat'], ['no-repeat!important']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png) repeat;background-repeat:no-repeat!important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 35, undefined]]],
+            ['property-value', 'no-repeat!important', [[1, 53, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - disabled background size merging': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-size:50%}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { properties: { backgroundSizeMerging: false } }), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background-size'], ['50%']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background-size:50%}', { properties: { backgroundSizeMerging: false } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-size', [[1, 28, undefined]]],
+            ['property-value', '50%', [[1, 44, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - disabled background clip merging': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-clip:padding-box}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { properties: { backgroundClipMerging: false } }), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background-clip'], ['padding-box']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background-clip:padding-box}', { properties: { backgroundClipMerging: false } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-clip', [[1, 28, undefined]]],
+            ['property-value', 'padding-box', [[1, 44, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - enabled background clip merging': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-clip:padding-box}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { properties: { backgroundClipMerging: true } }), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['padding-box']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background-clip:padding-box}', { properties: { backgroundClipMerging: true } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]],
+            ['property-value', 'padding-box']
+          ]
         ]);
       }
     },
     'shorthand then longhand - disabled background origin merging': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-origin:border-box}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { properties: { backgroundOriginMerging: false } }), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background-origin'], ['border-box']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background-origin:border-box}', { properties: { backgroundOriginMerging: false } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-origin', [[1, 28, undefined]]],
+            ['property-value', 'border-box', [[1, 46, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - enabled background origin merging': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-origin:border-box}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { properties: { backgroundOriginMerging: true } }), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['border-box']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background-origin:border-box}', { properties: { backgroundOriginMerging: true } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]],
+            ['property-value', 'border-box', [[1, 46, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - non mergeable value': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-color:none}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background-color'], ['none']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background-color:none}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-color', [[1, 28, undefined]]],
+            ['property-value', 'none', [[1, 45, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then multiplex longhand - non mergeable value': {
-      'topic': 'p{background:#fff;background-image:__ESCAPED_URL_CLEAN_CSS0__,linear-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['#fff']],
-          [['background-image'], ['__ESCAPED_URL_CLEAN_CSS0__'], [','], ['linear-gradient()']]
+      'topic': function () {
+        return _optimize('p{background:#fff;background-image:url(image.png),linear-gradient()}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', '#fff', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 18, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 35, undefined]]],
+            ['property-value', ',', [[1, 49, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 50, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - border with rgba() and color opacity on': {
-      'topic': 'p{border:solid rgba(0,0,0,0);border-color:transparent}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { colors: { opacity: true } }), [
-          [['border'], ['solid'], ['transparent']]
+      'topic': function () {
+        return _optimize('p{border:solid rgba(0,0,0,0);border-color:transparent}', { colors: { opacity: true } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', 'solid', [[1, 9, undefined]]],
+            ['property-value', 'transparent', [[1, 42, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - border with rgba() and color opacity off': {
-      'topic': 'p{border:solid rgba(0,0,0,0);border-color:transparent}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { colors: { opacity: false } }), [
-          [['border'], ['solid'], ['rgba(0,0,0,0)']],
-          [['border-color'], ['transparent']]
+      'topic': function () {
+        return _optimize('p{border:solid rgba(0,0,0,0);border-color:transparent}', { colors: { opacity: false } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', 'solid', [[1, 9, undefined]]],
+            ['property-value', 'rgba(0,0,0,0)', [[1, 15, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-color', [[1, 29, undefined]]],
+            ['property-value', 'transparent', [[1, 42, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - color into a color - with merging off': {
-      'topic': 'p{background:white;background-color:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { properties: { merging: false } }), [
-          [['background'], ['red']]
+      'topic': function () {
+        return _optimize('p{background:white;background-color:red}', { properties: { merging: false } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 36, undefined]]]
+          ],
         ]);
       }
     },
     'shorthand then longhand - color into a function - with merging off': {
-      'topic': 'p{background:linear-gradient();background-color:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, { properties: { merging: false } }), [
-          [['background'], ['linear-gradient()']],
-          [['background-color'], ['red']]
+      'topic': function () {
+        return _optimize('p{background:linear-gradient();background-color:red}', { properties: { merging: false } });
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-color', [[1, 31, undefined]]],
+            ['property-value', 'red', [[1, 48, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - two shorthands - pending #527': {
-      'topic': 'p{background:-webkit-linear-gradient();background:linear-gradient();background-repeat:repeat-x}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['-webkit-linear-gradient()']],
-          [['background'], ['linear-gradient()']],
-          [['background-repeat'], ['repeat-x']]
+      'topic': function () {
+        return _optimize('p{background:-webkit-linear-gradient();background:linear-gradient();background-repeat:repeat-x}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', '-webkit-linear-gradient()', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 39, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 50, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 68, undefined]]],
+            ['property-value', 'repeat-x', [[1, 86, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - two shorthands and default - pending #527': {
-      'topic': 'p{background:-webkit-linear-gradient();background:linear-gradient();background-repeat:repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['-webkit-linear-gradient()']],
-          [['background'], ['linear-gradient()']],
-          [['background-repeat'], ['repeat']]
+      'topic': function () {
+        return _optimize('p{background:-webkit-linear-gradient();background:linear-gradient();background-repeat:repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', '-webkit-linear-gradient()', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 39, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 50, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 68, undefined]]],
+            ['property-value', 'repeat', [[1, 86, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - two mergeable shorthands and default - pending #527': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background:__ESCAPED_URL_CLEAN_CSS1__;background-repeat:repeat-x}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS1__']],
-          [['background-repeat'], ['repeat-x']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background:url(image.jpg);background-repeat:repeat-x}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.jpg)', [[1, 39, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 54, undefined]]],
+            ['property-value', 'repeat-x', [[1, 72, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - non-function into a function': {
-      'topic': 'p{background:linear-gradient();background-color:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['linear-gradient()']],
-          [['background-color'], ['red']]
+      'topic': function () {
+        return _optimize('p{background:linear-gradient();background-color:red}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-color', [[1, 31, undefined]]],
+            ['property-value', 'red', [[1, 48, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand - function into a non-function': {
-      'topic': 'p{background:repeat-x;background-image:-webkit-linear-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['repeat-x']],
-          [['background-image'], ['-webkit-linear-gradient()']]
+      'topic': function () {
+        return _optimize('p{background:repeat-x;background-image:-webkit-linear-gradient()}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'repeat-x', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 22, undefined]]],
+            ['property-value', '-webkit-linear-gradient()', [[1, 39, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then shorthand - same values': {
-      'topic': 'p{background:red;background:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['red']]
+      'topic': function () {
+        return _optimize('p{background:red;background:red}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 28, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then shorthand - same values with defaults': {
-      'topic': 'p{background:repeat red;background:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['red']]
+      'topic': function () {
+        return _optimize('p{background:repeat red;background:red}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 35, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then shorthand - with different functions': {
-      'topic': 'p{background:linear-gradient();background:-webkit-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['linear-gradient()']],
-          [['background'], ['-webkit-gradient()']]
+      'topic': function () {
+        return _optimize('p{background:linear-gradient();background:-webkit-gradient()}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 31, undefined]]],
+            ['property-value', '-webkit-gradient()', [[1, 42, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then shorthand - with function then url': {
-      'topic': 'p{background:linear-gradient();background:__ESCAPED_URL_CLEAN_CSS0__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+      'topic': function () {
+        return _optimize('p{background:linear-gradient();background:url(image.png)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 42, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then shorthand - with url then function': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background:linear-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background'], ['linear-gradient()']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background:linear-gradient()}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 28, undefined]]],
+            ['property-value', 'linear-gradient()', [[1, 39, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then shorthand - important then non-important': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ no-repeat!important;background:__ESCAPED_URL_CLEAN_CSS1__ repeat red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat!important']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png) no-repeat!important;background:url(image.jpg) repeat red}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]],
+            ['property-value', 'no-repeat!important', [[1, 28, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then shorthand - non-important then important': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__ no-repeat;background:__ESCAPED_URL_CLEAN_CSS1__ repeat red!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS1__'], ['red!important']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png) no-repeat;background:url(image.jpg) repeat red!important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 38, undefined]]],
+            ['property-value', 'url(image.jpg)', [[1, 49, undefined]]],
+            ['property-value', 'red!important', [[1, 71, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then shorthand - same value and latter important': {
-      'topic': 'a{margin:0;margin:0 !important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['margin'], ['0!important']]
+      'topic': function () {
+        return _optimize('a{margin:0;margin:0 !important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin', [[1, 11, undefined]]],
+            ['property-value', '0!important', [[1, 18, undefined]]]
+          ]
         ]);
       }
     },
     'with aggressive off': {
-      'topic': 'a{background:white;color:red;background:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic, null, false), [
-          [['background'], ['red']],
-          [['color'], ['red']]
+      'topic': function () {
+        return _optimize('a{background:white;color:red;background:red}', undefined, false);
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 40, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 19, undefined]]],
+            ['property-value', 'red', [[1, 25, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'border': {
-      'topic': 'a{border:1px solid red;border-style:dotted}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border'], ['1px'], ['dotted'], ['red']]
+      'topic': function () {
+        return _optimize('a{border:1px solid red;border-style:dotted}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'dotted', [[1, 36, undefined]]],
+            ['property-value', 'red', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'border - multivalue righthand': {
-      'topic': 'a{border:1px solid red;border-style:dotted solid}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border'], ['1px'], ['solid'], ['red']],
-          [['border-style'], ['dotted'], ['solid']]
+      'topic': function () {
+        return _optimize('a{border:1px solid red;border-style:dotted solid}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'solid', [[1, 13, undefined]]],
+            ['property-value', 'red', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-style', [[1, 23, undefined]]],
+            ['property-value', 'dotted', [[1, 36, undefined]]],
+            ['property-value', 'solid', [[1, 43, undefined]]]
+          ]
         ]);
       }
     },
     'border - important righthand': {
-      'topic': 'a{border:1px solid red;border-style:dotted!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border'], ['1px'], ['solid'], ['red']],
-          [['border-style'], ['dotted!important']]
+      'topic': function () {
+        return _optimize('a{border:1px solid red;border-style:dotted!important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'solid', [[1, 13, undefined]]],
+            ['property-value', 'red', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-style', [[1, 23, undefined]]],
+            ['property-value', 'dotted!important', [[1, 36, undefined]]],
+          ]
         ]);
       }
     },
     'border - important lefthand': {
-      'topic': 'a{border:1px solid red!important;border-style:dotted}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border'], ['1px'], ['solid'], ['red!important']]
+      'topic': function () {
+        return _optimize('a{border:1px solid red!important;border-style:dotted}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'solid', [[1, 13, undefined]]],
+            ['property-value', 'red!important', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'border - both important': {
-      'topic': 'a{border:1px solid red!important;border-style:dotted!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border'], ['1px'], ['dotted'], ['red!important']]
+      'topic': function () {
+        return _optimize('a{border:1px solid red!important;border-style:dotted!important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'dotted', [[1, 46, undefined]]],
+            ['property-value', 'red!important', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'border - hex and rgb colors': {
-      'topic': 'a{border:1px solid #000;border-color:rgba(255,0,0,.5)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border'], ['1px'], ['solid'], ['#000']],
-          [['border-color'], ['rgba(255,0,0,.5)']]
+      'topic': function () {
+        return _optimize('a{border:1px solid #000;border-color:rgba(255,0,0,.5)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border', [[1, 2, undefined]]],
+            ['property-value', '1px', [[1, 9, undefined]]],
+            ['property-value', 'solid', [[1, 13, undefined]]],
+            ['property-value', '#000', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-color', [[1, 24, undefined]]],
+            ['property-value', 'rgba(255,0,0,.5)', [[1, 37, undefined]]],
+          ]
         ]);
       }
     },
     'border-color - hex then rgb': {
-      'topic': 'a{border-color:#000;border-color:rgba(255,0,0,.5)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-color'], ['#000']],
-          [['border-color'], ['rgba(255,0,0,.5)']]
+      'topic': function () {
+        return _optimize('a{border-color:#000;border-color:rgba(255,0,0,.5)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-color', [[1, 2, undefined]]],
+            ['property-value', '#000', [[1, 15, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-color', [[1, 20, undefined]]],
+            ['property-value', 'rgba(255,0,0,.5)', [[1, 33, undefined]]],
+          ]
         ]);
       }
     },
     'border-color - rgb then hex': {
-      'topic': 'a{border-color:rgba(255,0,0,.5);border-color:#000}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-color'], ['#000']]
+      'topic': function () {
+        return _optimize('a{border-color:rgba(255,0,0,.5);border-color:#000}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-color', [[1, 2, undefined]]],
+            ['property-value', '#000', [[1, 45, undefined]]]
+          ]
         ]);
       }
     },
     'border-color - hex then rgb with multiple values': {
-      'topic': 'a{border-color:red;border-color:#000 rgba(255,0,0,.5)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-color'], ['red']],
-          [['border-color'], ['#000'], ['rgba(255,0,0,.5)']]
+      'topic': function () {
+        return _optimize('a{border-color:red;border-color:#000 rgba(255,0,0,.5)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-color', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 15, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-color', [[1, 19, undefined]]],
+            ['property-value', '#000', [[1, 32, undefined]]],
+            ['property-value', 'rgba(255,0,0,.5)', [[1, 37, undefined]]],
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'border radius': {
-      'topic': 'a{-moz-border-radius:2px;-moz-border-top-left-radius:3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['-moz-border-radius'], ['3px'], ['2px'], ['2px']]
+      'topic': function () {
+        return _optimize('a{-moz-border-radius:2px;-moz-border-top-left-radius:3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', '-moz-border-radius', [[1, 2, undefined]]],
+            ['property-value', '3px', [[1, 53, undefined]]],
+            ['property-value', '2px', [[1, 21, undefined]]],
+            ['property-value', '2px', [[1, 21, undefined]]]
+          ]
         ]);
       }
     },
     'border radius prefixed and unprefixed': {
-      'topic': 'a{-moz-border-radius:2px;border-top-left-radius:3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['-moz-border-radius'], ['2px']],
-          [['border-top-left-radius'], ['3px']]
+      'topic': function () {
+        return _optimize('a{-moz-border-radius:2px;border-top-left-radius:3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', '-moz-border-radius', [[1, 2, undefined]]],
+            ['property-value', '2px', [[1, 21, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'border-top-left-radius', [[1, 25, undefined]]],
+            ['property-value', '3px', [[1, 48, undefined]]]
+          ]
         ]);
       }
     },
     'border width': {
-      'topic': 'a{border-width:2px 3px 2px 1px;border-left-width:3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-width'], ['2px'], ['3px']]
+      'topic': function () {
+        return _optimize('a{border-width:2px 3px 2px 1px;border-left-width:3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-width', [[1, 2, undefined]]],
+            ['property-value', '2px', [[1, 15, undefined]]],
+            ['property-value', '3px', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'list style': {
-      'topic': 'a{list-style:circle inside;list-style-image:__ESCAPED_URL_CLEAN_CSS0__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['list-style'], ['circle'], ['inside'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+      'topic': function () {
+        return _optimize('a{list-style:circle inside;list-style-image:url(image.png)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'list-style', [[1, 2, undefined]]],
+            ['property-value', 'circle', [[1, 13, undefined]]],
+            ['property-value', 'inside', [[1, 20, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 44, undefined]]]
+          ]
         ]);
       }
     },
     'margin': {
-      'topic': 'a{margin:10px 20px;margin-left:25px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['margin'], ['10px'], ['20px'], ['10px'], ['25px']]
+      'topic': function () {
+        return _optimize('a{margin:10px 20px;margin-left:25px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin', [[1, 2, undefined]]],
+            ['property-value', '10px', [[1, 9, undefined]]],
+            ['property-value', '20px', [[1, 14, undefined]]],
+            ['property-value', '10px', [[1, 9, undefined]]],
+            ['property-value', '25px', [[1, 31, undefined]]]
+          ]
         ]);
       }
     },
     'outline': {
-      'topic': 'a{outline:red solid 1px;outline-width:3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['outline'], ['red'], ['solid'], ['3px']]
+      'topic': function () {
+        return _optimize('a{outline:red solid 1px;outline-width:3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'outline', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 10, undefined]]],
+            ['property-value', 'solid', [[1, 14, undefined]]],
+            ['property-value', '3px', [[1, 38, undefined]]]
+          ]
         ]);
       }
     },
     'padding': {
-      'topic': 'a{padding:10px;padding-right:20px;padding-left:20px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding'], ['10px'], ['20px']]
+      'topic': function () {
+        return _optimize('a{padding:10px;padding-right:20px;padding-left:20px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding', [[1, 2, undefined]]],
+            ['property-value', '10px', [[1, 10, undefined]]],
+            ['property-value', '20px', [[1, 29, undefined]]],
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'colors with same understandability': {
-      'topic': 'a{color:red;color:#fff;color:blue}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['blue']]
+      'topic': function () {
+        return _optimize('a{color:red;color:#fff;color:blue}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 23, undefined]]],
+            ['property-value', 'blue', [[1, 29, undefined]]],
+          ]
         ]);
       }
     },
     'colors with different understandability': {
-      'topic': 'a{color:red;color:#fff;color:blue;color:rgba(1,2,3,.4)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['blue']],
-          [['color'], ['rgba(1,2,3,.4)']]
+      'topic': function () {
+        return _optimize('a{color:red;color:#fff;color:blue;color:rgba(1,2,3,.4)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 23, undefined]]],
+            ['property-value', 'blue', [[1, 29, undefined]]],
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 34, undefined]]],
+            ['property-value', 'rgba(1,2,3,.4)', [[1, 40, undefined]]],
+          ]
         ]);
       }
     },
     'colors with different understandability overridden by high understandability': {
-      'topic': 'a{color:red;color:#fff;color:blue;color:rgba(1,2,3,.4);color:red}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['red']]
+      'topic': function () {
+        return _optimize('a{color:red;color:#fff;color:blue;color:rgba(1,2,3,.4);color:red}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 55, undefined]]],
+            ['property-value', 'red', [[1, 61, undefined]]],
+          ]
         ]);
       }
     },
     'colors with different understandability and importance #1': {
-      'topic': 'a{color:#fff!important;color:rgba(1,2,3,.4)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['#fff!important']]
+      'topic': function () {
+        return _optimize('a{color:#fff!important;color:rgba(1,2,3,.4)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', '#fff!important', [[1, 8, undefined]]],
+          ]
         ]);
       }
     },
     'colors with different understandability and importance #2': {
-      'topic': 'a{color:#fff;color:rgba(1,2,3,.4)!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['#fff']],
-          [['color'], ['rgba(1,2,3,.4)!important']]
+      'topic': function () {
+        return _optimize('a{color:#fff;color:rgba(1,2,3,.4)!important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', '#fff', [[1, 8, undefined]]],
+          ],
+          [
+            'property',
+            ['property-name', 'color', [[1, 13, undefined]]],
+            ['property-value', 'rgba(1,2,3,.4)!important', [[1, 19, undefined]]],
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'shorthand then shorthand multiplex': {
-      'topic': 'p{background:url(one.png);background:url(two.png) center 1px,url(three.png) center 2px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['url(one.png)']],
-          [['background'], ['url(two.png)'], ['center'], ['1px'], [','], ['url(three.png)'], ['center'], ['2px']]
+      'topic': function () {
+        return _optimize('p{background:url(one.png);background:url(two.png) center 1px,url(three.png) center 2px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(one.png)', [[1, 13, undefined]]],
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 26, undefined]]],
+            ['property-value', 'url(two.png)', [[1, 37, undefined]]],
+            ['property-value', 'center', [[1, 50, undefined]]],
+            ['property-value', '1px', [[1, 57, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'url(three.png)', [[1, 61, undefined]]],
+            ['property-value', 'center', [[1, 76, undefined]]],
+            ['property-value', '2px', [[1, 83, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand then longhand multiplex': {
-      'topic': 'p{background:top left;background-repeat:no-repeat,no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['top'], ['left'], ['no-repeat'], [','], ['top'], ['left'], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:top left;background-repeat:no-repeat,no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'top', [[1, 13, undefined]]],
+            ['property-value', 'left', [[1, 17, undefined]]],
+            ['property-value', 'no-repeat', [[1, 40, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'top', [[1, 13, undefined]]],
+            ['property-value', 'left', [[1, 17, undefined]]],
+            ['property-value', 'no-repeat', [[1, 50, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand multiplex then longhand': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__,__ESCAPED_URL_CLEAN_CSS1__;background-repeat:no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], [','], ['__ESCAPED_URL_CLEAN_CSS1__'], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png),url(image.jpg);background-repeat:no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]],
+            ['property-value', 'no-repeat', [[1, 61, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'url(image.jpg)', [[1, 28, undefined]]],
+            ['property-value', 'no-repeat', [[1, 61, undefined]]]
+          ]
         ]);
       }
     },
     'longhand then shorthand multiplex': {
-      'topic': 'p{background-repeat:no-repeat;background:__ESCAPED_URL_CLEAN_CSS0__,__ESCAPED_URL_CLEAN_CSS1__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], [','], ['__ESCAPED_URL_CLEAN_CSS1__']]
+      'topic': function () {
+        return _optimize('p{background-repeat:no-repeat;background:url(image.png),url(image.jpg)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 30, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 41, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'url(image.jpg)', [[1, 56, undefined]]],
+          ]
         ]);
       }
     },
     'longhand multiplex then shorthand': {
-      'topic': 'p{background-repeat:no-repeat,no-repeat;background:__ESCAPED_URL_CLEAN_CSS0__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+      'topic': function () {
+        return _optimize('p{background-repeat:no-repeat,no-repeat;background:url(image.png)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 40, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 51, undefined]]],
+          ]
         ]);
       }
     },
     'multiplex longhand into multiplex shorthand': {
-      'topic': 'p{background:no-repeat,no-repeat;background-position:top left,bottom left}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['top'], ['left'], ['no-repeat'], [','], ['bottom'], ['left'], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:no-repeat,no-repeat;background-position:top left,bottom left}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'top', [[1, 53, undefined]]],
+            ['property-value', 'left', [[1, 57, undefined]]],
+            ['property-value', 'no-repeat', [[1, 13, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'bottom', [[1, 62, undefined]]],
+            ['property-value', 'left', [[1, 69, undefined]]],
+            ['property-value', 'no-repeat', [[1, 23, undefined]]]
+          ]
         ]);
       }
     },
     'two multiplex shorthands with vendor specific functions': {
-      'topic': 'p{background:url(1.png),-webkit-linear-gradient();background:url(1.png),linear-gradient()}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['url(1.png)'], [','], ['-webkit-linear-gradient()']],
-          [['background'], ['url(1.png)'], [','], ['linear-gradient()']]
+      'topic': function () {
+        return _optimize('p{background:url(1.png),-webkit-linear-gradient();background:url(1.png),linear-gradient()}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(1.png)', [[1, 13, undefined]]],
+            ['property-value', ','],
+            ['property-value', '-webkit-linear-gradient()', [[1, 24, undefined]]],
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 50, undefined]]],
+            ['property-value', 'url(1.png)', [[1, 61, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'linear-gradient()', [[1, 72, undefined]]],
+          ]
         ]);
       }
     },
     'not too long into multiplex #1': {
-      'topic': 'p{background:top left;background-repeat:no-repeat,no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['top'], ['left'], ['no-repeat'], [','], ['top'], ['left'], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:top left;background-repeat:no-repeat,no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'top', [[1, 13, undefined]]],
+            ['property-value', 'left', [[1, 17, undefined]]],
+            ['property-value', 'no-repeat', [[1, 40, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'top', [[1, 13, undefined]]],
+            ['property-value', 'left', [[1, 17, undefined]]],
+            ['property-value', 'no-repeat', [[1, 50, undefined]]]
+          ]
         ]);
       }
     },
     'not too long into multiplex #2': {
-      'topic': 'p{background:repeat content-box;background-repeat:no-repeat,no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['no-repeat'], ['content-box'], [','], ['no-repeat'], ['content-box']]
+      'topic': function () {
+        return _optimize('p{background:repeat content-box;background-repeat:no-repeat,no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'no-repeat', [[1, 50, undefined]]],
+            ['property-value', 'content-box', [[1, 20, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'no-repeat', [[1, 60, undefined]]],
+            ['property-value', 'content-box', [[1, 20, undefined]]]
+          ]
         ]);
       }
     },
     'not too long into multiplex - twice': {
-      'topic': 'p{background:top left;background-repeat:no-repeat,no-repeat;background-image:__ESCAPED_URL_CLEAN_CSS0__,__ESCAPED_URL_CLEAN_CSS1__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['top'], ['left'], ['no-repeat'], [','], ['__ESCAPED_URL_CLEAN_CSS1__'], ['top'], ['left'], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:top left;background-repeat:no-repeat,no-repeat;background-image:url(image.png),url(image.jpg)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 77, undefined]]],
+            ['property-value', 'top', [[1, 13, undefined]]],
+            ['property-value', 'left', [[1, 17, undefined]]],
+            ['property-value', 'no-repeat', [[1, 40, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'url(image.jpg)', [[1, 92, undefined]]],
+            ['property-value', 'top', [[1, 13, undefined]]],
+            ['property-value', 'left', [[1, 17, undefined]]],
+            ['property-value', 'no-repeat', [[1, 50, undefined]]]
+          ]
         ]);
       }
     },
     'not too long into multiplex - over a property': {
-      'topic': 'p{background:top left;background-repeat:no-repeat,no-repeat;background-image:__ESCAPED_URL_CLEAN_CSS0__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['top'], ['left']],
-          [['background-repeat'], ['no-repeat'], [','], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:top left;background-repeat:no-repeat,no-repeat;background-image:url(image.png)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 77, undefined]]],
+            ['property-value', 'top', [[1, 13, undefined]]],
+            ['property-value', 'left', [[1, 17, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 22, undefined]]],
+            ['property-value', 'no-repeat', [[1, 40, undefined]]],
+            ['property-value', ',', [[1, 49, undefined]]],
+            ['property-value', 'no-repeat', [[1, 50, undefined]]]
+          ]
         ]);
       }
     },
     'too long into multiplex #1': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background-repeat:no-repeat,no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background-repeat'], ['no-repeat'], [','], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:url(/long/image/path.png);background-repeat:no-repeat,no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(/long/image/path.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 39, undefined]]],
+            ['property-value', 'no-repeat', [[1, 57, undefined]]],
+            ['property-value', ',', [[1, 66, undefined]]],
+            ['property-value', 'no-repeat', [[1, 67, undefined]]]
+          ]
         ]);
       }
     },
     'too long into multiplex #2': {
-      'topic': 'p{background:content-box padding-box;background-repeat:no-repeat,no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['content-box'], ['padding-box']],
-          [['background-repeat'], ['no-repeat'], [','], ['no-repeat']]
-        ]);
-      }
-    },
-    'too long into multiplex #3': {
-      'topic': 'p{background:top left / 20px 20px;background-repeat:no-repeat,no-repeat}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['top'], ['left'], ['/'], ['20px'], ['20px']],
-          [['background-repeat'], ['no-repeat'], [','], ['no-repeat']]
+      'topic': function () {
+        return _optimize('p{background:content-box padding-box;background-repeat:no-repeat,no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'content-box', [[1, 13, undefined]]],
+            ['property-value', 'padding-box', [[1, 25, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 37, undefined]]],
+            ['property-value', 'no-repeat', [[1, 55, undefined]]],
+            ['property-value', ',', [[1, 64, undefined]]],
+            ['property-value', 'no-repeat', [[1, 65, undefined]]]
+          ]
+        ]);
+      }
+    },
+    'too long into multiplex #3 - equal size': {
+      'topic': function () {
+        return _optimize('p{background:top left / 20px 20px;background-repeat:no-repeat,no-repeat}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'top', [[1, 13, undefined]]],
+            ['property-value', 'left', [[1, 17, undefined]]],
+            ['property-value', '/'],
+            ['property-value', '20px', [[1, 24, undefined]]],
+            ['property-value', '20px', [[1, 29, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 34, undefined]]],
+            ['property-value', 'no-repeat', [[1, 52, undefined]]],
+            ['property-value', ',', [[1, 61, undefined]]],
+            ['property-value', 'no-repeat', [[1, 62, undefined]]]
+          ]
         ]);
       }
     },
     'background color into background': {
-      'topic': 'p{background:red;background-repeat:__ESCAPED_URL_CLEAN_CSS0__,__ESCAPED_URL_CLEAN_CSS1__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], [','], ['__ESCAPED_URL_CLEAN_CSS1__'], ['red']],
+      'topic': function () {
+        return _optimize('p{background:red;background-image:url(image.png),url(image.jpg)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 34, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'url(image.jpg)', [[1, 49, undefined]]],
+            ['property-value', 'red', [[1, 13, undefined]]]
+          ]
         ]);
       }
     },
     'background then background - svg hack': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background:__ESCAPED_URL_CLEAN_CSS1__,none}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS1__'], [','], ['none']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background:url(image.svg),none}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 28, undefined]]],
+            ['property-value', 'url(image.svg)', [[1, 39, undefined]]],
+            ['property-value', ','],
+            ['property-value', 'none', [[1, 54, undefined]]]
+          ]
         ]);
       }
     },
     'background then background - inverted svg hack': {
-      'topic': 'p{background:__ESCAPED_URL_CLEAN_CSS0__;background:none,__ESCAPED_URL_CLEAN_CSS1__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background'], ['0 0'], [','], ['__ESCAPED_URL_CLEAN_CSS1__']]
+      'topic': function () {
+        return _optimize('p{background:url(image.png);background:none,url(image.svg)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 13, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background', [[1, 28, undefined]]],
+            ['property-value', '0 0'],
+            ['property-value', ','],
+            ['property-value', 'url(image.svg)', [[1, 44, undefined]]]
+          ]
         ]);
       }
     },
     'background-image then background-image - svg hack': {
-      'topic': 'p{background-image:__ESCAPED_URL_CLEAN_CSS0__;background-image: __ESCAPED_URL_CLEAN_CSS1__,none}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-image'], ['__ESCAPED_URL_CLEAN_CSS0__']],
-          [['background-image'], ['__ESCAPED_URL_CLEAN_CSS1__'], [','], ['none']]
+      'topic': function () {
+        return _optimize('p{background-image:url(image.png);background-image:url(image.svg),none}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 2, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 34, undefined]]],
+            ['property-value', 'url(image.svg)', [[1, 51, undefined]]],
+            ['property-value', ',', [[1, 65, undefined]]],
+            ['property-value', 'none', [[1, 66, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'padding !important then not !important': {
-      'topic': 'a{padding:0!important;padding-left:3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding'], ['0!important']]
+      'topic': function () {
+        return _optimize('a{padding:0!important;padding-left:3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding', [[1, 2, undefined]]],
+            ['property-value', '0!important', [[1, 10, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'overriding !important by a star hack': {
-      'topic': 'a{color:red!important;display:block;*color:#fff}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['red!important']],
-          [['display'], ['block']],
-          [['*color'], ['#fff']]
+      'topic': function () {
+        return _optimize('a{color:red!important;display:block;*color:#fff}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', 'red!important', [[1, 8, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 22, undefined]]],
+            ['property-value', 'block', [[1, 30, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '*color', [[1, 36, undefined]]],
+            ['property-value', '#fff', [[1, 43, undefined]]]
+          ]
         ]);
       }
     },
     'overriding !important by an underscore hack': {
-      'topic': 'a{color:red!important;display:block;_color:#fff}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['red!important']],
-          [['display'], ['block']],
-          [['_color'], ['#fff']]
+      'topic': function () {
+        return _optimize('a{color:red!important;display:block;_color:#fff}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', 'red!important', [[1, 8, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 22, undefined]]],
+            ['property-value', 'block', [[1, 30, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '_color', [[1, 36, undefined]]],
+            ['property-value', '#fff', [[1, 43, undefined]]]
+          ]
         ]);
       }
     },
     'overriding !important by an backslash hack': {
-      'topic': 'a{color:red!important;display:block;color:#fff\\0}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['red!important']],
-          [['display'], ['block']]
+      'topic': function () {
+        return _optimize('a{color:red!important;display:block;color:#fff\\0}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', 'red!important', [[1, 8, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'display', [[1, 22, undefined]]],
+            ['property-value', 'block', [[1, 30, undefined]]]
+          ]
         ]);
       }
     }
index dc47369..0e4e649 100644 (file)
@@ -8,7 +8,16 @@ vows.describe(populateComponents)
   .addBatch({
     'shorthand': {
       'topic': function () {
-        var wrapped = wrapForOptimizing([[['margin'], ['0px'], ['1px'], ['2px'], ['3px']]]);
+        var wrapped = wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0px'],
+            ['property-value', '1px'],
+            ['property-value', '2px'],
+            ['property-value', '3px']
+          ]
+        ]);
 
         populateComponents(wrapped);
         return wrapped;
@@ -27,24 +36,30 @@ vows.describe(populateComponents)
       },
       'gets a margin-top': function (wrapped) {
         assert.deepEqual(wrapped[0].components[0].name, 'margin-top');
-        assert.deepEqual(wrapped[0].components[0].value, [['0px']]);
+        assert.deepEqual(wrapped[0].components[0].value, [['property-value', '0px']]);
       },
       'gets a margin-right': function (wrapped) {
         assert.deepEqual(wrapped[0].components[1].name, 'margin-right');
-        assert.deepEqual(wrapped[0].components[1].value, [['1px']]);
+        assert.deepEqual(wrapped[0].components[1].value, [['property-value', '1px']]);
       },
       'gets a margin-bottom': function (wrapped) {
         assert.deepEqual(wrapped[0].components[2].name, 'margin-bottom');
-        assert.deepEqual(wrapped[0].components[2].value, [['2px']]);
+        assert.deepEqual(wrapped[0].components[2].value, [['property-value', '2px']]);
       },
       'gets a margin-left': function (wrapped) {
         assert.deepEqual(wrapped[0].components[3].name, 'margin-left');
-        assert.deepEqual(wrapped[0].components[3].value, [['3px']]);
+        assert.deepEqual(wrapped[0].components[3].value, [['property-value', '3px']]);
       }
     },
     'longhand': {
       'topic': function () {
-        var wrapped = wrapForOptimizing([[['margin-top'], ['0px']]]);
+        var wrapped = wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin-top'],
+            ['property-value', '0px']
+          ]
+        ]);
 
         populateComponents(wrapped);
         return wrapped;
@@ -58,7 +73,12 @@ vows.describe(populateComponents)
     },
     'no value': {
       'topic': function () {
-        var wrapped = wrapForOptimizing([[['margin']]]);
+        var wrapped = wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin']
+          ]
+        ]);
 
         populateComponents(wrapped);
         return wrapped;
index 82ab47c..ec59385 100644 (file)
@@ -9,8 +9,16 @@ vows.describe(removeUnused)
     'it removes unused only': {
       'topic': function () {
         var properties = [
-          [['background'], ['none']],
-          [['color'], ['red']]
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'none']
+          ],
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red']
+          ]
         ];
         var _properties = wrapForOptimizing(properties);
         _properties[0].unused = true;
@@ -20,15 +28,26 @@ vows.describe(removeUnused)
       },
       'it has one property left': function (properties) {
         assert.lengthOf(properties, 1);
-        assert.equal(properties[0][0], 'color');
+        assert.equal(properties[0][1][1], 'color');
       }
     },
     'it respects comments': {
       'topic': function () {
         var properties = [
-          [['background'], ['none']],
-          '__ESCAPED_COMMENT_CLEAN_CSS0__',
-          [['color'], ['red']]
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'none']
+          ],
+          [
+            'comment',
+            ['/* comment */']
+          ],
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red']
+          ]
         ];
         var _properties = wrapForOptimizing(properties);
         _properties[1].unused = true;
@@ -38,8 +57,8 @@ vows.describe(removeUnused)
       },
       'it has one property left': function (properties) {
         assert.lengthOf(properties, 2);
-        assert.equal(properties[0][0], 'background');
-        assert.equal(properties[1], '__ESCAPED_COMMENT_CLEAN_CSS0__');
+        assert.equal(properties[0][1][1], 'background');
+        assert.equal(properties[1][1][0], '/* comment */');
       }
     }
   })
index 19bb268..36b0463 100644 (file)
@@ -7,163 +7,298 @@ var shallowClone = require('../../lib/properties/clone').shallow;
 
 var restoreFromOptimizing = require('../../lib/properties/restore-from-optimizing');
 
-var Compatibility = require('../../lib/utils/compatibility');
+var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
-var validator = new Validator(new Compatibility().toOptions());
+var validator = new Validator(compatibility());
 
 vows.describe(restoreFromOptimizing)
   .addBatch({
     'without descriptor': {
       'topic': function () {
-        var properties = [[['margin-top'], ['0']]];
-        var _properties = wrapForOptimizing(properties);
-        restoreFromOptimizing(_properties);
+        var properties = [
+          [
+            'property',
+            ['property-name', 'margin-top'],
+            ['property-value', '0']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
+        restoreFromOptimizing(wrapped);
 
         return properties;
       },
       'is same as source': function (properties) {
-        assert.deepEqual(properties, [[['margin-top'], ['0']]]);
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin-top'],
+            ['property-value', '0']
+          ]
+        ]);
       }
     },
     'with changed value but without descriptor': {
       'topic': function () {
-        var properties = [[['margin-top'], ['0']]];
-        var _properties = wrapForOptimizing(properties);
-        _properties[0].value = [['1px']];
-        _properties[0].dirty = true;
-        restoreFromOptimizing(_properties);
+        var properties = [
+          [
+            'property',
+            ['property-name', 'margin-top'],
+            ['property-value', '0']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
+        wrapped[0].value = [['property-value', '1px']];
+        wrapped[0].dirty = true;
+        restoreFromOptimizing(wrapped);
 
         return properties;
       },
       'has right output': function (properties) {
-        assert.deepEqual(properties, [[['margin-top'], ['1px']]]);
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin-top'],
+            ['property-value', '1px']
+          ]
+        ]);
       }
     },
-    'longhands': {
+    'with comment': {
       'topic': function () {
-        var properties = ['/*comment */', [['margin-top'], ['0']]];
-        var _properties = wrapForOptimizing(properties);
-        populateComponents(_properties, validator);
-        restoreFromOptimizing(_properties);
+        var properties = [
+          [
+            'comment',
+            '/* comment */'
+          ],
+          [
+            'property',
+            ['property-name', 'margin-top'],
+            ['property-value', '0']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
+        populateComponents(wrapped, validator);
+        restoreFromOptimizing(wrapped);
 
         return properties;
       },
       'is same as source': function (properties) {
-        assert.deepEqual(properties, ['/*comment */', [['margin-top'], ['0']]]);
+        assert.deepEqual(properties, [
+          [
+            'comment',
+            '/* comment */'
+          ],
+          [
+            'property',
+            ['property-name', 'margin-top'],
+            ['property-value', '0']
+          ]
+        ]);
       }
     },
     'shorthands': {
       'topic': function () {
-        var properties = ['/*comment */', [['background'], ['url(image.png)']]];
-        var _properties = wrapForOptimizing(properties);
-        populateComponents(_properties, validator);
+        var properties = [
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'url(image.png)']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
+        populateComponents(wrapped, validator);
 
-        properties[1].pop();
-        _properties[0].dirty = true;
+        wrapped[0].dirty = true;
 
-        restoreFromOptimizing(_properties);
+        restoreFromOptimizing(wrapped);
         return properties;
       },
       'is same as source': function (properties) {
-        assert.deepEqual(properties, ['/*comment */', [['background'], ['url(image.png)']]]);
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'url(image.png)']
+          ]
+        ]);
       }
     },
     'shorthands in simple mode': {
       'topic': function () {
-        var properties = [[['margin'], ['1px'], ['2px']]];
-        var _properties = wrapForOptimizing(properties);
+        var properties = [
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '1px'],
+            ['property-value', '2px']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
 
-        _properties[0].dirty = true;
+        wrapped[0].dirty = true;
 
-        restoreFromOptimizing(_properties, true);
+        restoreFromOptimizing(wrapped, true);
         return properties;
       },
       'is same as source': function (properties) {
-        assert.deepEqual(properties, [[['margin'], ['1px'], ['2px']]]);
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '1px'],
+            ['property-value', '2px']
+          ]
+        ]);
       }
     },
     'values': {
       'topic': function () {
-        var properties = [[['background'], ['url(image.png)']]];
-        var _properties = wrapForOptimizing(properties);
-        populateComponents(_properties, validator);
+        var properties = [
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'url(image.png)']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
+        populateComponents(wrapped, validator);
 
-        _properties[0].value = [];
-        _properties[0].dirty = true;
+        wrapped[0].value = [];
+        wrapped[0].dirty = true;
 
-        restoreFromOptimizing(_properties);
-        return _properties;
+        restoreFromOptimizing(wrapped);
+        return properties;
       },
-      'updates value': function (_properties) {
-        assert.deepEqual(_properties[0].value, [['url(image.png)']]);
+      'updates value': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'url(image.png)']
+          ]
+        ]);
       }
     },
     'in cloned without reference to `all`': {
       'topic': function () {
-        var properties = [[['background'], ['url(image.png)']]];
-        var _properties = wrapForOptimizing(properties);
-        populateComponents(_properties, validator);
+        var properties = [
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'url(image.png)']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
+        populateComponents(wrapped, validator);
 
-        var cloned = shallowClone(_properties[0]);
-        cloned.components = _properties[0].components;
+        var cloned = shallowClone(wrapped[0]);
+        cloned.components = wrapped[0].components;
         cloned.dirty = true;
 
         restoreFromOptimizing([cloned]);
         return cloned;
       },
       'does not fail': function (cloned) {
-        assert.deepEqual(cloned.value, [['url(image.png)']]);
+        assert.deepEqual(cloned.value, [['property-value', 'url(image.png)']]);
       }
     }
   })
   .addBatch({
     'important': {
       'topic': function () {
-        var properties = [[['color'], ['red!important']]];
-        var _properties = wrapForOptimizing(properties);
+        var properties = [
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red!important']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
 
-        restoreFromOptimizing(_properties, true);
+        restoreFromOptimizing(wrapped, true);
         return properties;
       },
       'restores important': function (properties) {
-        assert.deepEqual(properties, [[['color'], ['red!important']]]);
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red!important']
+          ]
+        ]);
       }
     },
     'underscore hack': {
       'topic': function () {
-        var properties = [[['_color'], ['red']]];
-        var _properties = wrapForOptimizing(properties);
+        var properties = [
+          [
+            'property',
+            ['property-name', '_color'],
+            ['property-value', 'red']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
 
-        restoreFromOptimizing(_properties, true);
+        restoreFromOptimizing(wrapped, true);
         return properties;
       },
       'restores hack': function (properties) {
-        assert.deepEqual(properties, [[['_color'], ['red']]]);
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', '_color'],
+            ['property-value', 'red']
+          ]
+        ]);
       }
     },
     'star hack': {
       'topic': function () {
-        var properties = [[['*color'], ['red']]];
-        var _properties = wrapForOptimizing(properties);
+        var properties = [
+          [
+            'property',
+            ['property-name', '*color'],
+            ['property-value', 'red']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
 
-        restoreFromOptimizing(_properties, true);
+        restoreFromOptimizing(wrapped, true);
         return properties;
       },
       'restores hack': function (properties) {
-        assert.deepEqual(properties, [[['*color'], ['red']]]);
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', '*color'],
+            ['property-value', 'red']
+          ]
+        ]);
       }
     },
     'suffix hack': {
       'topic': function () {
-        var properties = [[['color'], ['red\\9']]];
-        var _properties = wrapForOptimizing(properties);
+        var properties = [
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red\\9']
+          ]
+        ];
+        var wrapped = wrapForOptimizing(properties);
 
-        restoreFromOptimizing(_properties, true);
+        restoreFromOptimizing(wrapped, true);
         return properties;
       },
       'restores hack': function (properties) {
-        assert.deepEqual(properties, [[['color'], ['red\\9']]]);
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red\\9']
+          ]
+        ]);
       }
     }
   })
index 38adf33..bf2e8c1 100644 (file)
@@ -3,14 +3,14 @@ var assert = require('assert');
 
 var wrapForOptimizing = require('../../lib/properties/wrap-for-optimizing').single;
 var compactable = require('../../lib/properties/compactable');
-var Compatibility = require('../../lib/utils/compatibility');
+var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
 var restore = require('../../lib/properties/restore');
 
 function _breakUp(property) {
-  var validator = new Validator(new Compatibility().toOptions());
-  var descriptor = compactable[property[0][0]];
+  var validator = new Validator(compatibility());
+  var descriptor = compactable[property[1][1]];
   var _property = wrapForOptimizing(property);
   _property.components = descriptor.breakUp(_property, compactable, validator);
   _property.multiplex = _property.components[0].multiplex;
@@ -27,314 +27,807 @@ vows.describe(restore)
     'background': {
       'background with some values': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'no-repeat'],
+              ['property-value', 'padding-box']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)'],
+            ['property-value', 'no-repeat'],
+            ['property-value', 'padding-box']
+          ]);
         }
       },
       'background with some default values': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['repeat'], ['padding-box'], ['border-box']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'repeat'],
+              ['property-value', 'padding-box'],
+              ['property-value', 'border-box']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)']
+          ]);
         }
       },
       'background with all default values': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['transparent'], ['none'], ['repeat'], ['scroll'], ['0'], ['0'], ['padding-box'], ['border-box']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'transparent'],
+              ['property-value', 'none'],
+              ['property-value', 'repeat'],
+              ['property-value', 'scroll'],
+              ['property-value', '0'],
+              ['property-value', '0'],
+              ['property-value', 'padding-box'],
+              ['property-value', 'border-box']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0 0']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0 0']
+          ]);
         }
       },
       'background with some double values': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['repeat'], ['no-repeat'], ['2px'], ['3px'], ['/'], ['auto'], ['padding-box']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['2px'], ['3px'], ['repeat'], ['no-repeat'], ['padding-box']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'repeat'],
+              ['property-value', 'no-repeat'],
+              ['property-value', '2px'],
+              ['property-value', '3px'],
+              ['property-value', '/'],
+              ['property-value', 'auto'],
+              ['property-value', 'padding-box']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)'],
+            ['property-value', '2px'],
+            ['property-value', '3px'],
+            ['property-value', 'repeat'],
+            ['property-value', 'no-repeat'],
+            ['property-value', 'padding-box']
+          ]);
         }
       },
       'background with default background origin and background clip': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['padding-box'], ['border-box']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'padding-box'],
+              ['property-value', 'border-box']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)']
+          ]);
         }
       },
       'background with same background origin and background clip': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['padding-box'], ['padding-box']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'padding-box'],
+              ['property-value', 'padding-box']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['padding-box']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)'],
+            ['property-value', 'padding-box']
+          ]);
         }
       },
       'background with default background position and background size': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['0'], ['/'], ['50%'], ['25%']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['0'], ['/'], ['50%'], ['25%']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', '0'],
+              ['property-value', '0'],
+              ['property-value', '/'],
+              ['property-value', '50%'],
+              ['property-value', '25%']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)'],
+            ['property-value', '0'],
+            ['property-value', '0'],
+            ['property-value', '/'],
+            ['property-value', '50%'],
+            ['property-value', '25%']
+          ]);
         }
       },
       'background with default background position and single background size': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['0'], ['/'], ['50%']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', '0'],
+              ['property-value', '0'],
+              ['property-value', '/'],
+              ['property-value', '50%']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['0'], ['/'], ['50%']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)'],
+            ['property-value', '0'],
+            ['property-value', '0'],
+            ['property-value', '/'],
+            ['property-value', '50%']
+          ]);
         }
       },
       'background with default background position and background size differing by 2nd value': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['50px'], ['/'], ['0'], ['30px']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['0'], ['50px'], ['/'], ['0'], ['30px']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', '0'],
+              ['property-value', '50px'],
+              ['property-value', '/'],
+              ['property-value', '0'],
+              ['property-value', '30px']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)'],
+            ['property-value', '0'],
+            ['property-value', '50px'],
+            ['property-value', '/'],
+            ['property-value', '0'],
+            ['property-value', '30px']
+          ]);
         }
       },
       'background 0 to background 0': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['0']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', '0']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0']
+          ]);
         }
       },
       'background color in multiplex': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['blue'], [','], ['__ESCAPED_URL_CLEAN_CSS1__'], ['red']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], [','], ['__ESCAPED_URL_CLEAN_CSS1__'], ['red']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'blue'],
+              ['property-value', ','],
+              ['property-value', 'url(image.jpg)'],
+              ['property-value', 'red']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)'],
+            ['property-value', ','],
+            ['property-value', 'url(image.jpg)'],
+            ['property-value', 'red']
+          ]);
         }
       }
     },
     'border radius': {
       '4 values': {
         'topic': function () {
-          return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['3px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px'],
+            ['property-value', '2px'],
+            ['property-value', '3px']
+          ]);
         }
       },
       '3 values': {
         'topic': function () {
-          return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['1px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '1px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px'],
+            ['property-value', '2px']
+          ]);
         }
       },
       '2 values': {
         'topic': function () {
-          return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['0px'], ['1px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '0px'],
+              ['property-value', '1px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px']
+          ]);
         }
       },
       '1 value': {
         'topic': function () {
-          return _restore(_breakUp([['border-radius'], ['0px'], ['0px'], ['0px'], ['0px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '0px'],
+              ['property-value', '0px'],
+              ['property-value', '0px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px']
+          ]);
         }
       },
       'horizontal + vertical - different values': {
         'topic': function () {
-          return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['2px'], ['1px'], ['2px'], ['1px']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['2px'], ['1px']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px'],
+              ['property-value', '/'],
+              ['property-value', '2px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '1px']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px'],
+            ['property-value', '2px'],
+            ['property-value', '3px'],
+            ['property-value', '/'],
+            ['property-value', '2px'],
+            ['property-value', '1px']
+          ]);
         }
       },
       'horizontal + vertical - same values': {
         'topic': function () {
-          return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['0px'], ['1px'], ['2px'], ['3px']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px'],
+              ['property-value', '/'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px'],
+            ['property-value', '2px'],
+            ['property-value', '3px']
+          ]);
         }
       },
       'horizontal + vertical - asymetrical': {
         'topic': function () {
-          return _restore(_breakUp([['border-radius'], ['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['0px'], ['1px']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px'], ['/'], ['0px'], ['1px']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px'],
+              ['property-value', '/'],
+              ['property-value', '0px'],
+              ['property-value', '1px']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px'],
+            ['property-value', '2px'],
+            ['property-value', '3px'],
+            ['property-value', '/'],
+            ['property-value', '0px'],
+            ['property-value', '1px']
+          ]);
         }
       },
       'inherit': {
         'topic': function () {
-          return _restore(_breakUp([['border-radius'], ['inherit']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border-radius'],
+              ['property-value', 'inherit']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['inherit']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'inherit']
+          ]);
         }
       }
     },
     'four values': {
       '4 different': {
         'topic': function () {
-          return _restore(_breakUp([['padding'], ['0px'], ['1px'], ['2px'], ['3px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'padding'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '3px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px'], ['3px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px'],
+            ['property-value', '2px'],
+            ['property-value', '3px']
+          ]);
         }
       },
       '3 different': {
         'topic': function () {
-          return _restore(_breakUp([['padding'], ['0px'], ['1px'], ['2px'], ['1px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'padding'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '2px'],
+              ['property-value', '1px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px'], ['2px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px'],
+            ['property-value', '2px']
+          ]);
         }
       },
       '2 different': {
         'topic': function () {
-          return _restore(_breakUp([['padding'], ['0px'], ['1px'], ['0px'], ['1px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'padding'],
+              ['property-value', '0px'],
+              ['property-value', '1px'],
+              ['property-value', '0px'],
+              ['property-value', '1px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px'], ['1px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px'],
+            ['property-value', '1px']
+          ]);
         }
       },
       'all same': {
         'topic': function () {
-          return _restore(_breakUp([['padding'], ['0px'], ['0px'], ['0px'], ['0px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'padding'],
+              ['property-value', '0px'],
+              ['property-value', '0px'],
+              ['property-value', '0px'],
+              ['property-value', '0px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0px']
+          ]);
         }
       },
       'inherit': {
         'topic': function () {
-          return _restore(_breakUp([['padding'], ['inherit']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'padding'],
+              ['property-value', 'inherit']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['inherit']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'inherit']
+          ]);
         }
       }
     },
     'repeated values': {
       'background with some values': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box'], [','], ['repeat'], ['red']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['padding-box'], [','], ['red']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'url(image.png)'],
+              ['property-value', 'no-repeat'],
+              ['property-value', 'padding-box'],
+              ['property-value', ','],
+              ['property-value', 'repeat'],
+              ['property-value', 'red']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'url(image.png)'],
+            ['property-value', 'no-repeat'],
+            ['property-value', 'padding-box'],
+            ['property-value', ','],
+            ['property-value', 'red']
+          ]);
         }
       },
       'background with background origin and size': {
         'topic': function () {
-          return _restore(_breakUp([['background'], ['no-repeat'], ['padding-box'], [','], ['repeat'], ['10px'], ['10px'], ['/'], ['auto'], ['red'], [','], ['top'], ['left'], ['/'], ['30%']]));
-        },
-        'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['no-repeat'], ['padding-box'], [','], ['10px'], ['10px'], [','], ['top'], ['left'], ['/'], ['30%']]);
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'background'],
+              ['property-value', 'no-repeat'],
+              ['property-value', 'padding-box'],
+              ['property-value', ','],
+              ['property-value', 'repeat'],
+              ['property-value', '10px'],
+              ['property-value', '10px'],
+              ['property-value', '/'],
+              ['property-value', 'auto'],
+              ['property-value', 'red'],
+              ['property-value', ','],
+              ['property-value', 'top'],
+              ['property-value', 'left'],
+              ['property-value', '/'],
+              ['property-value', '30%']
+            ])
+          );
+        },
+        'gives right value back': function (restoredValue) {
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'no-repeat'],
+            ['property-value', 'padding-box'],
+            ['property-value', ','],
+            ['property-value', '10px'],
+            ['property-value', '10px'],
+            ['property-value', ','],
+            ['property-value', 'top'],
+            ['property-value', 'left'],
+            ['property-value', '/'],
+            ['property-value', '30%']
+          ]);
         }
       }
     },
     'without defaults': {
       'border with some values': {
         'topic': function () {
-          return _restore(_breakUp([['border'], ['solid']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border'],
+              ['property-value', 'solid']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['solid']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'solid']
+          ]);
         }
       },
       'border with all values': {
         'topic': function () {
-          return _restore(_breakUp([['border'], ['1px'], ['solid'], ['red']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border'],
+              ['property-value', '1px'],
+              ['property-value', 'solid'],
+              ['property-value', 'red']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['1px'], ['solid'], ['red']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '1px'],
+            ['property-value', 'solid'],
+            ['property-value', 'red']
+          ]);
         }
       },
       'border with all defaults': {
         'topic': function () {
-          return _restore(_breakUp([['border'], ['medium'], ['none'], ['none']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border'],
+              ['property-value', 'medium'],
+              ['property-value', 'none'],
+              ['property-value', 'none']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['none']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'none']
+          ]);
         }
       },
       'border with inherit': {
         'topic': function () {
-          return _restore(_breakUp([['border'], ['inherit']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'border'],
+              ['property-value', 'inherit']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['inherit']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'inherit']
+          ]);
         }
       },
       'list with some values': {
         'topic': function () {
-          return _restore(_breakUp([['list-style'], ['circle']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'list-style'],
+              ['property-value', 'circle']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['circle']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'circle']
+          ]);
         }
       },
       'list with all values': {
         'topic': function () {
-          return _restore(_breakUp([['list-style'], ['circle'], ['inside'], ['__ESCAPED_URL_CLEAN_CSS1__']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'list-style'],
+              ['property-value', 'circle'],
+              ['property-value', 'inside'],
+              ['property-value', 'url(image.png)']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['circle'], ['inside'], ['__ESCAPED_URL_CLEAN_CSS1__']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'circle'],
+            ['property-value', 'inside'],
+            ['property-value', 'url(image.png)']
+          ]);
         }
       },
       'list with some defaults': {
         'topic': function () {
-          return _restore(_breakUp([['list-style'], ['circle'], ['outside'], ['__ESCAPED_URL_CLEAN_CSS0__']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'list-style'],
+              ['property-value', 'circle'],
+              ['property-value', 'outside'],
+              ['property-value', 'url(image.png)']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['circle'], ['__ESCAPED_URL_CLEAN_CSS0__']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'circle'],
+            ['property-value', 'url(image.png)']
+          ]);
         }
       },
       'list with inherit': {
         'topic': function () {
-          return _restore(_breakUp([['list-style'], ['inherit']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'list-style'],
+              ['property-value', 'inherit']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['inherit']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'inherit']
+          ]);
         }
       },
       'outline with some values': {
         'topic': function () {
-          return _restore(_breakUp([['outline'], ['dotted']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', 'dotted']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['dotted']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'dotted']
+          ]);
         }
       },
       'outline with all values': {
         'topic': function () {
-          return _restore(_breakUp([['outline'], ['#fff'], ['dotted'], ['1px']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', '#fff'],
+              ['property-value', 'dotted'],
+              ['property-value', '1px']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['#fff'], ['dotted'], ['1px']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '#fff'],
+            ['property-value', 'dotted'],
+            ['property-value', '1px']
+          ]);
         }
       },
       'outline with all defaults': {
         'topic': function () {
-          return _restore(_breakUp([['outline'], ['invert'], ['none'], ['medium']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', 'invert'],
+              ['property-value', 'none'],
+              ['property-value', 'medium']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['0']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', '0']
+          ]);
         }
       },
       'outline with inherit': {
         'topic': function () {
-          return _restore(_breakUp([['outline'], ['inherit']]));
+          return _restore(
+            _breakUp([
+              'property',
+              ['property-name', 'outline'],
+              ['property-value', 'inherit']
+            ])
+          );
         },
         'gives right value back': function (restoredValue) {
-          assert.deepEqual(restoredValue, [['inherit']]);
+          assert.deepEqual(restoredValue, [
+            ['property-value', 'inherit']
+          ]);
         }
       }
     }
diff --git a/test/properties/shorthand-compacting-source-maps-test.js b/test/properties/shorthand-compacting-source-maps-test.js
deleted file mode 100644 (file)
index b32306d..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-var vows = require('vows');
-var assert = require('assert');
-
-var optimize = require('../../lib/properties/optimizer');
-
-var tokenize = require('../../lib/tokenizer/tokenize');
-var SourceTracker = require('../../lib/utils/source-tracker');
-var SourceReader = require('../../lib/utils/source-reader');
-var InputSourceMapTracker = require('../../lib/utils/input-source-map-tracker');
-
-var Compatibility = require('../../lib/utils/compatibility');
-var Validator = require('../../lib/properties/validator');
-
-function _optimize(source) {
-  var inputSourceMapTracker = new InputSourceMapTracker({
-    options: { inliner: {} },
-    errors: {},
-    sourceTracker: new SourceTracker()
-  });
-  var tokens = tokenize(source, {
-    options: { sourceMap: true },
-    inputSourceMapTracker: inputSourceMapTracker,
-    sourceReader: new SourceReader(),
-    sourceTracker: new SourceTracker(),
-    warnings: []
-  });
-
-  var compatibility = new Compatibility().toOptions();
-  var validator = new Validator(compatibility);
-  var options = {
-    aggressiveMerging: true,
-    compatibility: compatibility,
-    sourceMap: true,
-    shorthandCompacting: true
-  };
-  optimize(tokens[0][1], tokens[0][2], false, true, options, { validator: validator });
-
-  return tokens[0][2];
-}
-
-vows.describe(optimize)
-  .addBatch({
-    'with source map': {
-      topic: 'a{margin-top:10px;margin-bottom:4px;margin-left:5px;margin-right:5px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [
-            ['margin', [[1, 2, undefined], [1, 18, undefined], [1, 36, undefined], [1, 52, undefined]]],
-            [ '10px', [[1, 13, undefined]]],
-            [ '5px', [[1, 65, undefined]]],
-            [ '4px', [[1, 32, undefined]]]
-          ]
-        ]);
-      }
-    }
-  })
-  .export(module);
index f66af73..172e20a 100644 (file)
@@ -4,25 +4,23 @@ var assert = require('assert');
 var optimize = require('../../lib/properties/optimizer');
 
 var tokenize = require('../../lib/tokenizer/tokenize');
-var SourceTracker = require('../../lib/utils/source-tracker');
-var Compatibility = require('../../lib/utils/compatibility');
+var compatibility = require('../../lib/utils/compatibility');
 var Validator = require('../../lib/properties/validator');
 
 function _optimize(source) {
   var tokens = tokenize(source, {
     options: {},
-    sourceTracker: new SourceTracker(),
     warnings: []
   });
 
-  var compatibility = new Compatibility(compatibility).toOptions();
-  var validator = new Validator(compatibility);
+  var compat = compatibility(compat);
+  var validator = new Validator(compat);
   var options = {
     aggressiveMerging: true,
-    compatibility: compatibility,
+    compatibility: compat,
     shorthandCompacting: true
   };
-  optimize(tokens[0][1], tokens[0][2], false, true, options, { validator: validator });
+  optimize(tokens[0][1], tokens[0][2], false, true, { options: options, validator: validator });
 
   return tokens[0][2];
 }
@@ -30,222 +28,503 @@ function _optimize(source) {
 vows.describe(optimize)
   .addBatch({
     'shorthand background #1': {
-      'topic': 'p{background-color:#111;background-image:__ESCAPED_URL_CLEAN_CSS0__;background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['#111']]
+      'topic': function () {
+        return _optimize('p{background-color:#111;background-image:url(image.png);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'url(image.png)', [[1, 41, undefined]]],
+            ['property-value', '#111', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand background #2': {
-      'topic': 'p{background-color:#111;background-image:__ESCAPED_URL_CLEAN_CSS0__;background-repeat:no-repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['no-repeat'], ['#111']]
+      'topic': function () {
+        return _optimize('p{background-color:#111;background-image:url(image.png);background-repeat:no-repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'url(image.png)', [[1, 41, undefined]]],
+            ['property-value', 'no-repeat', [[1, 74, undefined]]],
+            ['property-value', '#111', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand important background': {
-      'topic': 'p{background-color:#111!important;background-image:__ESCAPED_URL_CLEAN_CSS0__!important;background-repeat:repeat!important;background-position:0 0!important;background-attachment:scroll!important;background-size:auto!important;background-origin:padding-box!important;background-clip:border-box!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['__ESCAPED_URL_CLEAN_CSS0__'], ['#111!important']]
+      'topic': function () {
+        return _optimize('p{background-color:#111!important;background-image:url(image.png)!important;background-repeat:repeat!important;background-position:0 0!important;background-attachment:scroll!important;background-size:auto!important;background-origin:padding-box!important;background-clip:border-box!important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background'],
+            ['property-value', 'url(image.png)', [[1, 51, undefined]]],
+            ['property-value', '#111!important', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand border-width': {
-      'topic': 'p{border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-width'], ['7px'], ['4px']]
+      'topic': function () {
+        return _optimize('p{border-top-width:7px;border-bottom-width:7px;border-left-width:4px;border-right-width:4px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-width'],
+            ['property-value', '7px', [[1, 19, undefined]]],
+            ['property-value', '4px', [[1, 88, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand border-color #1': {
-      'topic': 'p{border-top-color:#9fce00;border-bottom-color:#9fce00;border-left-color:#9fce00;border-right-color:#9fce00}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-color'], ['#9fce00']]
+      'topic': function () {
+        return _optimize('p{border-top-color:#9fce00;border-bottom-color:#9fce00;border-left-color:#9fce00;border-right-color:#9fce00}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-color'],
+            ['property-value', '#9fce00', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand border-color #2': {
-      'topic': 'p{border-right-color:#002;border-bottom-color:#003;border-top-color:#001;border-left-color:#004}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-color'], ['#001'], ['#002'], ['#003'], ['#004']]
+      'topic': function () {
+        return _optimize('p{border-right-color:#002;border-bottom-color:#003;border-top-color:#001;border-left-color:#004}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-color'],
+            ['property-value', '#001', [[1, 68, undefined]]],
+            ['property-value', '#002', [[1, 21, undefined]]],
+            ['property-value', '#003', [[1, 46, undefined]]],
+            ['property-value', '#004', [[1, 91, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand border-radius': {
-      'topic': 'p{border-top-left-radius:7px;border-bottom-right-radius:6px;border-bottom-left-radius:5px;border-top-right-radius:3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-radius'], ['7px'], ['3px'], ['6px'], ['5px']]
+      'topic': function () {
+        return _optimize('p{border-top-left-radius:7px;border-bottom-right-radius:6px;border-bottom-left-radius:5px;border-top-right-radius:3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-radius'],
+            ['property-value', '7px', [[1, 25, undefined]]],
+            ['property-value', '3px', [[1, 114, undefined]]],
+            ['property-value', '6px', [[1, 56, undefined]]],
+            ['property-value', '5px', [[1, 86, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand multiplexed border-radius': {
-      'topic': 'p{border-radius:7px/3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-radius'], ['7px'], ['/'], ['3px']]
+      'topic': function () {
+        return _optimize('p{border-radius:7px/3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-radius', [[1, 2, undefined]]],
+            ['property-value', '7px', [[1, 16, undefined]]],
+            ['property-value', '/'],
+            ['property-value', '3px', [[1, 20, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand asymmetric border-radius with same values': {
-      'topic': 'p{border-top-left-radius:7px 3px;border-top-right-radius:7px 3px;border-bottom-right-radius:7px 3px;border-bottom-left-radius:7px 3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-radius'], ['7px'], ['/'], ['3px']]
+      'topic': function () {
+        return _optimize('p{border-top-left-radius:7px 3px;border-top-right-radius:7px 3px;border-bottom-right-radius:7px 3px;border-bottom-left-radius:7px 3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-radius'],
+            ['property-value', '7px', [[1, 25, undefined]]],
+            ['property-value', '/'],
+            ['property-value', '3px', [[1, 29, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand asymmetric border-radius': {
-      'topic': 'p{border-top-left-radius:7px 3px;border-top-right-radius:6px 2px;border-bottom-right-radius:5px 1px;border-bottom-left-radius:4px 0}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-radius'], ['7px'], ['6px'], ['5px'], ['4px'], ['/'], ['3px'], ['2px'], ['1px'], ['0']]
+      'topic': function () {
+        return _optimize('p{border-top-left-radius:7px 3px;border-top-right-radius:6px 2px;border-bottom-right-radius:5px 1px;border-bottom-left-radius:4px 0}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-radius'],
+            ['property-value', '7px', [[1, 25, undefined]]],
+            ['property-value', '6px', [[1, 57, undefined]]],
+            ['property-value', '5px', [[1, 92, undefined]]],
+            ['property-value', '4px', [[1, 126, undefined]]],
+            ['property-value', '/'],
+            ['property-value', '3px', [[1, 29, undefined]]],
+            ['property-value', '2px', [[1, 61, undefined]]],
+            ['property-value', '1px', [[1, 96, undefined]]],
+            ['property-value', '0', [[1, 130, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand multiple !important': {
-      'topic': 'a{border-color:#123 !important;border-top-color: #456 !important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['border-color'], ['#456'], ['#123'], ['#123!important']]
+      'topic': function () {
+        return _optimize('a{border-color:#123 !important;border-top-color: #456 !important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'border-color', [[1, 2, undefined]]],
+            ['property-value', '#456', [[1, 49, undefined]]],
+            ['property-value', '#123', [[1, 15, undefined]]],
+            ['property-value', '#123!important', [[1, 15, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand list-style #1': {
-      'topic': 'a{list-style-type:circle;list-style-position:outside;list-style-image:__ESCAPED_URL_CLEAN_CSS0__}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['list-style'], ['circle'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+      'topic': function () {
+        return _optimize('a{list-style-type:circle;list-style-position:outside;list-style-image:url(image.png)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'list-style'],
+            ['property-value', 'circle', [[1, 18, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 70, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand list-style #2': {
-      'topic': 'a{list-style-image:__ESCAPED_URL_CLEAN_CSS0__;list-style-type:circle;list-style-position:inside}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['list-style'], ['circle'], ['inside'], ['__ESCAPED_URL_CLEAN_CSS0__']]
+      'topic': function () {
+        return _optimize('a{list-style-image:url(image.png);list-style-type:circle;list-style-position:inside}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'list-style'],
+            ['property-value', 'circle', [[1, 50, undefined]]],
+            ['property-value', 'inside', [[1, 77, undefined]]],
+            ['property-value', 'url(image.png)', [[1, 19, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand margin': {
-      'topic': 'a{margin-top:10px;margin-right:5px;margin-bottom:3px;margin-left:2px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['margin'], ['10px'], ['5px'], ['3px'], ['2px']]
+      'topic': function () {
+        return _optimize('a{margin-top:10px;margin-right:5px;margin-bottom:3px;margin-left:2px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '10px', [[1, 13, undefined]]],
+            ['property-value', '5px', [[1, 31, undefined]]],
+            ['property-value', '3px', [[1, 49, undefined]]],
+            ['property-value', '2px', [[1, 65, undefined]]]
+          ]
         ]);
       }
     },
     'shorthand padding': {
-      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:2px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding'], ['10px'], ['2px'], ['3px'], ['5px']]
+      'topic': function () {
+        return _optimize('a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:2px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding'],
+            ['property-value', '10px', [[1, 14, undefined]]],
+            ['property-value', '2px', [[1, 69, undefined]]],
+            ['property-value', '3px', [[1, 51, undefined]]],
+            ['property-value', '5px', [[1, 32, undefined]]]
+          ]
         ]);
       }
     },
     'mixed': {
-      'topic': 'a{padding-top:10px;margin-top:3px;padding-left:5px;margin-left:3px;padding-bottom:3px;margin-bottom:3px;padding-right:2px;margin-right:3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding'], ['10px'], ['2px'], ['3px'], ['5px']],
-          [['margin'], ['3px']]
+      'topic': function () {
+        return _optimize('a{padding-top:10px;margin-top:3px;padding-left:5px;margin-left:3px;padding-bottom:3px;margin-bottom:3px;padding-right:2px;margin-right:3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding'],
+            ['property-value', '10px', [[1, 14, undefined]]],
+            ['property-value', '2px', [[1, 118, undefined]]],
+            ['property-value', '3px', [[1, 82, undefined]]],
+            ['property-value', '5px', [[1, 47, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '3px', [[1, 30, undefined]]]
+          ]
         ]);
       }
     },
     'with other properties': {
-      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;color:red;padding-right:2px;width:100px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['color'], ['red']],
-          [['width'], ['100px']],
-          [['padding'], ['10px'], ['2px'], ['3px'], ['5px']]
+      'topic': function () {
+        return _optimize('a{padding-top:10px;padding-left:5px;padding-bottom:3px;color:red;padding-right:2px;width:100px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'color', [[1, 55, undefined]]],
+            ['property-value', 'red', [[1, 61, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'width', [[1, 83, undefined]]],
+            ['property-value', '100px', [[1, 89, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding'],
+            ['property-value', '10px', [[1, 14, undefined]]],
+            ['property-value', '2px', [[1, 79, undefined]]],
+            ['property-value', '3px', [[1, 51, undefined]]],
+            ['property-value', '5px', [[1, 32, undefined]]]
+          ]
         ]);
       }
     },
     'with hacks': {
-      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;_padding-right:2px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding-top'], ['10px']],
-          [['padding-left'], ['5px']],
-          [['padding-bottom'], ['3px']],
-          [['_padding-right'], ['2px']]
+      'topic': function () {
+        return _optimize('a{padding-top:10px;padding-left:5px;padding-bottom:3px;_padding-right:2px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding-top', [[1, 2, undefined]]],
+            ['property-value', '10px', [[1, 14, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-left', [[1, 19, undefined]]],
+            ['property-value', '5px', [[1, 32, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-bottom', [[1, 36, undefined]]],
+            ['property-value', '3px', [[1, 51, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', '_padding-right', [[1, 55, undefined]]],
+            ['property-value', '2px', [[1, 70, undefined]]]
+          ]
         ]);
       }
     },
     'just inherit': {
-      'topic': 'a{background:inherit}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background'], ['inherit']]
+      'topic': function () {
+        return _optimize('a{background:inherit}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background', [[1, 2, undefined]]],
+            ['property-value', 'inherit', [[1, 13, undefined]]]
+          ]
         ]);
       }
     }
   })
   .addBatch({
     'not enough components': {
-      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding-top'], ['10px']],
-          [['padding-left'], ['5px']],
-          [['padding-bottom'], ['3px']]
+      'topic': function () {
+        return _optimize('a{padding-top:10px;padding-left:5px;padding-bottom:3px}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding-top', [[1, 2, undefined]]],
+            ['property-value', '10px', [[1, 14, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-left', [[1, 19, undefined]]],
+            ['property-value', '5px', [[1, 32, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-bottom', [[1, 36, undefined]]],
+            ['property-value', '3px', [[1, 51, undefined]]]
+          ]
         ]);
       }
     },
     'with inherit - pending #525': {
-      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:inherit}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding-top'], ['10px']],
-          [['padding-left'], ['5px']],
-          [['padding-bottom'], ['3px']],
-          [['padding-right'], ['inherit']]
+      'topic': function () {
+        return _optimize('a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:inherit}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding-top', [[1, 2, undefined]]],
+            ['property-value', '10px', [[1, 14, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-left', [[1, 19, undefined]]],
+            ['property-value', '5px', [[1, 32, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-bottom', [[1, 36, undefined]]],
+            ['property-value', '3px', [[1, 51, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-right', [[1, 55, undefined]]],
+            ['property-value', 'inherit', [[1, 69, undefined]]]
+          ]
         ]);
       }
     },
     'mixed importance': {
-      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:2px!important}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding-top'], ['10px']],
-          [['padding-left'], ['5px']],
-          [['padding-bottom'], ['3px']],
-          [['padding-right'], ['2px!important']]
+      'topic': function () {
+        return _optimize('a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:2px!important}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding-top', [[1, 2, undefined]]],
+            ['property-value', '10px', [[1, 14, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-left', [[1, 19, undefined]]],
+            ['property-value', '5px', [[1, 32, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-bottom', [[1, 36, undefined]]],
+            ['property-value', '3px', [[1, 51, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-right', [[1, 55, undefined]]],
+            ['property-value', '2px!important', [[1, 69, undefined]]]
+          ]
         ]);
       }
     },
     'mixed understandability of units': {
-      'topic': 'a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:calc(100% - 20px)}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['padding-top'], ['10px']],
-          [['padding-left'], ['5px']],
-          [['padding-bottom'], ['3px']],
-          [['padding-right'], ['calc(100% - 20px)']]
+      'topic': function () {
+        return _optimize('a{padding-top:10px;padding-left:5px;padding-bottom:3px;padding-right:calc(100% - 20px)}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'padding-top', [[1, 2, undefined]]],
+            ['property-value', '10px', [[1, 14, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-left', [[1, 19, undefined]]],
+            ['property-value', '5px', [[1, 32, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-bottom', [[1, 36, undefined]]],
+            ['property-value', '3px', [[1, 51, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'padding-right', [[1, 55, undefined]]],
+            ['property-value', 'calc(100% - 20px)', [[1, 69, undefined]]]
+          ]
         ]);
       }
     },
     'mixed understandability of images': {
-      'topic': 'p{background-color:#111;background-image:linear-gradient(sth);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}',
-      'into': function (topic) {
-        assert.deepEqual(_optimize(topic), [
-          [['background-color'], ['#111']],
-          [['background-image'], ['linear-gradient(sth)']],
-          [['background-repeat'], ['repeat']],
-          [['background-position'], ['0'], ['0']],
-          [['background-attachment'], ['scroll']],
-          [['background-size'], ['auto']],
-          [['background-origin'], ['padding-box']],
-          [['background-clip'], ['border-box']]
+      'topic': function () {
+        return _optimize('p{background-color:#111;background-image:linear-gradient(sth);background-repeat:repeat;background-position:0 0;background-attachment:scroll;background-size:auto;background-origin:padding-box;background-clip:border-box}');
+      },
+      'into': function (properties) {
+        assert.deepEqual(properties, [
+          [
+            'property',
+            ['property-name', 'background-color', [[1, 2, undefined]]],
+            ['property-value', '#111', [[1, 19, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-image', [[1, 24, undefined]]],
+            ['property-value', 'linear-gradient(sth)', [[1, 41, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-repeat', [[1, 62, undefined]]],
+            ['property-value', 'repeat', [[1, 80, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-position', [[1, 87, undefined]]],
+            ['property-value', '0', [[1, 107, undefined]]],
+            ['property-value', '0', [[1, 109, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-attachment', [[1, 111, undefined]]],
+            ['property-value', 'scroll', [[1, 133, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-size', [[1, 140, undefined]]],
+            ['property-value', 'auto', [[1, 156, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-origin', [[1, 161, undefined]]],
+            ['property-value', 'padding-box', [[1, 179, undefined]]]
+          ],
+          [
+            'property',
+            ['property-name', 'background-clip', [[1, 191, undefined]]],
+            ['property-value', 'border-box', [[1, 207, undefined]]]
+          ]
         ]);
       }
-    }
+  }
   })
   .export(module);
index 8c699d2..22e76a5 100644 (file)
@@ -7,7 +7,14 @@ vows.describe(wrapForOptimizing)
   .addBatch({
     'one': {
       'topic': function () {
-        return wrapForOptimizing([[['margin'], ['0'], ['0']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0'],
+            ['property-value', '0']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
@@ -16,7 +23,7 @@ vows.describe(wrapForOptimizing)
         assert.deepEqual(wrapped[0].name, 'margin');
       },
       'has value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['0'], ['0']]);
+        assert.deepEqual(wrapped[0].value, [['property-value', '0'], ['property-value', '0']]);
       },
       'is not a block': function (wrapped) {
         assert.isFalse(wrapped[0].block);
@@ -45,7 +52,19 @@ vows.describe(wrapForOptimizing)
     },
     'two': {
       'topic': function () {
-        return wrapForOptimizing([[['margin'], ['0'], ['0']], [['color'], ['red']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0'],
+            ['property-value', '0']
+          ],
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red']
+          ]
+        ]);
       },
       'has two wraps': function (wrapped) {
         assert.lengthOf(wrapped, 2);
@@ -53,7 +72,17 @@ vows.describe(wrapForOptimizing)
     },
     'with comments': {
       'topic': function () {
-        return wrapForOptimizing([['/* comment */'], [['color'], ['red']]]);
+        return wrapForOptimizing([
+          [
+            'comment',
+            ['/* comment */']
+          ],
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
@@ -64,7 +93,15 @@ vows.describe(wrapForOptimizing)
     },
     'longhand': {
       'topic': function () {
-        return wrapForOptimizing([[['border-radius-top-left'], ['1px'], ['/'], ['2px']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'border-radius-top-left'],
+            ['property-value', '1px'],
+            ['property-value', '/'],
+            ['property-value', '2px']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
@@ -73,7 +110,7 @@ vows.describe(wrapForOptimizing)
         assert.deepEqual(wrapped[0].name, 'border-radius-top-left');
       },
       'has value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['1px'], ['/'], ['2px']]);
+        assert.deepEqual(wrapped[0].value, [['property-value', '1px'], ['property-value', '/'], ['property-value', '2px']]);
       },
       'is multiplex': function (wrapped) {
         assert.isTrue(wrapped[0].multiplex);
@@ -81,7 +118,13 @@ vows.describe(wrapForOptimizing)
     },
     'variable': {
       'topic': function () {
-        return wrapForOptimizing([[['--color'], ['red']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', '--color'],
+            ['property-value', 'red']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
@@ -89,19 +132,33 @@ vows.describe(wrapForOptimizing)
       'has name': function (wrapped) {
         assert.deepEqual(wrapped[0].name, '--color');
       },
-      'has value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['red']]);
-      },
       'is not a block': function (wrapped) {
         assert.isFalse(wrapped[0].block);
-      },
-      'is variable': function (wrapped) {
-        assert.isTrue(wrapped[0].variable);
       }
     },
     'variable block': {
       'topic': function () {
-        return wrapForOptimizing([[['--color'], [ [['color'], ['red']], [['text-color'], ['red']] ]]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', '--color'],
+            [
+              'property-block',
+              [
+                [
+                  'property',
+                  ['property-name', 'color'],
+                  ['property-value', 'red']
+                ],
+                [
+                  'property',
+                  ['property-name', 'text-color'],
+                  ['property-value', 'red']
+                ]
+              ]
+            ]
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
@@ -110,43 +167,143 @@ vows.describe(wrapForOptimizing)
         assert.deepEqual(wrapped[0].name, '--color');
       },
       'has value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [[ [['color'], ['red']], [['text-color'], ['red']] ]]);
-      },
-      'is not a block': function (wrapped) {
+        assert.deepEqual(wrapped[0].value, [
+          [
+            'property-block',
+            [
+              [
+                'property',
+                ['property-name', 'color'],
+                ['property-value', 'red']
+              ],
+              [
+                'property',
+                ['property-name', 'text-color'],
+                ['property-value', 'red']
+              ]
+            ]
+          ]
+        ]);
+      },
+      'is a block': function (wrapped) {
         assert.isTrue(wrapped[0].block);
-      },
-      'is variable': function (wrapped) {
-        assert.isTrue(wrapped[0].variable);
       }
     },
     'without value': {
       'topic': function () {
-        return wrapForOptimizing([[['margin']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
       },
-      'is unused': function (wrapped) {
-        assert.isTrue(wrapped[0].unused);
+      'has value': function (wrapped) {
+        assert.isUndefined(wrapped.value);
+      },
+      'unused is not set': function (wrapped) {
+        assert.isFalse(wrapped[0].unused);
       }
     },
     'important': {
       'topic': function () {
-        return wrapForOptimizing([[['margin'], ['0!important']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0!important']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
       },
       'has right value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['0']]);
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
       },
-      'is important': function (wrapped) {
+      'has important set': function (wrapped) {
+        assert.isTrue(wrapped[0].important);
+      }
+    },
+    'important with prefix space': {
+      'topic': function () {
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0'],
+            ['property-value', '!important']
+          ]
+        ]);
+      },
+      'has one wrap': function (wrapped) {
+        assert.lengthOf(wrapped, 1);
+      },
+      'has right value': function (wrapped) {
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
+      },
+      'has important set': function (wrapped) {
         assert.isTrue(wrapped[0].important);
       }
     },
+    'important with suffix space': {
+      'topic': function () {
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0!'],
+            ['property-value', 'important']
+          ]
+        ]);
+      },
+      'has one wrap': function (wrapped) {
+        assert.lengthOf(wrapped, 1);
+      },
+      'has right value': function (wrapped) {
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
+      },
+      'has important set': function (wrapped) {
+        assert.isTrue(wrapped[0].important);
+      }
+    },
+    'important with two spaces': {
+      'topic': function () {
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0'],
+            ['property-value', '!'],
+            ['property-value', 'important']
+          ]
+        ]);
+      },
+      'has one wrap': function (wrapped) {
+        assert.lengthOf(wrapped, 1);
+      },
+      'has right value': function (wrapped) {
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
+      },
+      'has important set': function (wrapped) {
+        assert.isTrue(wrapped[0].important);
+      },
+      'is not a bang hack': function (wrapped) {
+        assert.isFalse(wrapped[0].hack);
+      }
+    },
     'underscore hack': {
       'topic': function () {
-        return wrapForOptimizing([[['_color'], ['red']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', '_color'],
+            ['property-value', 'red']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
@@ -160,7 +317,13 @@ vows.describe(wrapForOptimizing)
     },
     'star hack': {
       'topic': function () {
-        return wrapForOptimizing([[['*color'], ['red']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', '*color'],
+            ['property-value', 'red']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
@@ -174,13 +337,19 @@ vows.describe(wrapForOptimizing)
     },
     'backslash hack': {
       'topic': function () {
-        return wrapForOptimizing([[['margin'], ['0\\9']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0\\9']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
       },
       'has right value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['0']]);
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
       },
       'is a hack': function (wrapped) {
         assert.equal(wrapped[0].hack, 'backslash');
@@ -188,13 +357,19 @@ vows.describe(wrapForOptimizing)
     },
     'backslash hack - single value': {
       'topic': function () {
-        return wrapForOptimizing([[['margin'], ['0']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
       },
       'has right value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['0']]);
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
       },
       'is a hack': function (wrapped) {
         assert.isFalse(wrapped[0].hack);
@@ -202,13 +377,20 @@ vows.describe(wrapForOptimizing)
     },
     'backslash hack - space between values': {
       'topic': function () {
-        return wrapForOptimizing([[['margin'], ['0'], ['\\9']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0'],
+            ['property-value', '\\9']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
       },
       'has right value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['0']]);
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
       },
       'is a hack': function (wrapped) {
         assert.equal(wrapped[0].hack, 'backslash');
@@ -216,13 +398,42 @@ vows.describe(wrapForOptimizing)
     },
     'bang hack': {
       'topic': function () {
-        return wrapForOptimizing([[['margin'], ['0'], ['!ie']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0!ie']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
       },
       'has right value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['0']]);
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
+      },
+      'is a hack': function (wrapped) {
+        assert.equal(wrapped[0].hack, 'bang');
+      },
+      'is not important': function (wrapped) {
+        assert.isFalse(wrapped[0].important);
+      }
+    },
+    'bang hack with space': {
+      'topic': function () {
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0 !ie']
+          ]
+        ]);
+      },
+      'has one wrap': function (wrapped) {
+        assert.lengthOf(wrapped, 1);
+      },
+      'has right value': function (wrapped) {
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
       },
       'is a hack': function (wrapped) {
         assert.equal(wrapped[0].hack, 'bang');
@@ -233,13 +444,20 @@ vows.describe(wrapForOptimizing)
     },
     'bang hack - space between values': {
       'topic': function () {
-        return wrapForOptimizing([[['margin'], ['0 !ie']]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'margin'],
+            ['property-value', '0'],
+            ['property-value', '!ie']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
       },
       'has right value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['0']]);
+        assert.deepEqual(wrapped[0].value, [['property-value', '0']]);
       },
       'is a hack': function (wrapped) {
         assert.equal(wrapped[0].hack, 'bang');
@@ -248,15 +466,21 @@ vows.describe(wrapForOptimizing)
         assert.isFalse(wrapped[0].important);
       }
     },
-    'source map': {
+    'two hacks': {
       'topic': function () {
-        return wrapForOptimizing([[['color', 1, 2, undefined], ['red\\9!important', 1, 2, undefined]]]);
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'color'],
+            ['property-value', 'red\\9!important']
+          ]
+        ]);
       },
       'has one wrap': function (wrapped) {
         assert.lengthOf(wrapped, 1);
       },
       'has right value': function (wrapped) {
-        assert.deepEqual(wrapped[0].value, [['red', 1, 2, undefined]]);
+        assert.deepEqual(wrapped[0].value, [['property-value', 'red']]);
       },
       'is important': function (wrapped) {
         assert.isTrue(wrapped[0].important);
@@ -264,6 +488,23 @@ vows.describe(wrapForOptimizing)
       'is a hack': function (wrapped) {
         assert.equal(wrapped[0].hack, 'backslash');
       }
+    },
+    'source map': {
+      'topic': function () {
+        return wrapForOptimizing([
+          [
+            'property',
+            ['property-name', 'color', [[1, 2, undefined]]],
+            ['property-value', 'red', [[1, 2, undefined]]]
+          ]
+        ]);
+      },
+      'has one wrap': function (wrapped) {
+        assert.lengthOf(wrapped, 1);
+      },
+      'has right value': function (wrapped) {
+        assert.deepEqual(wrapped[0].value, [['property-value', 'red', [[1, 2, undefined]]]]);
+      }
     }
   })
   .export(module);
diff --git a/test/selectors/extractor-test.js b/test/selectors/extractor-test.js
deleted file mode 100644 (file)
index 6d2fd88..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-var vows = require('vows');
-var assert = require('assert');
-var tokenize = require('../../lib/tokenizer/tokenize');
-var extractor = require('../../lib/selectors/extractor');
-
-function buildToken(source) {
-  return tokenize(source, { options: {} })[0];
-}
-
-vows.describe(extractor)
-  .addBatch({
-    'no properties': {
-      'topic': extractor(buildToken('a{}')),
-      'has no properties': function (tokens) {
-        assert.deepEqual(tokens, []);
-      }
-    },
-    'no valid properties': {
-      'topic': extractor(buildToken('a{:red}')),
-      'has no properties': function (tokens) {
-        assert.deepEqual(tokens, []);
-      }
-    },
-    'one property': {
-      'topic': extractor(buildToken('a{color:red}')),
-      'has no properties': function (tokens) {
-        assert.deepEqual(tokens, [['color', 'red', 'color', [['color'], ['red']], 'color:red', [['a']], true]]);
-      }
-    },
-    'one important property': {
-      'topic': extractor(buildToken('a{color:red!important}')),
-      'has no properties': function (tokens) {
-        assert.deepEqual(tokens, [['color', 'red!important', 'color', [['color'], ['red!important']], 'color:red!important', [['a']], true]]);
-      }
-    },
-    'one property - simple selector': {
-      'topic': extractor(buildToken('#one span{color:red}')),
-      'has no properties': function (tokens) {
-        assert.deepEqual(tokens, [['color', 'red', 'color', [['color'], ['red']], 'color:red', [['#one span']], true]]);
-      }
-    },
-    'one property - variable': {
-      'topic': extractor(buildToken('#one span{--color:red}')),
-      'has no properties': function (tokens) {
-        assert.deepEqual(tokens, []);
-      }
-    },
-    'one property - block variable': {
-      'topic': extractor(buildToken('#one span{--color:{color:red;display:block};}')),
-      'has no properties': function (tokens) {
-        assert.deepEqual(tokens, []);
-      }
-    },
-    'one property - complex selector': {
-      'topic': extractor(buildToken('.one{color:red}')),
-      'has no properties': function (tokens) {
-        assert.deepEqual(tokens, [['color', 'red', 'color', [['color'], ['red']], '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']], 'color:red', [['a']], true],
-          ['display', 'block', 'display', [['display'], ['block']], 'display:block', [['a']], true]
-        ]);
-      }
-    },
-    'from @media': {
-      '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']], 'color:red', [['a']], true],
-          ['display', 'block', 'display', [['display'], ['block']], 'display:block', [['a']], true],
-          ['color', 'red', 'color', [['color'], ['red']], 'color:red', [['p']], true]
-        ]);
-      }
-    },
-    'with source map info': {
-      'topic': extractor(['selector', [['a', 1, 0, undefined]], [[['color', 1, 3, undefined], ['red', 1, 9, undefined]]]]),
-      'has one property': function (tokens) {
-        assert.deepEqual(tokens, [
-          ['color', 'red', 'color', [['color', 1, 3, undefined], ['red', 1, 9, undefined]], 'color:red', [['a', 1, 0, undefined]], true],
-        ]);
-      }
-    }
-  })
-  .addBatch({
-    'name root special cases': {
-      'vendor prefix': {
-        'topic': extractor(buildToken('a{-moz-transform:none}')),
-        'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['-moz-transform', 'none', 'transform', [['-moz-transform'], ['none']], '-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']], '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']], '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']], '-webkit-border-top-left-radius:none', [['a']], true]]);
-        }
-      },
-      'border-image-width': {
-        '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']], 'border-image-width:2px', [['a']], true]]);
-        }
-      },
-      'border-color': {
-        'topic': extractor(buildToken('a{border-color:red}')),
-        'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['border-color', 'red', 'border', [['border-color'], ['red']], 'border-color:red', [['a']], true]]);
-        }
-      },
-      'border-top-style': {
-        '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']], 'border-top-style:none', [['a']], true]]);
-        }
-      },
-      'border-top': {
-        'topic': extractor(buildToken('a{border-top:none}')),
-        'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['border-top', 'none', 'border', [['border-top'], ['none']], 'border-top:none', [['a']], true]]);
-        }
-      },
-      'border-collapse': {
-        'topic': extractor(buildToken('a{border-collapse:collapse}')),
-        'has no properties': function (tokens) {
-          assert.deepEqual(tokens, [['border-collapse', 'collapse', 'border-collapse', [['border-collapse'], ['collapse']], 'border-collapse:collapse', [['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']], 'text-shadow:none', [['a']], true]]);
-        }
-      }
-    }
-  })
-  .export(module);
index 19eaa67..0a73eb9 100644 (file)
@@ -1,9 +1,6 @@
 var assert = require('assert');
 
 var CleanCSS = require('../lib/clean');
-var tokenize = require('../lib/tokenizer/tokenize');
-var simpleOptimize = require('../lib/selectors/simple');
-var Compatibility = require('../lib/utils/compatibility');
 
 function optimizerContext(group, specs, options) {
   var context = {};
@@ -27,64 +24,6 @@ function optimizerContext(group, specs, options) {
   return context;
 }
 
-function selectorContext(group, specs, options) {
-  var context = {};
-  options = options || {};
-  options.compatibility = new Compatibility(options.compatibility).toOptions();
-
-  function optimized(selectors) {
-    return function (source) {
-      var tokens = tokenize(source, { options: {} });
-      simpleOptimize(tokens, options, {});
-
-      assert.deepEqual(tokens[0] ? tokens[0][1] : null, selectors);
-    };
-  }
-
-  for (var name in specs) {
-    context['selector - ' + group + ' - ' + name] = {
-      topic: specs[name][0],
-      optimized: optimized(specs[name][1])
-    };
-  }
-
-  return context;
-}
-
-function propertyContext(group, specs, options) {
-  var context = {};
-  options = options || {};
-  options.compatibility = new Compatibility(options.compatibility).toOptions();
-
-  function optimized(selectors) {
-    return function (source) {
-      var tokens = tokenize(source, { options: {} });
-      simpleOptimize(tokens, options, {});
-
-      var value = tokens[0] ?
-        tokens[0][2].map(function (property) {
-          return typeof property == 'string' ?
-            property :
-            property.map(function (t) { return t[0]; });
-        }) :
-        null;
-
-      assert.deepEqual(value, selectors);
-    };
-  }
-
-  for (var name in specs) {
-    context['property - ' + group + ' - ' + name] = {
-      topic: specs[name][0],
-      optimized: optimized(specs[name][1])
-    };
-  }
-
-  return context;
-}
-
 module.exports = {
-  optimizerContext: optimizerContext,
-  selectorContext: selectorContext,
-  propertyContext: propertyContext
+  optimizerContext: optimizerContext
 };
index 429be44..954f1df 100644 (file)
 var vows = require('vows');
 var assert = require('assert');
-var Compatibility = require('../../lib/utils/compatibility');
+var compatibility = require('../../lib/utils/compatibility');
 
-vows.describe(Compatibility)
+vows.describe(compatibility)
   .addBatch({
     'as an empty hash': {
       'topic': function () {
-        return new Compatibility({}).toOptions();
+        return compatibility({});
       },
-      'gets default options': function (options) {
-        assert.isTrue(options.colors.opacity);
-        assert.isTrue(options.properties.colors);
-        assert.isFalse(options.properties.backgroundClipMerging);
-        assert.isFalse(options.properties.backgroundOriginMerging);
-        assert.isFalse(options.properties.backgroundSizeMerging);
-        assert.isFalse(options.properties.ieBangHack);
-        assert.isFalse(options.properties.iePrefixHack);
-        assert.isTrue(options.properties.ieSuffixHack);
-        assert.isTrue(options.properties.merging);
-        assert.isFalse(options.properties.shorterLengthUnits);
-        assert.isTrue(options.properties.spaceAfterClosingBrace);
-        assert.isFalse(options.properties.urlQuotes);
-        assert.isTrue(options.properties.zeroUnits);
-        assert.isFalse(options.selectors.adjacentSpace);
-        assert.isFalse(options.selectors.ie7Hack);
-        assert.isTrue(options.units.ch);
-        assert.isTrue(options.units.in);
-        assert.isTrue(options.units.pc);
-        assert.isTrue(options.units.pt);
-        assert.isTrue(options.units.rem);
-        assert.isTrue(options.units.vh);
-        assert.isTrue(options.units.vm);
-        assert.isTrue(options.units.vmax);
-        assert.isTrue(options.units.vmin);
-        assert.isTrue(options.units.vw);
+      'gets default compatibility': function (compat) {
+        assert.isTrue(compat.colors.opacity);
+        assert.isTrue(compat.properties.colors);
+        assert.isFalse(compat.properties.backgroundClipMerging);
+        assert.isFalse(compat.properties.backgroundOriginMerging);
+        assert.isFalse(compat.properties.backgroundSizeMerging);
+        assert.isFalse(compat.properties.ieBangHack);
+        assert.isFalse(compat.properties.iePrefixHack);
+        assert.isTrue(compat.properties.ieSuffixHack);
+        assert.isTrue(compat.properties.merging);
+        assert.isFalse(compat.properties.shorterLengthUnits);
+        assert.isTrue(compat.properties.spaceAfterClosingBrace);
+        assert.isFalse(compat.properties.urlQuotes);
+        assert.isTrue(compat.properties.zeroUnits);
+        assert.isFalse(compat.selectors.adjacentSpace);
+        assert.isFalse(compat.selectors.ie7Hack);
+        assert.isTrue(compat.units.ch);
+        assert.isTrue(compat.units.in);
+        assert.isTrue(compat.units.pc);
+        assert.isTrue(compat.units.pt);
+        assert.isTrue(compat.units.rem);
+        assert.isTrue(compat.units.vh);
+        assert.isTrue(compat.units.vm);
+        assert.isTrue(compat.units.vmax);
+        assert.isTrue(compat.units.vmin);
+        assert.isTrue(compat.units.vw);
       }
     },
     'not given': {
       'topic': function () {
-        return new Compatibility().toOptions();
+        return compatibility();
       },
-      'gets default options': function (options) {
-        assert.deepEqual(options, new Compatibility({}).toOptions());
+      'gets default compatibility': function (compat) {
+        assert.deepEqual(compat, compatibility({}));
       }
     },
     'as a populated hash': {
       'topic': function () {
-        return new Compatibility({ units: { rem: false, vmax: false }, properties: { prefix: true } }).toOptions();
+        return compatibility({ units: { rem: false, vmax: false }, properties: { prefix: true } });
       },
-      'gets merged options': function (options) {
-        assert.isTrue(options.colors.opacity);
-        assert.isFalse(options.properties.backgroundClipMerging);
-        assert.isFalse(options.properties.backgroundOriginMerging);
-        assert.isFalse(options.properties.backgroundSizeMerging);
-        assert.isTrue(options.properties.colors);
-        assert.isFalse(options.properties.ieBangHack);
-        assert.isFalse(options.properties.iePrefixHack);
-        assert.isTrue(options.properties.ieSuffixHack);
-        assert.isTrue(options.properties.merging);
-        assert.isFalse(options.properties.shorterLengthUnits);
-        assert.isTrue(options.properties.spaceAfterClosingBrace);
-        assert.isTrue(options.properties.zeroUnits);
-        assert.isFalse(options.selectors.adjacentSpace);
-        assert.isFalse(options.selectors.ie7Hack);
-        assert.isTrue(options.units.ch);
-        assert.isTrue(options.units.in);
-        assert.isTrue(options.units.pc);
-        assert.isTrue(options.units.pt);
-        assert.isFalse(options.units.rem);
-        assert.isTrue(options.units.vh);
-        assert.isTrue(options.units.vm);
-        assert.isFalse(options.units.vmax);
-        assert.isTrue(options.units.vmin);
-        assert.isTrue(options.units.vw);
+      'gets merged compatibility': function (compat) {
+        assert.isTrue(compat.colors.opacity);
+        assert.isFalse(compat.properties.backgroundClipMerging);
+        assert.isFalse(compat.properties.backgroundOriginMerging);
+        assert.isFalse(compat.properties.backgroundSizeMerging);
+        assert.isTrue(compat.properties.colors);
+        assert.isFalse(compat.properties.ieBangHack);
+        assert.isFalse(compat.properties.iePrefixHack);
+        assert.isTrue(compat.properties.ieSuffixHack);
+        assert.isTrue(compat.properties.merging);
+        assert.isFalse(compat.properties.shorterLengthUnits);
+        assert.isTrue(compat.properties.spaceAfterClosingBrace);
+        assert.isTrue(compat.properties.zeroUnits);
+        assert.isFalse(compat.selectors.adjacentSpace);
+        assert.isFalse(compat.selectors.ie7Hack);
+        assert.isTrue(compat.units.ch);
+        assert.isTrue(compat.units.in);
+        assert.isTrue(compat.units.pc);
+        assert.isTrue(compat.units.pt);
+        assert.isFalse(compat.units.rem);
+        assert.isTrue(compat.units.vh);
+        assert.isTrue(compat.units.vm);
+        assert.isFalse(compat.units.vmax);
+        assert.isTrue(compat.units.vmin);
+        assert.isTrue(compat.units.vw);
       }
     }
   })
   .addBatch({
     'as an ie8 template': {
       'topic': function () {
-        return new Compatibility('ie8').toOptions();
+        return compatibility('ie8');
       },
-      'gets template options': function (options) {
-        assert.isFalse(options.colors.opacity);
-        assert.isFalse(options.properties.backgroundClipMerging);
-        assert.isFalse(options.properties.backgroundOriginMerging);
-        assert.isFalse(options.properties.backgroundSizeMerging);
-        assert.isTrue(options.properties.colors);
-        assert.isFalse(options.properties.ieBangHack);
-        assert.isTrue(options.properties.iePrefixHack);
-        assert.isTrue(options.properties.ieSuffixHack);
-        assert.isFalse(options.properties.merging);
-        assert.isFalse(options.properties.shorterLengthUnits);
-        assert.isTrue(options.properties.spaceAfterClosingBrace);
-        assert.isFalse(options.properties.urlQuotes);
-        assert.isTrue(options.properties.zeroUnits);
-        assert.isFalse(options.selectors.adjacentSpace);
-        assert.isFalse(options.selectors.ie7Hack);
-        assert.isFalse(options.units.ch);
-        assert.isTrue(options.units.in);
-        assert.isTrue(options.units.pc);
-        assert.isTrue(options.units.pt);
-        assert.isFalse(options.units.rem);
-        assert.isFalse(options.units.vh);
-        assert.isFalse(options.units.vm);
-        assert.isFalse(options.units.vmax);
-        assert.isFalse(options.units.vmin);
-        assert.isFalse(options.units.vw);
+      'gets template compatibility': function (compat) {
+        assert.isFalse(compat.colors.opacity);
+        assert.isFalse(compat.properties.backgroundClipMerging);
+        assert.isFalse(compat.properties.backgroundOriginMerging);
+        assert.isFalse(compat.properties.backgroundSizeMerging);
+        assert.isTrue(compat.properties.colors);
+        assert.isFalse(compat.properties.ieBangHack);
+        assert.isTrue(compat.properties.iePrefixHack);
+        assert.isTrue(compat.properties.ieSuffixHack);
+        assert.isFalse(compat.properties.merging);
+        assert.isFalse(compat.properties.shorterLengthUnits);
+        assert.isTrue(compat.properties.spaceAfterClosingBrace);
+        assert.isFalse(compat.properties.urlQuotes);
+        assert.isTrue(compat.properties.zeroUnits);
+        assert.isFalse(compat.selectors.adjacentSpace);
+        assert.isFalse(compat.selectors.ie7Hack);
+        assert.isFalse(compat.units.ch);
+        assert.isTrue(compat.units.in);
+        assert.isTrue(compat.units.pc);
+        assert.isTrue(compat.units.pt);
+        assert.isFalse(compat.units.rem);
+        assert.isFalse(compat.units.vh);
+        assert.isFalse(compat.units.vm);
+        assert.isFalse(compat.units.vmax);
+        assert.isFalse(compat.units.vmin);
+        assert.isFalse(compat.units.vw);
       }
     },
     'as an ie7 template': {
       'topic': function () {
-        return new Compatibility('ie7').toOptions();
+        return compatibility('ie7');
       },
-      'gets template options': function (options) {
-        assert.isFalse(options.colors.opacity);
-        assert.isFalse(options.properties.backgroundClipMerging);
-        assert.isFalse(options.properties.backgroundOriginMerging);
-        assert.isFalse(options.properties.backgroundSizeMerging);
-        assert.isTrue(options.properties.colors);
-        assert.isTrue(options.properties.ieBangHack);
-        assert.isTrue(options.properties.iePrefixHack);
-        assert.isTrue(options.properties.ieSuffixHack);
-        assert.isFalse(options.properties.merging);
-        assert.isFalse(options.properties.shorterLengthUnits);
-        assert.isTrue(options.properties.spaceAfterClosingBrace);
-        assert.isFalse(options.properties.urlQuotes);
-        assert.isTrue(options.properties.zeroUnits);
-        assert.isFalse(options.selectors.adjacentSpace);
-        assert.isTrue(options.selectors.ie7Hack);
-        assert.isFalse(options.units.ch);
-        assert.isTrue(options.units.in);
-        assert.isTrue(options.units.pc);
-        assert.isTrue(options.units.pt);
-        assert.isFalse(options.units.rem);
-        assert.isFalse(options.units.vh);
-        assert.isFalse(options.units.vm);
-        assert.isFalse(options.units.vmax);
-        assert.isFalse(options.units.vmin);
-        assert.isFalse(options.units.vw);
+      'gets template compatibility': function (compat) {
+        assert.isFalse(compat.colors.opacity);
+        assert.isFalse(compat.properties.backgroundClipMerging);
+        assert.isFalse(compat.properties.backgroundOriginMerging);
+        assert.isFalse(compat.properties.backgroundSizeMerging);
+        assert.isTrue(compat.properties.colors);
+        assert.isTrue(compat.properties.ieBangHack);
+        assert.isTrue(compat.properties.iePrefixHack);
+        assert.isTrue(compat.properties.ieSuffixHack);
+        assert.isFalse(compat.properties.merging);
+        assert.isFalse(compat.properties.shorterLengthUnits);
+        assert.isTrue(compat.properties.spaceAfterClosingBrace);
+        assert.isFalse(compat.properties.urlQuotes);
+        assert.isTrue(compat.properties.zeroUnits);
+        assert.isFalse(compat.selectors.adjacentSpace);
+        assert.isTrue(compat.selectors.ie7Hack);
+        assert.isFalse(compat.units.ch);
+        assert.isTrue(compat.units.in);
+        assert.isTrue(compat.units.pc);
+        assert.isTrue(compat.units.pt);
+        assert.isFalse(compat.units.rem);
+        assert.isFalse(compat.units.vh);
+        assert.isFalse(compat.units.vm);
+        assert.isFalse(compat.units.vmax);
+        assert.isFalse(compat.units.vmin);
+        assert.isFalse(compat.units.vw);
       }
     },
     'as an unknown template': {
       'topic': function () {
-        return new Compatibility('').toOptions();
+        return compatibility('');
       },
-      'gets default options': function (options) {
-        assert.deepEqual(options, new Compatibility({}).toOptions());
+      'gets default compatibility': function (compat) {
+        assert.deepEqual(compat, compatibility({}));
       }
     }
   })
   .addBatch({
     'as a complex string value with group': {
       'topic': function () {
-        return new Compatibility('ie8,-properties.iePrefixHack,+colors.opacity').toOptions();
+        return compatibility('ie8,-properties.iePrefixHack,+colors.opacity');
       },
-      'gets calculated options': function (options) {
-        assert.isTrue(options.colors.opacity);
-        assert.isFalse(options.properties.backgroundClipMerging);
-        assert.isFalse(options.properties.backgroundOriginMerging);
-        assert.isFalse(options.properties.backgroundSizeMerging);
-        assert.isTrue(options.properties.colors);
-        assert.isFalse(options.properties.ieBangHack);
-        assert.isFalse(options.properties.iePrefixHack);
-        assert.isTrue(options.properties.ieSuffixHack);
-        assert.isFalse(options.properties.merging);
-        assert.isFalse(options.properties.shorterLengthUnits);
-        assert.isTrue(options.properties.spaceAfterClosingBrace);
-        assert.isFalse(options.properties.urlQuotes);
-        assert.isTrue(options.properties.zeroUnits);
-        assert.isFalse(options.selectors.adjacentSpace);
-        assert.isFalse(options.selectors.ie7Hack);
-        assert.isFalse(options.units.ch);
-        assert.isTrue(options.units.in);
-        assert.isTrue(options.units.pc);
-        assert.isTrue(options.units.pt);
-        assert.isFalse(options.units.rem);
-        assert.isFalse(options.units.vh);
-        assert.isFalse(options.units.vm);
-        assert.isFalse(options.units.vmax);
-        assert.isFalse(options.units.vmin);
-        assert.isFalse(options.units.vw);
+      'gets calculated compatibility': function (compat) {
+        assert.isTrue(compat.colors.opacity);
+        assert.isFalse(compat.properties.backgroundClipMerging);
+        assert.isFalse(compat.properties.backgroundOriginMerging);
+        assert.isFalse(compat.properties.backgroundSizeMerging);
+        assert.isTrue(compat.properties.colors);
+        assert.isFalse(compat.properties.ieBangHack);
+        assert.isFalse(compat.properties.iePrefixHack);
+        assert.isTrue(compat.properties.ieSuffixHack);
+        assert.isFalse(compat.properties.merging);
+        assert.isFalse(compat.properties.shorterLengthUnits);
+        assert.isTrue(compat.properties.spaceAfterClosingBrace);
+        assert.isFalse(compat.properties.urlQuotes);
+        assert.isTrue(compat.properties.zeroUnits);
+        assert.isFalse(compat.selectors.adjacentSpace);
+        assert.isFalse(compat.selectors.ie7Hack);
+        assert.isFalse(compat.units.ch);
+        assert.isTrue(compat.units.in);
+        assert.isTrue(compat.units.pc);
+        assert.isTrue(compat.units.pt);
+        assert.isFalse(compat.units.rem);
+        assert.isFalse(compat.units.vh);
+        assert.isFalse(compat.units.vm);
+        assert.isFalse(compat.units.vmax);
+        assert.isFalse(compat.units.vmin);
+        assert.isFalse(compat.units.vw);
       }
     },
     'as a single string value without group': {
       'topic': function () {
-        return new Compatibility('+properties.iePrefixHack').toOptions();
+        return compatibility('+properties.iePrefixHack');
       },
-      'gets calculated options': function (options) {
-        assert.isTrue(options.colors.opacity);
-        assert.isTrue(options.properties.colors);
-        assert.isFalse(options.properties.backgroundClipMerging);
-        assert.isFalse(options.properties.backgroundOriginMerging);
-        assert.isFalse(options.properties.backgroundSizeMerging);
-        assert.isFalse(options.properties.ieBangHack);
-        assert.isTrue(options.properties.iePrefixHack);
-        assert.isTrue(options.properties.ieSuffixHack);
-        assert.isTrue(options.properties.merging);
-        assert.isFalse(options.properties.shorterLengthUnits);
-        assert.isTrue(options.properties.spaceAfterClosingBrace);
-        assert.isFalse(options.properties.urlQuotes);
-        assert.isTrue(options.properties.zeroUnits);
-        assert.isFalse(options.selectors.adjacentSpace);
-        assert.isFalse(options.selectors.ie7Hack);
-        assert.isTrue(options.units.ch);
-        assert.isTrue(options.units.in);
-        assert.isTrue(options.units.pc);
-        assert.isTrue(options.units.pt);
-        assert.isTrue(options.units.rem);
-        assert.isTrue(options.units.vh);
-        assert.isTrue(options.units.vm);
-        assert.isTrue(options.units.vmax);
-        assert.isTrue(options.units.vmin);
-        assert.isTrue(options.units.vw);
+      'gets calculated compatibility': function (compat) {
+        assert.isTrue(compat.colors.opacity);
+        assert.isTrue(compat.properties.colors);
+        assert.isFalse(compat.properties.backgroundClipMerging);
+        assert.isFalse(compat.properties.backgroundOriginMerging);
+        assert.isFalse(compat.properties.backgroundSizeMerging);
+        assert.isFalse(compat.properties.ieBangHack);
+        assert.isTrue(compat.properties.iePrefixHack);
+        assert.isTrue(compat.properties.ieSuffixHack);
+        assert.isTrue(compat.properties.merging);
+        assert.isFalse(compat.properties.shorterLengthUnits);
+        assert.isTrue(compat.properties.spaceAfterClosingBrace);
+        assert.isFalse(compat.properties.urlQuotes);
+        assert.isTrue(compat.properties.zeroUnits);
+        assert.isFalse(compat.selectors.adjacentSpace);
+        assert.isFalse(compat.selectors.ie7Hack);
+        assert.isTrue(compat.units.ch);
+        assert.isTrue(compat.units.in);
+        assert.isTrue(compat.units.pc);
+        assert.isTrue(compat.units.pt);
+        assert.isTrue(compat.units.rem);
+        assert.isTrue(compat.units.vh);
+        assert.isTrue(compat.units.vm);
+        assert.isTrue(compat.units.vmax);
+        assert.isTrue(compat.units.vmin);
+        assert.isTrue(compat.units.vw);
       }
     },
     'as a complex string value without group': {
       'topic': function () {
-        return new Compatibility('+properties.iePrefixHack,-units.rem').toOptions();
+        return compatibility('+properties.iePrefixHack,-units.rem');
       },
-      'gets calculated options': function (options) {
-        assert.isTrue(options.colors.opacity);
-        assert.isTrue(options.properties.colors);
-        assert.isFalse(options.properties.backgroundClipMerging);
-        assert.isFalse(options.properties.backgroundOriginMerging);
-        assert.isFalse(options.properties.backgroundSizeMerging);
-        assert.isFalse(options.properties.ieBangHack);
-        assert.isTrue(options.properties.iePrefixHack);
-        assert.isTrue(options.properties.ieSuffixHack);
-        assert.isTrue(options.properties.merging);
-        assert.isFalse(options.properties.shorterLengthUnits);
-        assert.isTrue(options.properties.spaceAfterClosingBrace);
-        assert.isFalse(options.properties.urlQuotes);
-        assert.isTrue(options.properties.zeroUnits);
-        assert.isFalse(options.selectors.adjacentSpace);
-        assert.isFalse(options.selectors.ie7Hack);
-        assert.isTrue(options.units.ch);
-        assert.isTrue(options.units.in);
-        assert.isTrue(options.units.pc);
-        assert.isTrue(options.units.pt);
-        assert.isFalse(options.units.rem);
-        assert.isTrue(options.units.vh);
-        assert.isTrue(options.units.vm);
-        assert.isTrue(options.units.vmax);
-        assert.isTrue(options.units.vmin);
-        assert.isTrue(options.units.vw);
+      'gets calculated compatibility': function (compat) {
+        assert.isTrue(compat.colors.opacity);
+        assert.isTrue(compat.properties.colors);
+        assert.isFalse(compat.properties.backgroundClipMerging);
+        assert.isFalse(compat.properties.backgroundOriginMerging);
+        assert.isFalse(compat.properties.backgroundSizeMerging);
+        assert.isFalse(compat.properties.ieBangHack);
+        assert.isTrue(compat.properties.iePrefixHack);
+        assert.isTrue(compat.properties.ieSuffixHack);
+        assert.isTrue(compat.properties.merging);
+        assert.isFalse(compat.properties.shorterLengthUnits);
+        assert.isTrue(compat.properties.spaceAfterClosingBrace);
+        assert.isFalse(compat.properties.urlQuotes);
+        assert.isTrue(compat.properties.zeroUnits);
+        assert.isFalse(compat.selectors.adjacentSpace);
+        assert.isFalse(compat.selectors.ie7Hack);
+        assert.isTrue(compat.units.ch);
+        assert.isTrue(compat.units.in);
+        assert.isTrue(compat.units.pc);
+        assert.isTrue(compat.units.pt);
+        assert.isFalse(compat.units.rem);
+        assert.isTrue(compat.units.vh);
+        assert.isTrue(compat.units.vm);
+        assert.isTrue(compat.units.vmax);
+        assert.isTrue(compat.units.vmin);
+        assert.isTrue(compat.units.vw);
       }
     }
   })
diff --git a/test/utils/quote-scanner-test.js b/test/utils/quote-scanner-test.js
deleted file mode 100644 (file)
index ac80972..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-var vows = require('vows');
-var assert = require('assert');
-var QuoteScanner = require('../../lib/utils/quote-scanner');
-
-vows.describe(QuoteScanner)
-  .addBatch({
-    'no quotes': {
-      topic: 'text without quotes',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator() { index++; });
-
-        assert.equal(index, 0);
-      }
-    },
-    'one single quote': {
-      topic: 'text with \'one quote\'!',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator(match, tokensSoFar, nextStart) {
-          index++;
-
-          assert.equal(match, '\'one quote\'');
-          assert.deepEqual(tokensSoFar, ['text with ']);
-          assert.equal(nextStart, 10);
-        });
-
-        assert.equal(index, 1);
-      }
-    },
-    'one double quote': {
-      topic: 'text with "one quote"!',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator(match, tokensSoFar, nextStart) {
-          index++;
-
-          assert.equal(match, '"one quote"');
-          assert.deepEqual(tokensSoFar, ['text with ']);
-          assert.equal(nextStart, 10);
-        });
-
-        assert.equal(index, 1);
-      }
-    },
-    'mixed quotes': {
-      topic: 'text with "one \'quote\'"!',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator(match, tokensSoFar, nextStart) {
-          index++;
-
-          assert.equal(match, '"one \'quote\'"');
-          assert.deepEqual(tokensSoFar, ['text with ']);
-          assert.equal(nextStart, 10);
-        });
-
-        assert.equal(index, 1);
-      }
-    },
-    'escaped quotes': {
-      topic: 'text with "one \\"quote"!',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator(match, tokensSoFar, nextStart) {
-          index++;
-
-          assert.equal(match, '"one \\"quote"');
-          assert.deepEqual(tokensSoFar, ['text with ']);
-          assert.equal(nextStart, 10);
-        });
-
-        assert.equal(index, 1);
-      }
-    },
-    'one open-ended quote': {
-      topic: '.this-class\\\'s-got-an-apostrophe {}',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator() {
-          index++;
-        });
-
-        assert.equal(index, 0);
-      }
-    },
-    'many open-ended quotes': {
-      topic: '.this-class\\\'s-got-many\\\"-apostrophes\\\' {}',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator() {
-          index++;
-        });
-
-        assert.equal(index, 0);
-      }
-    },
-    'two quotes': {
-      topic: 'text with "one \\"quote" and \'another one\'!',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator(match, tokensSoFar, nextStart) {
-          index++;
-
-          if (index == 1) {
-            assert.equal(match, '"one \\"quote"');
-            assert.deepEqual(tokensSoFar, ['text with ']);
-            assert.equal(nextStart, 10);
-          } else {
-            assert.equal(match, '\'another one\'');
-            assert.deepEqual(tokensSoFar, ['text with ', ' and ']);
-            assert.equal(nextStart, 28);
-          }
-        });
-
-        assert.equal(index, 2);
-      }
-    },
-    'between comments': {
-      topic: '/*! comment */*{box-sizing:border-box}div:before{content:" "}/*! @comment */',
-      iterator: function (topic) {
-        var index = 0;
-        new QuoteScanner(topic).each(function iterator(match) {
-          index++;
-
-          assert.equal(match, '" "');
-        });
-
-        assert.equal(index, 1);
-      }
-    },
-  })
-  .export(module);