diff options
Diffstat (limited to 'node_modules/clean-css/lib/optimizer/level-2/restructure.js')
| -rw-r--r-- | node_modules/clean-css/lib/optimizer/level-2/restructure.js | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/node_modules/clean-css/lib/optimizer/level-2/restructure.js b/node_modules/clean-css/lib/optimizer/level-2/restructure.js new file mode 100644 index 00000000..90b8bfa6 --- /dev/null +++ b/node_modules/clean-css/lib/optimizer/level-2/restructure.js @@ -0,0 +1,389 @@ +var canReorderSingle = require('./reorderable').canReorderSingle; +var extractProperties = require('./extract-properties'); +var isMergeable = require('./is-mergeable'); +var tidyRuleDuplicates = require('./tidy-rule-duplicates'); + +var Token = require('../../tokenizer/token'); + +var cloneArray = require('../../utils/clone-array'); + +var serializeBody = require('../../writer/one-time').body; +var serializeRules = require('../../writer/one-time').rules; + +function naturalSorter(a, b) { + return a > b ? 1 : -1; +} + +function cloneAndMergeSelectors(propertyA, propertyB) { + var cloned = cloneArray(propertyA); + cloned[5] = cloned[5].concat(propertyB[5]); + + return cloned; +} + +function restructure(tokens, context) { + var options = context.options; + var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses; + var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements; + var mergeLimit = options.compatibility.selectors.mergeLimit; + var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging; + var specificityCache = context.cache.specificity; + 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(serializeRules(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 (!isMergeable(serializeRules(sourceTokens[i][1]), mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging)) { + 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 = tidyRuleDuplicates(allSelectors); + dropAsNewTokenAt(position, [movedProperty], allSelectors, qualifiedTokens); + } + + function fitSorter(fit1, fit2) { + return fit1[1] > fit2[1] ? 1 : (fit1[1] == fit2[1] ? 0 : -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 ? serializeRules(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[1][1]; + var propertyName = property[0]; + var propertyBody = property[4]; + if (mergeablePropertyName == propertyName && serializeBody([mergeableProperty]) == propertyBody) { + mergeableToken[2].splice(j, 1); + break; + } + } + } + } + + for (i = properties.length - 1; i >= 0; i--) { + allProperties.unshift(properties[i][3]); + } + + var newToken = [Token.RULE, 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 = tidyRuleDuplicates(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 isRule; + var j, k, m; + var samePropertyAt; + + if (token[0] == Token.RULE) { + isRule = true; + } else if (token[0] == Token.NESTED_BLOCK) { + isRule = 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], specificityCache)) { + unmovableInCurrentToken.push(j); + break; + } + } + } + + for (j = properties.length - 1; j >= 0; 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, specificityCache) && !boundToAnotherPropertyInCurrrentToken(property, movedProperty, token) || + movableTokens[movedProperty[4]] && movableTokens[movedProperty[4]].length === mergeLimit)) { + 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 (movedSameProperty) { + samePropertyAt = k; + } + } + } + + if (!isRule || unmovableInCurrentToken.indexOf(j) > -1) + continue; + + var key = property[4]; + + if (movedSameProperty && movedProperties[samePropertyAt][5].length + property[5].length > mergeLimit) { + dropPropertiesAt(i + 1, movedProperties[samePropertyAt]); + movedProperties.splice(samePropertyAt, 1); + movableTokens[key] = [token]; + movedSameProperty = false; + } else { + movableTokens[key] = movableTokens[key] || []; + movableTokens[key].push(token); + } + + if (movedSameProperty) { + movedProperties[samePropertyAt] = cloneAndMergeSelectors(movedProperties[samePropertyAt], property); + } else { + 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] == Token.AT_RULE && tokens[0][1].indexOf('@charset') === 0 ? 1 : 0; + for (; position < tokens.length - 1; position++) { + 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; + } + + for (i = 0; i < movedProperties.length; i++) { + dropPropertiesAt(position, movedProperties[i]); + } +} + +module.exports = restructure; |
