Extracts 'merge non adjacent by body' optimization into a module.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 21 Jun 2015 11:37:56 +0000 (12:37 +0100)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Sun, 21 Jun 2015 11:37:56 +0000 (12:37 +0100)
lib/selectors/advanced.js
lib/selectors/merge-non-adjacent-by-body.js [new file with mode: 0644]
test/integration-test.js
test/selectors/advanced-test.js
test/selectors/merge-adjacent-test.js
test/selectors/merge-non-adjacent-by-body-test.js [new file with mode: 0644]

index 7c806f3..5d3e66b 100644 (file)
@@ -12,16 +12,13 @@ var removeDuplicates = require('./remove-duplicates');
 var mergeAdjacent = require('./merge-adjacent');
 var reduceNonAdjacent = require('./reduce-non-adjacent');
 var mergeNonAdjacentBySelector = require('./merge-non-adjacent-by-selector');
+var mergeNonAdjacentByBody = require('./merge-non-adjacent-by-body');
 
 function AdvancedOptimizer(options, context) {
   this.options = options;
   this.validator = context.validator;
 }
 
-function unsafeSelector(value) {
-  return /\.|\*| :/.test(value);
-}
-
 function naturalSorter(a, b) {
   return a > b;
 }
@@ -30,54 +27,6 @@ AdvancedOptimizer.prototype.isSpecial = function (selector) {
   return this.options.compatibility.selectors.special.test(selector);
 };
 
-function isBemElement(token) {
-  var asString = stringifySelectors(token[1]);
-  return asString.indexOf('__') > -1 || asString.indexOf('--') > -1;
-}
-
-function withoutModifier(selector) {
-  return selector.replace(/--[^ ,>\+~:]+/g, '');
-}
-
-function removeAnyUnsafeElements(left, candidates) {
-  var leftSelector = withoutModifier(stringifySelectors(left[1]));
-
-  for (var body in candidates) {
-    var right = candidates[body];
-    var rightSelector = withoutModifier(stringifySelectors(right[1]));
-
-    if (rightSelector.indexOf(leftSelector) > -1 || leftSelector.indexOf(rightSelector) > -1)
-      delete candidates[body];
-  }
-}
-
-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];
-    if (token[0] != 'selector')
-      continue;
-
-    if (token[2].length > 0 && (!this.options.semanticMerging && unsafeSelector(stringifySelectors(token[1]))))
-      candidates = {};
-
-    if (token[2].length > 0 && this.options.semanticMerging && isBemElement(token))
-      removeAnyUnsafeElements(token, candidates);
-
-    var oldToken = candidates[stringifyBody(token[2])];
-    if (oldToken && !this.isSpecial(stringifySelectors(token[1])) && !this.isSpecial(stringifySelectors(oldToken[1]))) {
-      token[1] = CleanUp.selectors(oldToken[1].concat(token[1]), false, adjacentSpace);
-
-      oldToken[2] = [];
-      candidates[stringifyBody(token[2])] = null;
-    }
-
-    candidates[stringifyBody(token[2])] = token;
-  }
-};
-
 AdvancedOptimizer.prototype.restructure = function (tokens) {
   var movableTokens = {};
   var movedProperties = [];
@@ -536,7 +485,7 @@ AdvancedOptimizer.prototype.optimize = function (tokens) {
     reduceNonAdjacent(tokens, self.options, self.validator);
 
     mergeNonAdjacentBySelector(tokens, self.options, self.validator);
-    self.mergeNonAdjacentByBody(tokens);
+    mergeNonAdjacentByBody(tokens, self.options);
 
     if (self.options.restructuring && withRestructuring) {
       self.restructure(tokens);
diff --git a/lib/selectors/merge-non-adjacent-by-body.js b/lib/selectors/merge-non-adjacent-by-body.js
new file mode 100644 (file)
index 0000000..b38b3f8
--- /dev/null
@@ -0,0 +1,58 @@
+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 unsafeSelector(value) {
+  return /\.|\*| :/.test(value);
+}
+
+function isBemElement(token) {
+  var asString = stringifySelectors(token[1]);
+  return asString.indexOf('__') > -1 || asString.indexOf('--') > -1;
+}
+
+function withoutModifier(selector) {
+  return selector.replace(/--[^ ,>\+~:]+/g, '');
+}
+
+function removeAnyUnsafeElements(left, candidates) {
+  var leftSelector = withoutModifier(stringifySelectors(left[1]));
+
+  for (var body in candidates) {
+    var right = candidates[body];
+    var rightSelector = withoutModifier(stringifySelectors(right[1]));
+
+    if (rightSelector.indexOf(leftSelector) > -1 || leftSelector.indexOf(rightSelector) > -1)
+      delete candidates[body];
+  }
+}
+
+function mergeNonAdjacentByBody(tokens, options) {
+  var candidates = {};
+  var adjacentSpace = options.compatibility.selectors.adjacentSpace;
+
+  for (var i = tokens.length - 1; i >= 0; i--) {
+    var token = tokens[i];
+    if (token[0] != 'selector')
+      continue;
+
+    if (token[2].length > 0 && (!options.semanticMerging && unsafeSelector(stringifySelectors(token[1]))))
+      candidates = {};
+
+    if (token[2].length > 0 && options.semanticMerging && isBemElement(token))
+      removeAnyUnsafeElements(token, candidates);
+
+    var oldToken = candidates[stringifyBody(token[2])];
+    if (oldToken && !isSpecial(options, stringifySelectors(token[1])) && !isSpecial(options, stringifySelectors(oldToken[1]))) {
+      token[1] = cleanUpSelectors(oldToken[1].concat(token[1]), false, adjacentSpace);
+
+      oldToken[2] = [];
+      candidates[stringifyBody(token[2])] = null;
+    }
+
+    candidates[stringifyBody(token[2])] = token;
+  }
+}
+
+module.exports = mergeNonAdjacentByBody;
index 1a12fed..06b9867 100644 (file)
@@ -2166,82 +2166,6 @@ vows.describe('integration tests')
       ]
     })
   )
-  .addBatch(
-    optimizerContext('same bodies', {
-      'of two non-adjacent selectors': [
-        '.one{color:red}.two{color:#00f}.three{color:red}',
-        '.one{color:red}.two{color:#00f}.three{color:red}'
-      ],
-      'of two adjacent single selectors': [
-        '.one{color:red}.two{color:red}',
-        '.one,.two{color:red}'
-      ],
-      'of three adjacent complex, multiple selectors': [
-        '.one{color:red}#two.three{color:red}.four>.five{color:red}',
-        '#two.three,.four>.five,.one{color:red}'
-      ],
-      'with repeated selectors': [
-        '#zero>p,.one,.two{color:red}.two,#zero>p,.three{color:red}',
-        '#zero>p,.one,.three,.two{color:red}'
-      ],
-      'of element selectors': [
-        'p{color:red}a{color:#000}div{color:red}',
-        'div,p{color:red}a{color:#000}'
-      ],
-      'of element selectors inside @media': [
-        '@media screen{p{color:red}a{color:#000}div{color:red}}',
-        '@media screen{div,p{color:red}a{color:#000}}'
-      ],
-      'of element selectors with a class selector in between': [
-        'p{color:red}.a{color:#000}div{color:red}',
-        'p{color:red}.a{color:#000}div{color:red}'
-      ],
-      'of element selectors with an empty class selector in between': [
-        'p{color:red}.a{}div{color:red}',
-        'div,p{color:red}'
-      ]
-    })
-  )
-  .addBatch(
-    optimizerContext('same bodies - IE8 compat', {
-      'of two supported selectors': [
-        '.one:first-child{color:red}.two>.three{color:red}',
-        '.one:first-child,.two>.three{color:red}'
-      ],
-      'of supported and unsupported selector': [
-        '.one:first-child{color:red}.two:last-child{color:red}',
-        '.one:first-child{color:red}.two:last-child{color:red}'
-      ],
-      'of two unsupported selectors': [
-        '.one:nth-child(5){color:red}.two:last-child{color:red}',
-        '.one:nth-child(5){color:red}.two:last-child{color:red}'
-      ]
-    }, { compatibility: 'ie8' })
-  )
-  .addBatch(
-    optimizerContext('same bodies - IE7 compat', {
-      'of two supported selectors': [
-        '.one{color:red}.two>.three{color:red}',
-        '.one,.two>.three{color:red}'
-      ],
-      'of supported and unsupported selector': [
-        '.one{color:red}.two:last-child{color:red}',
-        '.one{color:red}.two:last-child{color:red}'
-      ],
-      'of two unsupported selectors': [
-        '.one:before{color:red}.two:last-child{color:red}',
-        '.one:before{color:red}.two:last-child{color:red}'
-      ]
-    }, { compatibility: 'ie7' })
-  )
-  .addBatch(
-    optimizerContext('same bodies - +adjacentSpace', {
-      'of two supported selectors': [
-        '.one{color:red}.two + nav{color:red}',
-        '.one,.two+ nav{color:red}'
-      ]
-    }, { compatibility: { selectors: { adjacentSpace: true } } })
-  )
   .addBatch(
     optimizerContext('units - IE8 compatibility', {
       'rems': [
index 0ad9960..38ce094 100644 (file)
@@ -138,43 +138,6 @@ vows.describe('advanced optimizer')
       ]
     }, { advanced: true })
   )
-  .addBatch(
-    optimizerContext('selectors - semantic merging mode', {
-      'simple': [
-        '.a{color:red}.b{color:#000}.c{color:red}',
-        '.a,.c{color:red}.b{color:#000}'
-      ],
-      'BEM - modifiers #1': [
-        '.block{color:red}.block__element{color:#000}.block__element--modifier{color:red}',
-        '.block{color:red}.block__element{color:#000}.block__element--modifier{color:red}'
-      ],
-      'BEM - modifiers #2': [
-        '.block1{color:red}.block1__element,.block2{color:#000}.block1__element--modifier{color:red}',
-        '.block1{color:red}.block1__element,.block2{color:#000}.block1__element--modifier{color:red}'
-      ],
-      'BEM - modifiers #3': [
-        '.block1{color:red}.block1--modifier,.block2{color:#000}.block1--another-modifier{color:red}',
-        '.block1{color:red}.block1--modifier,.block2{color:#000}.block1--another-modifier{color:red}'
-      ],
-      'BEM - tail merging': [
-        '.block1{color:red}.block1__element{color:#000}.block1__element--modifier{color:red}a{color:red}.block2__element--modifier{color:red}',
-        '.block1{color:red}.block1__element{color:#000}.block1__element--modifier,.block2__element--modifier,a{color:red}'
-      ],
-      'BEM - two blocks #1': [
-        '.block1__element{color:#000}.block2{color:red}.block2__element{color:#000}.block2__element--modifier{color:red}',
-        '.block1__element,.block2__element{color:#000}.block2,.block2__element--modifier{color:red}'
-      ],
-      'BEM - two blocks #2': [
-        '.block1__element{color:#000}.block1__element--modifier{color:red}.block2{color:red}.block2__element{color:#000}.block2__element--modifier{color:red}',
-        '.block1__element,.block2__element{color:#000}.block1__element--modifier,.block2,.block2__element--modifier{color:red}'
-      ],
-      'BEM - complex traversing #1': [
-        '.block1__element{color:#000}.block1__element--modifier{color:red}.block2{color:#000;display:block;width:100%}',
-        '.block1__element{color:#000}.block1__element--modifier{color:red}.block2{color:#000;display:block;width:100%}'
-        // '.block1__element,.block2{color:#000}.block1__element--modifier{color:red}.block2{display:block;width:100%}' - pending #588
-      ]
-    }, { advanced: true, semanticMerging: true })
-  )
   .addBatch(
     optimizerContext('@media', {
       'empty': [
index fe29de7..358ef18 100644 (file)
@@ -20,6 +20,10 @@ vows.describe('remove duplicates')
         '.one{color:red}.one{font-weight:700}.one{font-size:12px}',
         '.one{color:red;font-weight:700;font-size:12px}'
       ],
+      'of three adjacent complex, multiple selectors': [
+        '.one{color:red}#two.three{color:red}.four>.five{color:red}',
+        '#two.three,.four>.five,.one{color:red}'
+      ],
       'of two adjacent single, complex selectors': [
         '#box>.one{color:red}#box>.one{font-weight:700}',
         '#box>.one{color:red;font-weight:700}'
diff --git a/test/selectors/merge-non-adjacent-by-body-test.js b/test/selectors/merge-non-adjacent-by-body-test.js
new file mode 100644 (file)
index 0000000..f7cbb56
--- /dev/null
@@ -0,0 +1,126 @@
+var vows = require('vows');
+var optimizerContext = require('../test-helper').optimizerContext;
+
+vows.describe('merge non djacent by body')
+  .addBatch(
+    optimizerContext('advanced on', {
+      'of two non-adjacent selectors': [
+        '.one{color:red}.two{color:#00f}.three{color:red}',
+        '.one{color:red}.two{color:#00f}.three{color:red}'
+      ],
+      'with repeated selectors': [
+        '#zero>p,.one,.two{color:red}.two,#zero>p,.three{color:red}',
+        '#zero>p,.one,.three,.two{color:red}'
+      ],
+      'of element selectors': [
+        'p{color:red}a{color:#000}div{color:red}',
+        'div,p{color:red}a{color:#000}'
+      ],
+      'of element selectors inside @media': [
+        '@media screen{p{color:red}a{color:#000}div{color:red}}',
+        '@media screen{div,p{color:red}a{color:#000}}'
+      ],
+      'of element selectors with a class selector in between': [
+        'p{color:red}.a{color:#000}div{color:red}',
+        'p{color:red}.a{color:#000}div{color:red}'
+      ],
+      'of element selectors with an empty class selector in between': [
+        'p{color:red}.a{}div{color:red}',
+        'div,p{color:red}'
+      ]
+    })
+  )
+  .addBatch(
+    optimizerContext('advanced off', {
+      'with repeated selectors': [
+        '#zero>p,.one,.two{color:red}#zero>p,.three,.two{color:red}',
+        '#zero>p,.one,.two{color:red}#zero>p,.three,.two{color:red}'
+      ],
+      'of element selectors': [
+        'p{color:red}a{color:#000}div{color:red}',
+        'p{color:red}a{color:#000}div{color:red}'
+      ],
+      'of element selectors inside @media': [
+        '@media screen{p{color:red}a{color:#000}div{color:red}}',
+        '@media screen{p{color:red}a{color:#000}div{color:red}}'
+      ]
+    }, { advanced: false })
+  )
+  .addBatch(
+    optimizerContext('selectors - semantic merging mode', {
+      'simple': [
+        '.a{color:red}.b{color:#000}.c{color:red}',
+        '.a,.c{color:red}.b{color:#000}'
+      ],
+      'BEM - modifiers #1': [
+        '.block{color:red}.block__element{color:#000}.block__element--modifier{color:red}',
+        '.block{color:red}.block__element{color:#000}.block__element--modifier{color:red}'
+      ],
+      'BEM - modifiers #2': [
+        '.block1{color:red}.block1__element,.block2{color:#000}.block1__element--modifier{color:red}',
+        '.block1{color:red}.block1__element,.block2{color:#000}.block1__element--modifier{color:red}'
+      ],
+      'BEM - modifiers #3': [
+        '.block1{color:red}.block1--modifier,.block2{color:#000}.block1--another-modifier{color:red}',
+        '.block1{color:red}.block1--modifier,.block2{color:#000}.block1--another-modifier{color:red}'
+      ],
+      'BEM - tail merging': [
+        '.block1{color:red}.block1__element{color:#000}.block1__element--modifier{color:red}a{color:red}.block2__element--modifier{color:red}',
+        '.block1{color:red}.block1__element{color:#000}.block1__element--modifier,.block2__element--modifier,a{color:red}'
+      ],
+      'BEM - two blocks #1': [
+        '.block1__element{color:#000}.block2{color:red}.block2__element{color:#000}.block2__element--modifier{color:red}',
+        '.block1__element,.block2__element{color:#000}.block2,.block2__element--modifier{color:red}'
+      ],
+      'BEM - two blocks #2': [
+        '.block1__element{color:#000}.block1__element--modifier{color:red}.block2{color:red}.block2__element{color:#000}.block2__element--modifier{color:red}',
+        '.block1__element,.block2__element{color:#000}.block1__element--modifier,.block2,.block2__element--modifier{color:red}'
+      ],
+      'BEM - complex traversing #1': [
+        '.block1__element{color:#000}.block1__element--modifier{color:red}.block2{color:#000;display:block;width:100%}',
+        '.block1__element{color:#000}.block1__element--modifier{color:red}.block2{color:#000;display:block;width:100%}'
+        // '.block1__element,.block2{color:#000}.block1__element--modifier{color:red}.block2{display:block;width:100%}' - pending #588
+      ]
+    }, { advanced: true, semanticMerging: true })
+  )
+  .addBatch(
+    optimizerContext('IE8 compatibility', {
+      'of two supported selectors': [
+        '.one:first-child{color:red}.two>.three{color:red}',
+        '.one:first-child,.two>.three{color:red}'
+      ],
+      'of supported and unsupported selector': [
+        '.one:first-child{color:red}.two:last-child{color:red}',
+        '.one:first-child{color:red}.two:last-child{color:red}'
+      ],
+      'of two unsupported selectors': [
+        '.one:nth-child(5){color:red}.two:last-child{color:red}',
+        '.one:nth-child(5){color:red}.two:last-child{color:red}'
+      ]
+    }, { compatibility: 'ie8' })
+  )
+  .addBatch(
+    optimizerContext('IE7 compatibility', {
+      'of two supported selectors': [
+        '.one{color:red}.two>.three{color:red}',
+        '.one,.two>.three{color:red}'
+      ],
+      'of supported and unsupported selector': [
+        '.one{color:red}.two:last-child{color:red}',
+        '.one{color:red}.two:last-child{color:red}'
+      ],
+      'of two unsupported selectors': [
+        '.one:before{color:red}.two:last-child{color:red}',
+        '.one:before{color:red}.two:last-child{color:red}'
+      ]
+    }, { compatibility: 'ie7' })
+  )
+  .addBatch(
+    optimizerContext('+adjacentSpace', {
+      'of two supported selectors': [
+        '.one{color:red}.two + nav{color:red}',
+        '.one,.two+ nav{color:red}'
+      ]
+    }, { compatibility: { selectors: { adjacentSpace: true } } })
+  )
+  .export(module);