You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							389 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							389 lines
						
					
					
						
							12 KiB
						
					
					
				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;
 | 
						|
 |