497 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			497 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
var everyValuesPair = require('./every-values-pair');
 | 
						|
var hasInherit = require('./has-inherit');
 | 
						|
var hasSameValues = require('./has-same-values');
 | 
						|
var populateComponents = require('./populate-components');
 | 
						|
 | 
						|
var configuration = require('../../configuration');
 | 
						|
var deepClone = require('../../clone').deep;
 | 
						|
var restoreWithComponents = require('../restore-with-components');
 | 
						|
 | 
						|
var restoreFromOptimizing = require('../../restore-from-optimizing');
 | 
						|
var wrapSingle = require('../../wrap-for-optimizing').single;
 | 
						|
 | 
						|
var serializeBody = require('../../../writer/one-time').body;
 | 
						|
var Token = require('../../../tokenizer/token');
 | 
						|
 | 
						|
function mergeIntoShorthands(properties, validator) {
 | 
						|
  var candidates = {};
 | 
						|
  var descriptor;
 | 
						|
  var componentOf;
 | 
						|
  var property;
 | 
						|
  var i, l;
 | 
						|
  var j, m;
 | 
						|
 | 
						|
  // there is no shorthand property made up of less than 3 longhands
 | 
						|
  if (properties.length < 3) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  for (i = 0, l = properties.length; i < l; i++) {
 | 
						|
    property = properties[i];
 | 
						|
    descriptor = configuration[property.name];
 | 
						|
 | 
						|
    if (property.dynamic) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (property.unused) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (property.hack) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (property.block) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (descriptor && descriptor.singleTypeComponents && !hasSameValues(property)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    invalidateOrCompact(properties, i, candidates, validator);
 | 
						|
 | 
						|
    if (descriptor && descriptor.componentOf) {
 | 
						|
      for (j = 0, m = descriptor.componentOf.length; j < m; j++) {
 | 
						|
        componentOf = descriptor.componentOf[j];
 | 
						|
 | 
						|
        candidates[componentOf] = candidates[componentOf] || {};
 | 
						|
        candidates[componentOf][property.name] = property;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  invalidateOrCompact(properties, i, candidates, validator);
 | 
						|
}
 | 
						|
 | 
						|
function invalidateOrCompact(properties, position, candidates, validator) {
 | 
						|
  var invalidatedBy = properties[position];
 | 
						|
  var shorthandName;
 | 
						|
  var shorthandDescriptor;
 | 
						|
  var candidateComponents;
 | 
						|
  var replacedCandidates = [];
 | 
						|
  var i;
 | 
						|
 | 
						|
  for (shorthandName in candidates) {
 | 
						|
    if (undefined !== invalidatedBy && shorthandName == invalidatedBy.name) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    shorthandDescriptor = configuration[shorthandName];
 | 
						|
    candidateComponents = candidates[shorthandName];
 | 
						|
    if (invalidatedBy && invalidates(candidates, shorthandName, invalidatedBy)) {
 | 
						|
      delete candidates[shorthandName];
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (shorthandDescriptor.components.length > Object.keys(candidateComponents).length) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (mixedImportance(candidateComponents)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!overridable(candidateComponents, shorthandName, validator)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!mergeable(candidateComponents)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (mixedInherit(candidateComponents)) {
 | 
						|
      replaceWithInheritBestFit(properties, candidateComponents, shorthandName, validator);
 | 
						|
    } else {
 | 
						|
      replaceWithShorthand(properties, candidateComponents, shorthandName, validator);
 | 
						|
    }
 | 
						|
 | 
						|
    replacedCandidates.push(shorthandName);
 | 
						|
  }
 | 
						|
 | 
						|
  for (i = replacedCandidates.length - 1; i >= 0; i--) {
 | 
						|
    delete candidates[replacedCandidates[i]];
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function invalidates(candidates, shorthandName, invalidatedBy) {
 | 
						|
  var shorthandDescriptor = configuration[shorthandName];
 | 
						|
  var invalidatedByDescriptor = configuration[invalidatedBy.name];
 | 
						|
  var componentName;
 | 
						|
 | 
						|
  if ('overridesShorthands' in shorthandDescriptor && shorthandDescriptor.overridesShorthands.indexOf(invalidatedBy.name) > -1) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (invalidatedByDescriptor && 'componentOf' in invalidatedByDescriptor) {
 | 
						|
    for (componentName in candidates[shorthandName]) {
 | 
						|
      if (invalidatedByDescriptor.componentOf.indexOf(componentName) > -1) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
function mixedImportance(components) {
 | 
						|
  var important;
 | 
						|
  var componentName;
 | 
						|
 | 
						|
  for (componentName in components) {
 | 
						|
    if (undefined !== important && components[componentName].important != important) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    important = components[componentName].important;
 | 
						|
  }
 | 
						|
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
function overridable(components, shorthandName, validator) {
 | 
						|
  var descriptor = configuration[shorthandName];
 | 
						|
  var newValuePlaceholder = [
 | 
						|
    Token.PROPERTY,
 | 
						|
    [Token.PROPERTY_NAME, shorthandName],
 | 
						|
    [Token.PROPERTY_VALUE, descriptor.defaultValue]
 | 
						|
  ];
 | 
						|
  var newProperty = wrapSingle(newValuePlaceholder);
 | 
						|
  var component;
 | 
						|
  var mayOverride;
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  populateComponents([newProperty], validator, []);
 | 
						|
 | 
						|
  for (i = 0, l = descriptor.components.length; i < l; i++) {
 | 
						|
    component = components[descriptor.components[i]];
 | 
						|
    mayOverride = configuration[component.name].canOverride || sameValue;
 | 
						|
 | 
						|
    if (!everyValuesPair(mayOverride.bind(null, validator), newProperty.components[i], component)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
function sameValue(_validator, value1, value2) {
 | 
						|
  return value1 === value2;
 | 
						|
}
 | 
						|
 | 
						|
function mergeable(components) {
 | 
						|
  var lastCount = null;
 | 
						|
  var currentCount;
 | 
						|
  var componentName;
 | 
						|
  var component;
 | 
						|
  var descriptor;
 | 
						|
  var values;
 | 
						|
 | 
						|
  for (componentName in components) {
 | 
						|
    component = components[componentName];
 | 
						|
    descriptor = configuration[componentName];
 | 
						|
 | 
						|
    if (!('restore' in descriptor)) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    restoreFromOptimizing([component.all[component.position]], restoreWithComponents);
 | 
						|
    values = descriptor.restore(component, configuration);
 | 
						|
 | 
						|
    currentCount = values.length;
 | 
						|
 | 
						|
    if (lastCount !== null && currentCount !== lastCount) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    lastCount = currentCount;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
function mixedInherit(components) {
 | 
						|
  var componentName;
 | 
						|
  var lastValue = null;
 | 
						|
  var currentValue;
 | 
						|
 | 
						|
  for (componentName in components) {
 | 
						|
    currentValue = hasInherit(components[componentName]);
 | 
						|
 | 
						|
    if (lastValue !== null && lastValue !== currentValue) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    lastValue = currentValue;
 | 
						|
  }
 | 
						|
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
function replaceWithInheritBestFit(properties, candidateComponents, shorthandName, validator) {
 | 
						|
  var viaLonghands = buildSequenceWithInheritLonghands(candidateComponents, shorthandName, validator);
 | 
						|
  var viaShorthand = buildSequenceWithInheritShorthand(candidateComponents, shorthandName, validator);
 | 
						|
  var longhandTokensSequence = viaLonghands[0];
 | 
						|
  var shorthandTokensSequence = viaShorthand[0];
 | 
						|
  var isLonghandsShorter = serializeBody(longhandTokensSequence).length < serializeBody(shorthandTokensSequence).length;
 | 
						|
  var newTokensSequence = isLonghandsShorter ? longhandTokensSequence : shorthandTokensSequence;
 | 
						|
  var newProperty = isLonghandsShorter ? viaLonghands[1] : viaShorthand[1];
 | 
						|
  var newComponents = isLonghandsShorter ? viaLonghands[2] : viaShorthand[2];
 | 
						|
  var lastComponent = candidateComponents[Object.keys(candidateComponents).pop()];
 | 
						|
  var all = lastComponent.all;
 | 
						|
  var insertAt = lastComponent.position;
 | 
						|
  var componentName;
 | 
						|
  var oldComponent;
 | 
						|
  var newComponent;
 | 
						|
  var newToken;
 | 
						|
 | 
						|
  newProperty.position = insertAt;
 | 
						|
  newProperty.shorthand = true;
 | 
						|
  newProperty.important = lastComponent.important;
 | 
						|
  newProperty.multiplex = false;
 | 
						|
  newProperty.dirty = true;
 | 
						|
  newProperty.all = all;
 | 
						|
  newProperty.all[insertAt] = newTokensSequence[0];
 | 
						|
 | 
						|
  properties.splice(insertAt, 1, newProperty);
 | 
						|
 | 
						|
  for (componentName in candidateComponents) {
 | 
						|
    oldComponent = candidateComponents[componentName];
 | 
						|
    oldComponent.unused = true;
 | 
						|
 | 
						|
    newProperty.multiplex = newProperty.multiplex || oldComponent.multiplex;
 | 
						|
 | 
						|
    if (oldComponent.name in newComponents) {
 | 
						|
      newComponent = newComponents[oldComponent.name];
 | 
						|
      newToken = findTokenIn(newTokensSequence, componentName);
 | 
						|
 | 
						|
      newComponent.position = all.length;
 | 
						|
      newComponent.all = all;
 | 
						|
      newComponent.all.push(newToken);
 | 
						|
 | 
						|
      properties.push(newComponent);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function buildSequenceWithInheritLonghands(components, shorthandName, validator) {
 | 
						|
  var tokensSequence = [];
 | 
						|
  var inheritComponents = {};
 | 
						|
  var nonInheritComponents = {};
 | 
						|
  var descriptor = configuration[shorthandName];
 | 
						|
  var shorthandToken = [
 | 
						|
    Token.PROPERTY,
 | 
						|
    [Token.PROPERTY_NAME, shorthandName],
 | 
						|
    [Token.PROPERTY_VALUE, descriptor.defaultValue]
 | 
						|
  ];
 | 
						|
  var newProperty = wrapSingle(shorthandToken);
 | 
						|
  var component;
 | 
						|
  var longhandToken;
 | 
						|
  var newComponent;
 | 
						|
  var nameMetadata;
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  populateComponents([newProperty], validator, []);
 | 
						|
 | 
						|
  for (i = 0, l = descriptor.components.length; i < l; i++) {
 | 
						|
    component = components[descriptor.components[i]];
 | 
						|
 | 
						|
    if (hasInherit(component)) {
 | 
						|
      longhandToken = component.all[component.position].slice(0, 2);
 | 
						|
      Array.prototype.push.apply(longhandToken, component.value);
 | 
						|
      tokensSequence.push(longhandToken);
 | 
						|
 | 
						|
      newComponent = deepClone(component);
 | 
						|
      newComponent.value = inferComponentValue(components, newComponent.name);
 | 
						|
 | 
						|
      newProperty.components[i] = newComponent;
 | 
						|
      inheritComponents[component.name] = deepClone(component);
 | 
						|
    } else {
 | 
						|
      newComponent = deepClone(component);
 | 
						|
      newComponent.all = component.all;
 | 
						|
      newProperty.components[i] = newComponent;
 | 
						|
 | 
						|
      nonInheritComponents[component.name] = component;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  newProperty.important = components[Object.keys(components).pop()].important;
 | 
						|
 | 
						|
  nameMetadata = joinMetadata(nonInheritComponents, 1);
 | 
						|
  shorthandToken[1].push(nameMetadata);
 | 
						|
 | 
						|
  restoreFromOptimizing([newProperty], restoreWithComponents);
 | 
						|
 | 
						|
  shorthandToken = shorthandToken.slice(0, 2);
 | 
						|
  Array.prototype.push.apply(shorthandToken, newProperty.value);
 | 
						|
 | 
						|
  tokensSequence.unshift(shorthandToken);
 | 
						|
 | 
						|
  return [tokensSequence, newProperty, inheritComponents];
 | 
						|
}
 | 
						|
 | 
						|
function inferComponentValue(components, propertyName) {
 | 
						|
  var descriptor = configuration[propertyName];
 | 
						|
 | 
						|
  if ('oppositeTo' in descriptor) {
 | 
						|
    return components[descriptor.oppositeTo].value;
 | 
						|
  }
 | 
						|
  return [[Token.PROPERTY_VALUE, descriptor.defaultValue]];
 | 
						|
}
 | 
						|
 | 
						|
function joinMetadata(components, at) {
 | 
						|
  var metadata = [];
 | 
						|
  var component;
 | 
						|
  var originalValue;
 | 
						|
  var componentMetadata;
 | 
						|
  var componentName;
 | 
						|
 | 
						|
  for (componentName in components) {
 | 
						|
    component = components[componentName];
 | 
						|
    originalValue = component.all[component.position];
 | 
						|
    componentMetadata = originalValue[at][originalValue[at].length - 1];
 | 
						|
 | 
						|
    Array.prototype.push.apply(metadata, componentMetadata);
 | 
						|
  }
 | 
						|
 | 
						|
  return metadata.sort(metadataSorter);
 | 
						|
}
 | 
						|
 | 
						|
function metadataSorter(metadata1, metadata2) {
 | 
						|
  var line1 = metadata1[0];
 | 
						|
  var line2 = metadata2[0];
 | 
						|
  var column1 = metadata1[1];
 | 
						|
  var column2 = metadata2[1];
 | 
						|
 | 
						|
  if (line1 < line2) {
 | 
						|
    return -1;
 | 
						|
  } if (line1 === line2) {
 | 
						|
    return column1 < column2 ? -1 : 1;
 | 
						|
  }
 | 
						|
  return 1;
 | 
						|
}
 | 
						|
 | 
						|
function buildSequenceWithInheritShorthand(components, shorthandName, validator) {
 | 
						|
  var tokensSequence = [];
 | 
						|
  var inheritComponents = {};
 | 
						|
  var nonInheritComponents = {};
 | 
						|
  var descriptor = configuration[shorthandName];
 | 
						|
  var shorthandToken = [
 | 
						|
    Token.PROPERTY,
 | 
						|
    [Token.PROPERTY_NAME, shorthandName],
 | 
						|
    [Token.PROPERTY_VALUE, 'inherit']
 | 
						|
  ];
 | 
						|
  var newProperty = wrapSingle(shorthandToken);
 | 
						|
  var component;
 | 
						|
  var longhandToken;
 | 
						|
  var nameMetadata;
 | 
						|
  var valueMetadata;
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  populateComponents([newProperty], validator, []);
 | 
						|
 | 
						|
  for (i = 0, l = descriptor.components.length; i < l; i++) {
 | 
						|
    component = components[descriptor.components[i]];
 | 
						|
 | 
						|
    if (hasInherit(component)) {
 | 
						|
      inheritComponents[component.name] = component;
 | 
						|
    } else {
 | 
						|
      longhandToken = component.all[component.position].slice(0, 2);
 | 
						|
      Array.prototype.push.apply(longhandToken, component.value);
 | 
						|
      tokensSequence.push(longhandToken);
 | 
						|
 | 
						|
      nonInheritComponents[component.name] = deepClone(component);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  nameMetadata = joinMetadata(inheritComponents, 1);
 | 
						|
  shorthandToken[1].push(nameMetadata);
 | 
						|
 | 
						|
  valueMetadata = joinMetadata(inheritComponents, 2);
 | 
						|
  shorthandToken[2].push(valueMetadata);
 | 
						|
 | 
						|
  tokensSequence.unshift(shorthandToken);
 | 
						|
 | 
						|
  return [tokensSequence, newProperty, nonInheritComponents];
 | 
						|
}
 | 
						|
 | 
						|
function findTokenIn(tokens, componentName) {
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  for (i = 0, l = tokens.length; i < l; i++) {
 | 
						|
    if (tokens[i][1][1] == componentName) {
 | 
						|
      return tokens[i];
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function replaceWithShorthand(properties, candidateComponents, shorthandName, validator) {
 | 
						|
  var descriptor = configuration[shorthandName];
 | 
						|
  var nameMetadata;
 | 
						|
  var valueMetadata;
 | 
						|
  var newValuePlaceholder = [
 | 
						|
    Token.PROPERTY,
 | 
						|
    [Token.PROPERTY_NAME, shorthandName],
 | 
						|
    [Token.PROPERTY_VALUE, descriptor.defaultValue]
 | 
						|
  ];
 | 
						|
  var all;
 | 
						|
  var insertAt = inferInsertAtFrom(properties, candidateComponents, shorthandName);
 | 
						|
 | 
						|
  var newProperty = wrapSingle(newValuePlaceholder);
 | 
						|
  newProperty.shorthand = true;
 | 
						|
  newProperty.dirty = true;
 | 
						|
  newProperty.multiplex = false;
 | 
						|
 | 
						|
  populateComponents([newProperty], validator, []);
 | 
						|
 | 
						|
  for (var i = 0, l = descriptor.components.length; i < l; i++) {
 | 
						|
    var component = candidateComponents[descriptor.components[i]];
 | 
						|
 | 
						|
    newProperty.components[i] = deepClone(component);
 | 
						|
    newProperty.important = component.important;
 | 
						|
    newProperty.multiplex = newProperty.multiplex || component.multiplex;
 | 
						|
 | 
						|
    all = component.all;
 | 
						|
  }
 | 
						|
 | 
						|
  for (var componentName in candidateComponents) {
 | 
						|
    candidateComponents[componentName].unused = true;
 | 
						|
  }
 | 
						|
 | 
						|
  nameMetadata = joinMetadata(candidateComponents, 1);
 | 
						|
  newValuePlaceholder[1].push(nameMetadata);
 | 
						|
 | 
						|
  valueMetadata = joinMetadata(candidateComponents, 2);
 | 
						|
  newValuePlaceholder[2].push(valueMetadata);
 | 
						|
 | 
						|
  newProperty.position = insertAt;
 | 
						|
  newProperty.all = all;
 | 
						|
  newProperty.all[insertAt] = newValuePlaceholder;
 | 
						|
 | 
						|
  properties.splice(insertAt, 1, newProperty);
 | 
						|
}
 | 
						|
 | 
						|
function inferInsertAtFrom(properties, candidateComponents, shorthandName) {
 | 
						|
  var candidateComponentNames = Object.keys(candidateComponents);
 | 
						|
  var firstCandidatePosition = candidateComponents[candidateComponentNames[0]].position;
 | 
						|
  var lastCandidatePosition = candidateComponents[candidateComponentNames[candidateComponentNames.length - 1]].position;
 | 
						|
 | 
						|
  if (shorthandName == 'border' && traversesVia(properties.slice(firstCandidatePosition, lastCandidatePosition), 'border-image')) {
 | 
						|
    return firstCandidatePosition;
 | 
						|
  }
 | 
						|
  return lastCandidatePosition;
 | 
						|
}
 | 
						|
 | 
						|
function traversesVia(properties, propertyName) {
 | 
						|
  for (var i = properties.length - 1; i >= 0; i--) {
 | 
						|
    if (properties[i].name == propertyName) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
module.exports = mergeIntoShorthands;
 |