From: Jakub Pawlowicz Date: Sun, 21 Jun 2015 11:37:56 +0000 (+0100) Subject: Extracts 'merge non adjacent by body' optimization into a module. X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=3c400e684317bc8cf1df9c2926b44b33d298c9f0;p=clean-css.git Extracts 'merge non adjacent by body' optimization into a module. --- diff --git a/lib/selectors/advanced.js b/lib/selectors/advanced.js index 7c806f37..5d3e66b0 100644 --- a/lib/selectors/advanced.js +++ b/lib/selectors/advanced.js @@ -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 index 00000000..b38b3f89 --- /dev/null +++ b/lib/selectors/merge-non-adjacent-by-body.js @@ -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; diff --git a/test/integration-test.js b/test/integration-test.js index 1a12fed5..06b98672 100644 --- a/test/integration-test.js +++ b/test/integration-test.js @@ -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': [ diff --git a/test/selectors/advanced-test.js b/test/selectors/advanced-test.js index 0ad99608..38ce0940 100644 --- a/test/selectors/advanced-test.js +++ b/test/selectors/advanced-test.js @@ -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': [ diff --git a/test/selectors/merge-adjacent-test.js b/test/selectors/merge-adjacent-test.js index fe29de70..358ef180 100644 --- a/test/selectors/merge-adjacent-test.js +++ b/test/selectors/merge-adjacent-test.js @@ -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 index 00000000..f7cbb564 --- /dev/null +++ b/test/selectors/merge-non-adjacent-by-body-test.js @@ -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);