From f8f123b6ef1067912c74c8577ce10774df259348 Mon Sep 17 00:00:00 2001 From: Jakub Pawlowicz Date: Sun, 21 Jun 2015 14:35:19 +0100 Subject: [PATCH] Extracts 'restructure' optimization into a module. --- lib/selectors/advanced.js | 356 +---------------------------- lib/selectors/restructure.js | 352 ++++++++++++++++++++++++++++ test/selectors/advanced-test.js | 136 ----------- test/selectors/restructure-test.js | 149 ++++++++++++ 4 files changed, 503 insertions(+), 490 deletions(-) create mode 100644 lib/selectors/restructure.js create mode 100644 test/selectors/restructure-test.js diff --git a/lib/selectors/advanced.js b/lib/selectors/advanced.js index 5d3e66b0..4f55c398 100644 --- a/lib/selectors/advanced.js +++ b/lib/selectors/advanced.js @@ -1,373 +1,21 @@ var optimizeProperties = require('../properties/optimizer'); -var CleanUp = require('./clean-up'); var extractProperties = require('./extractor'); var canReorder = require('./reorderable').canReorder; -var canReorderSingle = require('./reorderable').canReorderSingle; var stringifyAll = require('../stringifier/one-time').all; -var stringifyBody = require('../stringifier/one-time').body; -var stringifySelectors = require('../stringifier/one-time').selectors; 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'); +var restructure = require('./restructure'); function AdvancedOptimizer(options, context) { this.options = options; this.validator = context.validator; } -function naturalSorter(a, b) { - return a > b; -} - -AdvancedOptimizer.prototype.isSpecial = function (selector) { - return this.options.compatibility.selectors.special.test(selector); -}; - -AdvancedOptimizer.prototype.restructure = function (tokens) { - var movableTokens = {}; - var movedProperties = []; - var multiPropertyMoveCache = {}; - var movedToBeDropped = []; - var self = this; - var maxCombinationsLevel = 2; - var ID_JOIN_CHARACTER = '%'; - - function sendToMultiPropertyMoveCache(position, movedProperty, allFits) { - for (var i = allFits.length - 1; i >= 0; i--) { - var fit = allFits[i][0]; - var id = addToCache(movedProperty, fit); - - if (multiPropertyMoveCache[id].length > 1 && processMultiPropertyMove(position, multiPropertyMoveCache[id])) { - removeAllMatchingFromCache(id); - break; - } - } - } - - function addToCache(movedProperty, fit) { - var id = cacheId(fit); - multiPropertyMoveCache[id] = multiPropertyMoveCache[id] || []; - multiPropertyMoveCache[id].push([movedProperty, fit]); - return id; - } - - function removeAllMatchingFromCache(matchId) { - var matchSelectors = matchId.split(ID_JOIN_CHARACTER); - var forRemoval = []; - var i; - - for (var id in multiPropertyMoveCache) { - var selectors = id.split(ID_JOIN_CHARACTER); - for (i = selectors.length - 1; i >= 0; i--) { - if (matchSelectors.indexOf(selectors[i]) > -1) { - forRemoval.push(id); - break; - } - } - } - - for (i = forRemoval.length - 1; i >= 0; i--) { - delete multiPropertyMoveCache[forRemoval[i]]; - } - } - - function cacheId(cachedTokens) { - var id = []; - for (var i = 0, l = cachedTokens.length; i < l; i++) { - id.push(stringifySelectors(cachedTokens[i][1])); - } - return id.join(ID_JOIN_CHARACTER); - } - - function tokensToMerge(sourceTokens) { - var uniqueTokensWithBody = []; - var mergeableTokens = []; - - for (var i = sourceTokens.length - 1; i >= 0; i--) { - if (self.isSpecial(stringifySelectors(sourceTokens[i][1]))) - continue; - - mergeableTokens.unshift(sourceTokens[i]); - if (sourceTokens[i][2].length > 0 && uniqueTokensWithBody.indexOf(sourceTokens[i]) == -1) - uniqueTokensWithBody.push(sourceTokens[i]); - } - - return uniqueTokensWithBody.length > 1 ? - mergeableTokens : - []; - } - - function shortenIfPossible(position, movedProperty) { - var name = movedProperty[0]; - var value = movedProperty[1]; - var key = movedProperty[4]; - var valueSize = name.length + value.length + 1; - var allSelectors = []; - var qualifiedTokens = []; - - var mergeableTokens = tokensToMerge(movableTokens[key]); - if (mergeableTokens.length < 2) - return; - - var allFits = findAllFits(mergeableTokens, valueSize, 1); - var bestFit = allFits[0]; - if (bestFit[1] > 0) - return sendToMultiPropertyMoveCache(position, movedProperty, allFits); - - for (var i = bestFit[0].length - 1; i >=0; i--) { - allSelectors = bestFit[0][i][1].concat(allSelectors); - qualifiedTokens.unshift(bestFit[0][i]); - } - - allSelectors = CleanUp.selectorDuplicates(allSelectors); - dropAsNewTokenAt(position, [movedProperty], allSelectors, qualifiedTokens); - } - - function fitSorter(fit1, fit2) { - return fit1[1] > fit2[1]; - } - - function findAllFits(mergeableTokens, propertySize, propertiesCount) { - var combinations = allCombinations(mergeableTokens, propertySize, propertiesCount, maxCombinationsLevel - 1); - return combinations.sort(fitSorter); - } - - function allCombinations(tokensVariant, propertySize, propertiesCount, level) { - var differenceVariants = [[tokensVariant, sizeDifference(tokensVariant, propertySize, propertiesCount)]]; - if (tokensVariant.length > 2 && level > 0) { - for (var i = tokensVariant.length - 1; i >= 0; i--) { - var subVariant = Array.prototype.slice.call(tokensVariant, 0); - subVariant.splice(i, 1); - differenceVariants = differenceVariants.concat(allCombinations(subVariant, propertySize, propertiesCount, level - 1)); - } - } - - return differenceVariants; - } - - 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; - } - return allSelectorsSize - (tokensVariant.length - 1) * propertySize + 1; - } - - function dropAsNewTokenAt(position, properties, allSelectors, mergeableTokens) { - var i, j, k, m; - var allProperties = []; - - for (i = mergeableTokens.length - 1; i >= 0; i--) { - var mergeableToken = mergeableTokens[i]; - - for (j = mergeableToken[2].length - 1; j >= 0; j--) { - var mergeableProperty = mergeableToken[2][j]; - - for (k = 0, m = properties.length; k < m; k++) { - var property = properties[k]; - - var mergeablePropertyName = mergeableProperty[0][0]; - var propertyName = property[0]; - var propertyBody = property[4]; - if (mergeablePropertyName == propertyName && stringifyBody([mergeableProperty]) == propertyBody) { - mergeableToken[2].splice(j, 1); - break; - } - } - } - } - - for (i = properties.length - 1; i >= 0; i--) { - allProperties.push(properties[i][3]); - } - - var newToken = ['selector', allSelectors, allProperties]; - tokens.splice(position, 0, newToken); - } - - function dropPropertiesAt(position, movedProperty) { - var key = movedProperty[4]; - var toMove = movableTokens[key]; - - if (toMove && toMove.length > 1) { - if (!shortenMultiMovesIfPossible(position, movedProperty)) - shortenIfPossible(position, movedProperty); - } - } - - function shortenMultiMovesIfPossible(position, movedProperty) { - var candidates = []; - var propertiesAndMergableTokens = []; - var key = movedProperty[4]; - var j, k; - - var mergeableTokens = tokensToMerge(movableTokens[key]); - if (mergeableTokens.length < 2) - return; - - movableLoop: - for (var value in movableTokens) { - var tokensList = movableTokens[value]; - - for (j = mergeableTokens.length - 1; j >= 0; j--) { - if (tokensList.indexOf(mergeableTokens[j]) == -1) - continue movableLoop; - } - - candidates.push(value); - } - - if (candidates.length < 2) - return false; - - for (j = candidates.length - 1; j >= 0; j--) { - for (k = movedProperties.length - 1; k >= 0; k--) { - if (movedProperties[k][4] == candidates[j]) { - propertiesAndMergableTokens.unshift([movedProperties[k], mergeableTokens]); - break; - } - } - } - - return processMultiPropertyMove(position, propertiesAndMergableTokens); - } - - function processMultiPropertyMove(position, propertiesAndMergableTokens) { - var valueSize = 0; - var properties = []; - var property; - - for (var i = propertiesAndMergableTokens.length - 1; i >= 0; i--) { - property = propertiesAndMergableTokens[i][0]; - var fullValue = property[4]; - valueSize += fullValue.length + (i > 0 ? 1 : 0); - - properties.push(property); - } - - var mergeableTokens = propertiesAndMergableTokens[0][1]; - var bestFit = findAllFits(mergeableTokens, valueSize, properties.length)[0]; - if (bestFit[1] > 0) - return false; - - var allSelectors = []; - var qualifiedTokens = []; - for (i = bestFit[0].length - 1; i >= 0; i--) { - allSelectors = bestFit[0][i][1].concat(allSelectors); - qualifiedTokens.unshift(bestFit[0][i]); - } - - allSelectors = CleanUp.selectorDuplicates(allSelectors); - dropAsNewTokenAt(position, properties, allSelectors, qualifiedTokens); - - for (i = properties.length - 1; i >= 0; i--) { - property = properties[i]; - var index = movedProperties.indexOf(property); - - delete movableTokens[property[4]]; - - if (index > -1 && movedToBeDropped.indexOf(index) == -1) - movedToBeDropped.push(index); - } - - return true; - } - - function boundToAnotherPropertyInCurrrentToken(property, movedProperty, token) { - var propertyName = property[0]; - var movedPropertyName = movedProperty[0]; - if (propertyName != movedPropertyName) - return false; - - var key = movedProperty[4]; - var toMove = movableTokens[key]; - return toMove && toMove.indexOf(token) > -1; - } - - for (var i = tokens.length - 1; i >= 0; i--) { - var token = tokens[i]; - var isSelector; - var j, k, m; - - if (token[0] == 'selector') { - isSelector = true; - } else if (token[0] == 'block') { - isSelector = false; - } else { - continue; - } - - // We cache movedProperties.length as it may change in the loop - var movedCount = movedProperties.length; - - var properties = extractProperties(token); - movedToBeDropped = []; - - var unmovableInCurrentToken = []; - for (j = properties.length - 1; j >= 0; j--) { - for (k = j - 1; k >= 0; k--) { - if (!canReorderSingle(properties[j], properties[k])) { - unmovableInCurrentToken.push(j); - break; - } - } - } - - for (j = 0, m = properties.length; j < m; j++) { - var property = properties[j]; - var movedSameProperty = false; - - for (k = 0; k < movedCount; k++) { - var movedProperty = movedProperties[k]; - - if (movedToBeDropped.indexOf(k) == -1 && !canReorderSingle(property, movedProperty) && !boundToAnotherPropertyInCurrrentToken(property, movedProperty, token)) { - dropPropertiesAt(i + 1, movedProperty, token); - - if (movedToBeDropped.indexOf(k) == -1) { - movedToBeDropped.push(k); - delete movableTokens[movedProperty[4]]; - } - } - - if (!movedSameProperty) - movedSameProperty = property[0] == movedProperty[0] && property[1] == movedProperty[1]; - } - - if (!isSelector || unmovableInCurrentToken.indexOf(j) > -1) - continue; - - var key = property[4]; - movableTokens[key] = movableTokens[key] || []; - movableTokens[key].push(token); - - if (!movedSameProperty) - movedProperties.push(property); - } - - movedToBeDropped = movedToBeDropped.sort(naturalSorter); - for (j = 0, m = movedToBeDropped.length; j < m; j++) { - var dropAt = movedToBeDropped[j] - j; - movedProperties.splice(dropAt, 1); - } - } - - var position = tokens[0] && tokens[0][0] == 'at-rule' && tokens[0][1][0].indexOf('@charset') === 0 ? 1 : 0; - for (; position < tokens.length - 1; position++) { - var isImportRule = tokens[position][0] === 'at-rule' && tokens[position][1][0].indexOf('@import') === 0; - var isEscapedCommentSpecial = tokens[position][0] === 'text' && tokens[position][1][0].indexOf('__ESCAPED_COMMENT_SPECIAL') === 0; - if (!(isImportRule || isEscapedCommentSpecial)) - break; - } - - for (i = 0; i < movedProperties.length; i++) { - dropPropertiesAt(position, movedProperties[i]); - } -}; - AdvancedOptimizer.prototype.removeDuplicateMediaQueries = function (tokens) { var candidates = {}; @@ -488,7 +136,7 @@ AdvancedOptimizer.prototype.optimize = function (tokens) { mergeNonAdjacentByBody(tokens, self.options); if (self.options.restructuring && withRestructuring) { - self.restructure(tokens); + restructure(tokens, self.options); mergeAdjacent(tokens, self.options, self.validator); } diff --git a/lib/selectors/restructure.js b/lib/selectors/restructure.js new file mode 100644 index 00000000..2fe72fb4 --- /dev/null +++ b/lib/selectors/restructure.js @@ -0,0 +1,352 @@ +var extractProperties = require('./extractor'); +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 isSpecial = require('./is-special'); + +function naturalSorter(a, b) { + return a > b; +} + +function restructure(tokens, options) { + var movableTokens = {}; + var movedProperties = []; + var multiPropertyMoveCache = {}; + var movedToBeDropped = []; + var maxCombinationsLevel = 2; + var ID_JOIN_CHARACTER = '%'; + + function sendToMultiPropertyMoveCache(position, movedProperty, allFits) { + for (var i = allFits.length - 1; i >= 0; i--) { + var fit = allFits[i][0]; + var id = addToCache(movedProperty, fit); + + if (multiPropertyMoveCache[id].length > 1 && processMultiPropertyMove(position, multiPropertyMoveCache[id])) { + removeAllMatchingFromCache(id); + break; + } + } + } + + function addToCache(movedProperty, fit) { + var id = cacheId(fit); + multiPropertyMoveCache[id] = multiPropertyMoveCache[id] || []; + multiPropertyMoveCache[id].push([movedProperty, fit]); + return id; + } + + function removeAllMatchingFromCache(matchId) { + var matchSelectors = matchId.split(ID_JOIN_CHARACTER); + var forRemoval = []; + var i; + + for (var id in multiPropertyMoveCache) { + var selectors = id.split(ID_JOIN_CHARACTER); + for (i = selectors.length - 1; i >= 0; i--) { + if (matchSelectors.indexOf(selectors[i]) > -1) { + forRemoval.push(id); + break; + } + } + } + + for (i = forRemoval.length - 1; i >= 0; i--) { + delete multiPropertyMoveCache[forRemoval[i]]; + } + } + + function cacheId(cachedTokens) { + var id = []; + for (var i = 0, l = cachedTokens.length; i < l; i++) { + id.push(stringifySelectors(cachedTokens[i][1])); + } + return id.join(ID_JOIN_CHARACTER); + } + + function tokensToMerge(sourceTokens) { + var uniqueTokensWithBody = []; + var mergeableTokens = []; + + for (var i = sourceTokens.length - 1; i >= 0; i--) { + if (isSpecial(options, stringifySelectors(sourceTokens[i][1]))) + continue; + + mergeableTokens.unshift(sourceTokens[i]); + if (sourceTokens[i][2].length > 0 && uniqueTokensWithBody.indexOf(sourceTokens[i]) == -1) + uniqueTokensWithBody.push(sourceTokens[i]); + } + + return uniqueTokensWithBody.length > 1 ? + mergeableTokens : + []; + } + + function shortenIfPossible(position, movedProperty) { + var name = movedProperty[0]; + var value = movedProperty[1]; + var key = movedProperty[4]; + var valueSize = name.length + value.length + 1; + var allSelectors = []; + var qualifiedTokens = []; + + var mergeableTokens = tokensToMerge(movableTokens[key]); + if (mergeableTokens.length < 2) + return; + + var allFits = findAllFits(mergeableTokens, valueSize, 1); + var bestFit = allFits[0]; + if (bestFit[1] > 0) + return sendToMultiPropertyMoveCache(position, movedProperty, allFits); + + for (var i = bestFit[0].length - 1; i >=0; i--) { + allSelectors = bestFit[0][i][1].concat(allSelectors); + qualifiedTokens.unshift(bestFit[0][i]); + } + + allSelectors = cleanUpSelectorDuplicates(allSelectors); + dropAsNewTokenAt(position, [movedProperty], allSelectors, qualifiedTokens); + } + + function fitSorter(fit1, fit2) { + return fit1[1] > fit2[1]; + } + + function findAllFits(mergeableTokens, propertySize, propertiesCount) { + var combinations = allCombinations(mergeableTokens, propertySize, propertiesCount, maxCombinationsLevel - 1); + return combinations.sort(fitSorter); + } + + function allCombinations(tokensVariant, propertySize, propertiesCount, level) { + var differenceVariants = [[tokensVariant, sizeDifference(tokensVariant, propertySize, propertiesCount)]]; + if (tokensVariant.length > 2 && level > 0) { + for (var i = tokensVariant.length - 1; i >= 0; i--) { + var subVariant = Array.prototype.slice.call(tokensVariant, 0); + subVariant.splice(i, 1); + differenceVariants = differenceVariants.concat(allCombinations(subVariant, propertySize, propertiesCount, level - 1)); + } + } + + return differenceVariants; + } + + 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; + } + return allSelectorsSize - (tokensVariant.length - 1) * propertySize + 1; + } + + function dropAsNewTokenAt(position, properties, allSelectors, mergeableTokens) { + var i, j, k, m; + var allProperties = []; + + for (i = mergeableTokens.length - 1; i >= 0; i--) { + var mergeableToken = mergeableTokens[i]; + + for (j = mergeableToken[2].length - 1; j >= 0; j--) { + var mergeableProperty = mergeableToken[2][j]; + + for (k = 0, m = properties.length; k < m; k++) { + var property = properties[k]; + + var mergeablePropertyName = mergeableProperty[0][0]; + var propertyName = property[0]; + var propertyBody = property[4]; + if (mergeablePropertyName == propertyName && stringifyBody([mergeableProperty]) == propertyBody) { + mergeableToken[2].splice(j, 1); + break; + } + } + } + } + + for (i = properties.length - 1; i >= 0; i--) { + allProperties.push(properties[i][3]); + } + + var newToken = ['selector', allSelectors, allProperties]; + tokens.splice(position, 0, newToken); + } + + function dropPropertiesAt(position, movedProperty) { + var key = movedProperty[4]; + var toMove = movableTokens[key]; + + if (toMove && toMove.length > 1) { + if (!shortenMultiMovesIfPossible(position, movedProperty)) + shortenIfPossible(position, movedProperty); + } + } + + function shortenMultiMovesIfPossible(position, movedProperty) { + var candidates = []; + var propertiesAndMergableTokens = []; + var key = movedProperty[4]; + var j, k; + + var mergeableTokens = tokensToMerge(movableTokens[key]); + if (mergeableTokens.length < 2) + return; + + movableLoop: + for (var value in movableTokens) { + var tokensList = movableTokens[value]; + + for (j = mergeableTokens.length - 1; j >= 0; j--) { + if (tokensList.indexOf(mergeableTokens[j]) == -1) + continue movableLoop; + } + + candidates.push(value); + } + + if (candidates.length < 2) + return false; + + for (j = candidates.length - 1; j >= 0; j--) { + for (k = movedProperties.length - 1; k >= 0; k--) { + if (movedProperties[k][4] == candidates[j]) { + propertiesAndMergableTokens.unshift([movedProperties[k], mergeableTokens]); + break; + } + } + } + + return processMultiPropertyMove(position, propertiesAndMergableTokens); + } + + function processMultiPropertyMove(position, propertiesAndMergableTokens) { + var valueSize = 0; + var properties = []; + var property; + + for (var i = propertiesAndMergableTokens.length - 1; i >= 0; i--) { + property = propertiesAndMergableTokens[i][0]; + var fullValue = property[4]; + valueSize += fullValue.length + (i > 0 ? 1 : 0); + + properties.push(property); + } + + var mergeableTokens = propertiesAndMergableTokens[0][1]; + var bestFit = findAllFits(mergeableTokens, valueSize, properties.length)[0]; + if (bestFit[1] > 0) + return false; + + var allSelectors = []; + var qualifiedTokens = []; + for (i = bestFit[0].length - 1; i >= 0; i--) { + allSelectors = bestFit[0][i][1].concat(allSelectors); + qualifiedTokens.unshift(bestFit[0][i]); + } + + allSelectors = cleanUpSelectorDuplicates(allSelectors); + dropAsNewTokenAt(position, properties, allSelectors, qualifiedTokens); + + for (i = properties.length - 1; i >= 0; i--) { + property = properties[i]; + var index = movedProperties.indexOf(property); + + delete movableTokens[property[4]]; + + if (index > -1 && movedToBeDropped.indexOf(index) == -1) + movedToBeDropped.push(index); + } + + return true; + } + + function boundToAnotherPropertyInCurrrentToken(property, movedProperty, token) { + var propertyName = property[0]; + var movedPropertyName = movedProperty[0]; + if (propertyName != movedPropertyName) + return false; + + var key = movedProperty[4]; + var toMove = movableTokens[key]; + return toMove && toMove.indexOf(token) > -1; + } + + for (var i = tokens.length - 1; i >= 0; i--) { + var token = tokens[i]; + var isSelector; + var j, k, m; + + if (token[0] == 'selector') { + isSelector = true; + } else if (token[0] == 'block') { + isSelector = false; + } else { + continue; + } + + // We cache movedProperties.length as it may change in the loop + var movedCount = movedProperties.length; + + var properties = extractProperties(token); + movedToBeDropped = []; + + var unmovableInCurrentToken = []; + for (j = properties.length - 1; j >= 0; j--) { + for (k = j - 1; k >= 0; k--) { + if (!canReorderSingle(properties[j], properties[k])) { + unmovableInCurrentToken.push(j); + break; + } + } + } + + for (j = 0, m = properties.length; j < m; j++) { + var property = properties[j]; + var movedSameProperty = false; + + for (k = 0; k < movedCount; k++) { + var movedProperty = movedProperties[k]; + + if (movedToBeDropped.indexOf(k) == -1 && !canReorderSingle(property, movedProperty) && !boundToAnotherPropertyInCurrrentToken(property, movedProperty, token)) { + dropPropertiesAt(i + 1, movedProperty, token); + + if (movedToBeDropped.indexOf(k) == -1) { + movedToBeDropped.push(k); + delete movableTokens[movedProperty[4]]; + } + } + + if (!movedSameProperty) + movedSameProperty = property[0] == movedProperty[0] && property[1] == movedProperty[1]; + } + + if (!isSelector || unmovableInCurrentToken.indexOf(j) > -1) + continue; + + var key = property[4]; + movableTokens[key] = movableTokens[key] || []; + movableTokens[key].push(token); + + if (!movedSameProperty) + movedProperties.push(property); + } + + movedToBeDropped = movedToBeDropped.sort(naturalSorter); + for (j = 0, m = movedToBeDropped.length; j < m; j++) { + var dropAt = movedToBeDropped[j] - j; + movedProperties.splice(dropAt, 1); + } + } + + var position = tokens[0] && tokens[0][0] == 'at-rule' && tokens[0][1][0].indexOf('@charset') === 0 ? 1 : 0; + for (; position < tokens.length - 1; position++) { + var isImportRule = tokens[position][0] === 'at-rule' && tokens[position][1][0].indexOf('@import') === 0; + var isEscapedCommentSpecial = tokens[position][0] === 'text' && tokens[position][1][0].indexOf('__ESCAPED_COMMENT_SPECIAL') === 0; + if (!(isImportRule || isEscapedCommentSpecial)) + break; + } + + for (i = 0; i < movedProperties.length; i++) { + dropPropertiesAt(position, movedProperties[i]); + } +} + +module.exports = restructure; diff --git a/test/selectors/advanced-test.js b/test/selectors/advanced-test.js index 38ce0940..f5409bd3 100644 --- a/test/selectors/advanced-test.js +++ b/test/selectors/advanced-test.js @@ -2,142 +2,6 @@ var vows = require('vows'); var optimizerContext = require('../test-helper').optimizerContext; vows.describe('advanced optimizer') - .addBatch( - optimizerContext('selectors - restructuring', { - 'up until changed': [ - 'a{color:#000}div{color:red}.one{display:block}.two{display:inline;color:red}', - 'a{color:#000}.two,div{color:red}.one{display:block}.two{display:inline}' - ], - 'up until top': [ - 'a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', - '.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' - ], - 'up until top with charset': [ - '@charset "utf-8";a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', - '@charset "utf-8";.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' - ], - 'two at once': [ - '.one,.two,.three{color:red;display:block}div{margin:0}.four,.five,.six{color:red;display:block}', - '.five,.four,.one,.six,.three,.two{color:red;display:block}div{margin:0}' - ], - 'down until changed': [ - '.one{padding:0}.two{margin:0}.one{margin-bottom:3px}', - '.two{margin:0}.one{padding:0;margin-bottom:3px}' - ], - 'over shorthands': [ - 'div{margin-top:0}.one{margin:0}.two{display:block;margin-top:0}', - '.two,div{margin-top:0}.one{margin:0}.two{display:block}' - ], - 'over shorthands with flush': [ - 'div{margin-top:0}.one{margin:5px}.two{display:block;margin-top:0}.three{color:red}.four{margin-top:0}', - 'div{margin-top:0}.one{margin:5px}.four,.two{margin-top:0}.two{display:block}.three{color:red}' - ], - 'over shorthand - border': [ - '.one{border-color:red}.two{border:1px solid}.three{color:#fff;border-color:red}', - '.one{border-color:red}.two{border:1px solid}.three{color:#fff;border-color:red}' - ], - 'granuar over granular': [ - 'div{margin-top:0}.one{margin-bottom:2px}.two{display:block;margin-top:0}', - '.two,div{margin-top:0}.one{margin-bottom:2px}.two{display:block}' - ], - 'shorthand over granular with different value': [ - 'div{margin:0}.one{margin-bottom:1px}.two{display:block;margin:0}', - 'div{margin:0}.one{margin-bottom:1px}.two{display:block;margin:0}' - ], - 'shorthand over granular with different value for simple tags': [ - 'div{margin:0}body{margin-bottom:1px}p{display:block;margin:0}', - 'div,p{margin:0}body{margin-bottom:1px}p{display:block}' - ], - 'shorthand over granular with different value for simple tags when tag match': [ - 'div{margin:0}body,p{margin-bottom:1px}p{display:block;margin:0}', - 'div{margin:0}body,p{margin-bottom:1px}p{display:block;margin:0}' - ], - 'shorthand over granular with same value': [ - 'div{margin:0}.one{margin-bottom:0}.two{display:block;margin:0}', - '.two,div{margin:0}.one{margin-bottom:0}.two{display:block}' - ], - 'dropping longer content at a right place': [ - '.one,a:hover{color:red}a:hover{color:#000;display:block;border-color:#000}.longer-name{color:#000;border-color:#000}', - '.one,a:hover{color:red}.longer-name,a:hover{color:#000;border-color:#000}a:hover{display:block}' - ], - 'over media without overriding': [ - 'div{margin:0}@media{.one{color:red}}.two{display:block;margin:0}', - '.two,div{margin:0}@media{.one{color:red}}.two{display:block}' - ], - 'over media with overriding by different value': [ - 'div{margin:0}@media{.one{margin:10px}}.two{display:block;margin:0}', - 'div{margin:0}@media{.one{margin:10px}}.two{display:block;margin:0}' - ], - 'over media with overriding by same value': [ - 'div{margin:0}@media{.one{margin:0}}.two{display:block;margin:0}', - '.two,div{margin:0}@media{.one{margin:0}}.two{display:block}' - ], - 'over media with overriding by a granular': [ - 'div{margin:0}@media{.one{margin-bottom:0}}.two{display:block;margin:0}', - '.two,div{margin:0}@media{.one{margin-bottom:0}}.two{display:block}' - ], - 'over media with overriding by a different granular': [ - 'div{margin-top:0}@media{.one{margin-bottom:0}}.two{display:block;margin-top:0}', - '.two,div{margin-top:0}@media{.one{margin-bottom:0}}.two{display:block}' - ], - 'over media with a new property': [ - 'div{margin-top:0}@media{.one{margin-top:0}}.two{display:block;margin:0}', - 'div{margin-top:0}@media{.one{margin-top:0}}.two{display:block;margin:0}' - ], - 'over a property in the same selector': [ - 'div{background-size:100%}a{background:no-repeat;background-size:100%}', - 'div{background-size:100%}a{background:no-repeat;background-size:100%}' - ], - 'multiple granular up to a shorthand': [ - '.one{border:1px solid #bbb}.two{border-color:#666}.three{border-width:1px;border-style:solid}', - '.one{border:1px solid #bbb}.two{border-color:#666}.three{border-width:1px;border-style:solid}' - ], - 'multiple granular - complex case': [ - '.one{background:red;padding:8px 16px}.two{padding-left:16px;padding-right:16px}.three{padding-top:20px}.four{border-left:1px solid #000;border-right:1px solid #000;border-bottom:1px solid #000}.five{background-color:#fff;background-image:-moz-linear-gradient();background-image:-ms-linear-gradient();background-image:-webkit-gradient();background-image:-webkit-linear-gradient()}', - '.one{background:red;padding:8px 16px}.two{padding-left:16px;padding-right:16px}.three{padding-top:20px}.four{border-left:1px solid #000;border-right:1px solid #000;border-bottom:1px solid #000}.five{background-color:#fff;background-image:-moz-linear-gradient();background-image:-ms-linear-gradient();background-image:-webkit-gradient();background-image:-webkit-linear-gradient()}' - ], - 'multiple granular - special': [ - 'input:-ms-input-placeholder{color:red;text-align:center}input::placeholder{color:red;text-align:center}', - 'input:-ms-input-placeholder{color:red;text-align:center}input::placeholder{color:red;text-align:center}' - ], - 'moving one already being moved with different value': [ - '.one{color:red}.two{display:block}.three{color:red;display:inline}.four{display:inline-block}.five{color:#000}', - '.one,.three{color:red}.two{display:block}.three{display:inline}.four{display:inline-block}.five{color:#000}' - ], - 'not in keyframes': [ - '@keyframes test{0%{transform:scale3d(1,1,1);opacity:1}100%{transform:scale3d(.5,.5,.5);opacity:1}}', - '@keyframes test{0%{transform:scale3d(1,1,1);opacity:1}100%{transform:scale3d(.5,.5,.5);opacity:1}}' - ], - 'not in vendored keyframes': [ - '@-moz-keyframes test{0%{transform:scale3d(1,1,1);opacity:1}100%{transform:scale3d(.5,.5,.5);opacity:1}}', - '@-moz-keyframes test{0%{transform:scale3d(1,1,1);opacity:1}100%{transform:scale3d(.5,.5,.5);opacity:1}}' - ], - 'with one important comment': [ - '/*! comment */a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', - '/*! comment */.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' - ], - 'with many important comments': [ - '/*! comment 1 *//*! comment 2 */a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', - '/*! comment 1 *//*! comment 2 */.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' - ], - 'with important comment and charset': [ - '@charset "utf-8";/*! comment */a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', - '@charset "utf-8";/*! comment */.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' - ], - 'with charset and import': [ - '@charset "UTF-8";@import url(http://fonts.googleapis.com/css?family=Lora:400,700);a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', - '@charset "UTF-8";@import url(http://fonts.googleapis.com/css?family=Lora:400,700);.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' - ], - 'with charset and import and comments': [ - '@charset "UTF-8";@import url(http://fonts.googleapis.com/css?family=Lora:400,700);/*! comment */a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', - '@charset "UTF-8";@import url(http://fonts.googleapis.com/css?family=Lora:400,700);/*! comment */.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' - ], - 'with vendor prefixed value group': [ - 'a{-moz-box-sizing:content-box;box-sizing:content-box}div{color:red}p{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}', - 'a{box-sizing:content-box}a,p{-moz-box-sizing:content-box}div{color:red}p{-webkit-box-sizing:content-box;box-sizing:content-box}' - ] - }, { advanced: true }) - ) .addBatch( optimizerContext('@media', { 'empty': [ diff --git a/test/selectors/restructure-test.js b/test/selectors/restructure-test.js new file mode 100644 index 00000000..59cf0aa2 --- /dev/null +++ b/test/selectors/restructure-test.js @@ -0,0 +1,149 @@ +var vows = require('vows'); +var optimizerContext = require('../test-helper').optimizerContext; + +vows.describe('restructure') + .addBatch( + optimizerContext('advanced on', { + 'up until changed': [ + 'a{color:#000}div{color:red}.one{display:block}.two{display:inline;color:red}', + 'a{color:#000}.two,div{color:red}.one{display:block}.two{display:inline}' + ], + 'up until top': [ + 'a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', + '.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' + ], + 'up until top with charset': [ + '@charset "utf-8";a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', + '@charset "utf-8";.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' + ], + 'two at once': [ + '.one,.two,.three{color:red;display:block}div{margin:0}.four,.five,.six{color:red;display:block}', + '.five,.four,.one,.six,.three,.two{color:red;display:block}div{margin:0}' + ], + 'down until changed': [ + '.one{padding:0}.two{margin:0}.one{margin-bottom:3px}', + '.two{margin:0}.one{padding:0;margin-bottom:3px}' + ], + 'over shorthands': [ + 'div{margin-top:0}.one{margin:0}.two{display:block;margin-top:0}', + '.two,div{margin-top:0}.one{margin:0}.two{display:block}' + ], + 'over shorthands with flush': [ + 'div{margin-top:0}.one{margin:5px}.two{display:block;margin-top:0}.three{color:red}.four{margin-top:0}', + 'div{margin-top:0}.one{margin:5px}.four,.two{margin-top:0}.two{display:block}.three{color:red}' + ], + 'over shorthand - border': [ + '.one{border-color:red}.two{border:1px solid}.three{color:#fff;border-color:red}', + '.one{border-color:red}.two{border:1px solid}.three{color:#fff;border-color:red}' + ], + 'granuar over granular': [ + 'div{margin-top:0}.one{margin-bottom:2px}.two{display:block;margin-top:0}', + '.two,div{margin-top:0}.one{margin-bottom:2px}.two{display:block}' + ], + 'shorthand over granular with different value': [ + 'div{margin:0}.one{margin-bottom:1px}.two{display:block;margin:0}', + 'div{margin:0}.one{margin-bottom:1px}.two{display:block;margin:0}' + ], + 'shorthand over granular with different value for simple tags': [ + 'div{margin:0}body{margin-bottom:1px}p{display:block;margin:0}', + 'div,p{margin:0}body{margin-bottom:1px}p{display:block}' + ], + 'shorthand over granular with different value for simple tags when tag match': [ + 'div{margin:0}body,p{margin-bottom:1px}p{display:block;margin:0}', + 'div{margin:0}body,p{margin-bottom:1px}p{display:block;margin:0}' + ], + 'shorthand over granular with same value': [ + 'div{margin:0}.one{margin-bottom:0}.two{display:block;margin:0}', + '.two,div{margin:0}.one{margin-bottom:0}.two{display:block}' + ], + 'dropping longer content at a right place': [ + '.one,a:hover{color:red}a:hover{color:#000;display:block;border-color:#000}.longer-name{color:#000;border-color:#000}', + '.one,a:hover{color:red}.longer-name,a:hover{color:#000;border-color:#000}a:hover{display:block}' + ], + 'over media without overriding': [ + 'div{margin:0}@media{.one{color:red}}.two{display:block;margin:0}', + '.two,div{margin:0}@media{.one{color:red}}.two{display:block}' + ], + 'over media with overriding by different value': [ + 'div{margin:0}@media{.one{margin:10px}}.two{display:block;margin:0}', + 'div{margin:0}@media{.one{margin:10px}}.two{display:block;margin:0}' + ], + 'over media with overriding by same value': [ + 'div{margin:0}@media{.one{margin:0}}.two{display:block;margin:0}', + '.two,div{margin:0}@media{.one{margin:0}}.two{display:block}' + ], + 'over media with overriding by a granular': [ + 'div{margin:0}@media{.one{margin-bottom:0}}.two{display:block;margin:0}', + '.two,div{margin:0}@media{.one{margin-bottom:0}}.two{display:block}' + ], + 'over media with overriding by a different granular': [ + 'div{margin-top:0}@media{.one{margin-bottom:0}}.two{display:block;margin-top:0}', + '.two,div{margin-top:0}@media{.one{margin-bottom:0}}.two{display:block}' + ], + 'over media with a new property': [ + 'div{margin-top:0}@media{.one{margin-top:0}}.two{display:block;margin:0}', + 'div{margin-top:0}@media{.one{margin-top:0}}.two{display:block;margin:0}' + ], + 'over a property in the same selector': [ + 'div{background-size:100%}a{background:no-repeat;background-size:100%}', + 'div{background-size:100%}a{background:no-repeat;background-size:100%}' + ], + 'multiple granular up to a shorthand': [ + '.one{border:1px solid #bbb}.two{border-color:#666}.three{border-width:1px;border-style:solid}', + '.one{border:1px solid #bbb}.two{border-color:#666}.three{border-width:1px;border-style:solid}' + ], + 'multiple granular - complex case': [ + '.one{background:red;padding:8px 16px}.two{padding-left:16px;padding-right:16px}.three{padding-top:20px}.four{border-left:1px solid #000;border-right:1px solid #000;border-bottom:1px solid #000}.five{background-color:#fff;background-image:-moz-linear-gradient();background-image:-ms-linear-gradient();background-image:-webkit-gradient();background-image:-webkit-linear-gradient()}', + '.one{background:red;padding:8px 16px}.two{padding-left:16px;padding-right:16px}.three{padding-top:20px}.four{border-left:1px solid #000;border-right:1px solid #000;border-bottom:1px solid #000}.five{background-color:#fff;background-image:-moz-linear-gradient();background-image:-ms-linear-gradient();background-image:-webkit-gradient();background-image:-webkit-linear-gradient()}' + ], + 'multiple granular - special': [ + 'input:-ms-input-placeholder{color:red;text-align:center}input::placeholder{color:red;text-align:center}', + 'input:-ms-input-placeholder{color:red;text-align:center}input::placeholder{color:red;text-align:center}' + ], + 'moving one already being moved with different value': [ + '.one{color:red}.two{display:block}.three{color:red;display:inline}.four{display:inline-block}.five{color:#000}', + '.one,.three{color:red}.two{display:block}.three{display:inline}.four{display:inline-block}.five{color:#000}' + ], + 'not in keyframes': [ + '@keyframes test{0%{transform:scale3d(1,1,1);opacity:1}100%{transform:scale3d(.5,.5,.5);opacity:1}}', + '@keyframes test{0%{transform:scale3d(1,1,1);opacity:1}100%{transform:scale3d(.5,.5,.5);opacity:1}}' + ], + 'not in vendored keyframes': [ + '@-moz-keyframes test{0%{transform:scale3d(1,1,1);opacity:1}100%{transform:scale3d(.5,.5,.5);opacity:1}}', + '@-moz-keyframes test{0%{transform:scale3d(1,1,1);opacity:1}100%{transform:scale3d(.5,.5,.5);opacity:1}}' + ], + 'with one important comment': [ + '/*! comment */a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', + '/*! comment */.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' + ], + 'with many important comments': [ + '/*! comment 1 *//*! comment 2 */a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', + '/*! comment 1 *//*! comment 2 */.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' + ], + 'with important comment and charset': [ + '@charset "utf-8";/*! comment */a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', + '@charset "utf-8";/*! comment */.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' + ], + 'with charset and import': [ + '@charset "UTF-8";@import url(http://fonts.googleapis.com/css?family=Lora:400,700);a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', + '@charset "UTF-8";@import url(http://fonts.googleapis.com/css?family=Lora:400,700);.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' + ], + 'with charset and import and comments': [ + '@charset "UTF-8";@import url(http://fonts.googleapis.com/css?family=Lora:400,700);/*! comment */a{width:100px}div{color:red}.one{display:block}.two{display:inline;color:red}', + '@charset "UTF-8";@import url(http://fonts.googleapis.com/css?family=Lora:400,700);/*! comment */.two,div{color:red}a{width:100px}.one{display:block}.two{display:inline}' + ], + 'with vendor prefixed value group': [ + 'a{-moz-box-sizing:content-box;box-sizing:content-box}div{color:red}p{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}', + 'a{box-sizing:content-box}a,p{-moz-box-sizing:content-box}div{color:red}p{-webkit-box-sizing:content-box;box-sizing:content-box}' + ] + }) + ) + .addBatch( + optimizerContext('advanced off', { + 'up until changed': [ + 'a{color:#000}div{color:red}.one{display:block}.two{display:inline;color:red}', + 'a{color:#000}div{color:red}.one{display:block}.two{display:inline;color:red}' + ] + }, { advanced: false }) + ) + .export(module); -- 2.34.1