Spaces:
Sleeping
Sleeping
| 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); | |
| 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; | |