123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- 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;
|