Fixes #442 - space before adjacent `nav`.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 22 Jan 2015 20:10:00 +0000 (20:10 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 22 Jan 2015 20:12:40 +0000 (20:12 +0000)
As it turns out Android stock browser <= 4.3 needs a space
between `+` and `nav`, e.g. `div+ nav{}`.

It's off by default and can be turned on with:
* `--compatibility +selectors.adjacentSpace` or
* `compatibility: { selectors: { adjacentSpace: true } }`

See http://codepen.io/anon/pen/QwgLVv

History.md
README.md
lib/selectors/optimizers/advanced.js
lib/selectors/optimizers/clean-up.js
lib/selectors/optimizers/simple.js
lib/utils/compatibility.js
test/integration-test.js
test/selectors/optimizers/simple-test.js
test/utils/compatibility-test.js

index a075134..2fea22f 100644 (file)
@@ -10,6 +10,7 @@
 * Fixed issue [#416](https://github.com/GoalSmashers/clean-css/issues/416) - accepts hash as `minify` argument.
 * Fixed issue [#435](https://github.com/GoalSmashers/clean-css/issues/435) - `background-clip` in shorthand.
 * Fixed issue [#439](https://github.com/GoalSmashers/clean-css/issues/439) - `background-origin` in shorthand.
+* Fixed issue [#442](https://github.com/GoalSmashers/clean-css/issues/442) - space before adjacent `nav`.
 
 [3.0.7 / 2015-01-22](https://github.com/jakubpawlowicz/clean-css/compare/v3.0.6...v3.0.7)
 ==================
index 66150a6..3dabed0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -288,6 +288,7 @@ with the following options available:
 * `'[+-]properties.backgroundSizeMerging'` - turn on / off background-size merging into shorthand
 * `'[+-]properties.merging'` - turn on / off property merging based on understandability
 * `'[+-]properties.spaceAfterClosingBrace'` - turn on / off removing space after closing brace - `url() no-repeat` into `url()no-repeat`
+* `'[+-]selectors.adjacentSpace'` - turn on / off extra space before `nav` element
 * `'[+-]selectors.ie7Hack'` - turn on / off IE7 selector hack removal (`*+html...`)
 * `'[+-]units.rem'` - turn on / off treating `rem` as a proper unit
 
index 7fe297b..c82826f 100644 (file)
@@ -79,6 +79,7 @@ AdvancedOptimizer.prototype.removeDuplicates = function (tokens) {
 AdvancedOptimizer.prototype.mergeAdjacent = function (tokens) {
   var forRemoval = [];
   var lastToken = { selector: null, body: null };
+  var adjacentSpace = this.options.compatibility.selectors.adjacentSpace;
 
   for (var i = 0, l = tokens.length; i < l; i++) {
     var token = tokens[i];
@@ -97,7 +98,7 @@ AdvancedOptimizer.prototype.mergeAdjacent = function (tokens) {
         !this.isSpecial(token.metadata.selector) && !this.isSpecial(lastToken.metadata.selector)) {
       changeSelectorOf(
         lastToken,
-        CleanUp.selectors(lastToken.value.concat(token.value), false)
+        CleanUp.selectors(lastToken.value.concat(token.value), false, adjacentSpace)
       );
       forRemoval.push(i);
     } else {
@@ -321,6 +322,7 @@ AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) {
 
 AdvancedOptimizer.prototype.mergeNonAdjacentByBody = function (tokens) {
   var candidates = {};
+  var adjacentSpace = this.options.compatibility.selectors.adjacentSpace;
 
   for (var i = tokens.length - 1; i >= 0; i--) {
     var token = tokens[i];
@@ -334,7 +336,7 @@ AdvancedOptimizer.prototype.mergeNonAdjacentByBody = function (tokens) {
     if (oldToken && !this.isSpecial(token.metadata.selector) && !this.isSpecial(oldToken.metadata.selector)) {
       changeSelectorOf(
         token,
-        CleanUp.selectors(oldToken.value.concat(token.value), false)
+        CleanUp.selectors(oldToken.value.concat(token.value), false, adjacentSpace)
       );
 
       oldToken.body = [];
index 943e405..5c2555e 100644 (file)
@@ -7,7 +7,7 @@ function selectorSorter(s1, s2) {
 }
 
 var CleanUp = {
-  selectors: function (selectors, removeUnsupported) {
+  selectors: function (selectors, removeUnsupported, adjacentSpace) {
     var plain = [];
     var tokenized = [];
 
@@ -19,6 +19,9 @@ var CleanUp = {
         .replace(/\s*([>\+\~])\s*/g, '$1')
         .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;
 
index 1190723..3153e5f 100644 (file)
@@ -240,7 +240,7 @@ SimpleOptimizer.prototype.optimize = function(tokens) {
         break;
 
       if (token.kind == 'selector') {
-        var newSelectors = CleanUp.selectors(token.value, !options.compatibility.selectors.ie7Hack);
+        var newSelectors = CleanUp.selectors(token.value, !options.compatibility.selectors.ie7Hack, options.compatibility.selectors.adjacentSpace);
         token.value = newSelectors.tokenized;
 
         if (token.value.length === 0) {
index 23684e5..70c3874 100644 (file)
@@ -13,6 +13,7 @@ var DEFAULTS = {
       spaceAfterClosingBrace: false // 'url() no-repeat' to 'url()no-repeat'
     },
     selectors: {
+      adjacentSpace: false, // div+ nav Android stock browser hack
       ie7Hack: false, // *+html hack
       special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:dir\([a-z-]*\)|:first(?![a-z-])|:fullscreen|:left|:read-only|:read-write|:right)/ // special selectors which prevent merging
     },
@@ -32,6 +33,7 @@ var DEFAULTS = {
       spaceAfterClosingBrace: true
     },
     selectors: {
+      adjacentSpace: false,
       ie7Hack: false,
       special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/
     },
@@ -51,6 +53,7 @@ var DEFAULTS = {
       spaceAfterClosingBrace: true
     },
     selectors: {
+      adjacentSpace: false,
       ie7Hack: true,
       special: /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:focus|:before|:after|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/
     },
index e2bea44..0b15265 100644 (file)
@@ -1852,6 +1852,12 @@ title']{display:block}",
     'of supported and unsupported selector': '.one{color:red}.two:last-child{color:red}',
     'of two unsupported selectors': '.one:before{color:red}.two:last-child{color:red}'
   }, { compatibility: 'ie7' }),
+  'same bodies - +adjacentSpace': cssContext({
+    'of two supported selectors': [
+      '.one{color:red}.two + nav{color:red}',
+      '.one,.two+ nav{color:red}'
+    ]
+  }, { compatibility: { selectors: { adjacentSpace: true } } }),
   'units - IE8 compatibility': cssContext({
     'rems': 'div{padding-top:16px;padding-top:1rem}'
   }, { compatibility: 'ie8' }),
index d5af0da..da90646 100644 (file)
@@ -72,6 +72,10 @@ vows.describe(SimpleOptimizer)
       '+html': [
         '*+html .foo{display:inline}',
         null
+      ],
+      'adjacent nav': [
+        'div + nav{}',
+        [{ value: 'div+nav' }]
       ]
     })
   )
@@ -103,6 +107,18 @@ vows.describe(SimpleOptimizer)
       ]
     }, { compatibility: 'ie7' })
   )
+  .addBatch(
+    selectorContext('+adjacentSpace', {
+      'with whitespace': [
+        'div + nav{}',
+        [{ value: 'div+ nav' }]
+      ],
+      'without whitespace': [
+        'div+nav{}',
+        [{ value: 'div+ nav' }]
+      ]
+    }, { compatibility: { selectors: { adjacentSpace: true } } })
+  )
   .addBatch(
     propertyContext('@background', {
       'none to 0 0': [
@@ -499,4 +515,12 @@ vows.describe(SimpleOptimizer)
       ]
     }, { compatibility: 'ie8' })
   )
+  .addBatch(
+    propertyContext('whitespace in compatibility mode', {
+      'stripped spaces': [
+        'div{text-shadow:rgba(255,1,1,.5) 1px}',
+        ['text-shadow:rgba(255,1,1,.5) 1px']
+      ]
+    }, { compatibility: 'ie8' })
+  )
   .export(module);
index 95b3fbb..cbf7b8c 100644 (file)
@@ -9,6 +9,7 @@ vows.describe(Compatibility)
       'gets default options': function(options) {
         assert.isFalse(options.properties.iePrefixHack);
         assert.isFalse(options.properties.ieSuffixHack);
+        assert.isFalse(options.selectors.adjacentSpace);
         assert.isFalse(options.selectors.ie7Hack);
         assert.isFalse(options.properties.backgroundSizeMerging);
         assert.isTrue(options.properties.merging);
@@ -29,6 +30,7 @@ vows.describe(Compatibility)
       'gets merged options': function(options) {
         assert.isFalse(options.properties.iePrefixHack);
         assert.isFalse(options.properties.ieSuffixHack);
+        assert.isFalse(options.selectors.adjacentSpace);
         assert.isFalse(options.selectors.ie7Hack);
         assert.isFalse(options.properties.backgroundSizeMerging);
         assert.isTrue(options.properties.merging);
@@ -45,6 +47,7 @@ vows.describe(Compatibility)
       'gets template options': function(options) {
         assert.isTrue(options.properties.iePrefixHack);
         assert.isTrue(options.properties.ieSuffixHack);
+        assert.isFalse(options.selectors.adjacentSpace);
         assert.isFalse(options.selectors.ie7Hack);
         assert.isFalse(options.properties.backgroundSizeMerging);
         assert.isFalse(options.properties.merging);
@@ -60,6 +63,7 @@ vows.describe(Compatibility)
         assert.isTrue(options.properties.iePrefixHack);
         assert.isTrue(options.properties.ieSuffixHack);
         assert.isTrue(options.selectors.ie7Hack);
+        assert.isFalse(options.selectors.adjacentSpace);
         assert.isFalse(options.properties.backgroundSizeMerging);
         assert.isFalse(options.properties.merging);
         assert.isTrue(options.properties.spaceAfterClosingBrace);