shorthand-compactor.js 3.63 KB
var compactable = require('./compactable');
var deepClone = require('./clone').deep;
var hasInherit = require('./has-inherit');
var populateComponents = require('./populate-components');
var wrapSingle = require('./wrap-for-optimizing').single;
var everyCombination = require('./every-combination');

function mixedImportance(components) {
  var important;

  for (var name in components) {
    if (undefined !== important && components[name].important != important)
      return true;

    important = components[name].important;
  }

  return false;
}

function componentSourceMaps(components) {
  var sourceMapping = [];

  for (var name in components) {
    var component = components[name];
    var originalValue = component.all[component.position];
    var mapping = originalValue[0][originalValue[0].length - 1];

    if (Array.isArray(mapping))
      Array.prototype.push.apply(sourceMapping, mapping);
  }

  return sourceMapping;
}

function replaceWithShorthand(properties, candidateComponents, name, sourceMaps, validator) {
  var descriptor = compactable[name];
  var newValuePlaceholder = [[name], [descriptor.defaultValue]];
  var all;

  var newProperty = wrapSingle(newValuePlaceholder);
  newProperty.shorthand = true;
  newProperty.dirty = true;

  populateComponents([newProperty], validator, []);

  for (var i = 0, l = descriptor.components.length; i < l; i++) {
    var component = candidateComponents[descriptor.components[i]];
    var canOverride = compactable[component.name].canOverride;

    if (hasInherit(component))
      return;

    if (!everyCombination(canOverride, newProperty.components[i], component, validator))
      return;

    newProperty.components[i] = deepClone(component);
    newProperty.important = component.important;

    all = component.all;
  }

  for (var componentName in candidateComponents) {
    candidateComponents[componentName].unused = true;
  }

  if (sourceMaps) {
    var sourceMapping = componentSourceMaps(candidateComponents);
    if (sourceMapping.length > 0)
      newValuePlaceholder[0].push(sourceMapping);
  }

  newProperty.position = all.length;
  newProperty.all = all;
  newProperty.all.push(newValuePlaceholder);

  properties.push(newProperty);
}

function invalidateOrCompact(properties, position, candidates, sourceMaps, validator) {
  var property = properties[position];

  for (var name in candidates) {
    if (undefined !== property && name == property.name)
      continue;

    var descriptor = compactable[name];
    var candidateComponents = candidates[name];
    if (descriptor.components.length > Object.keys(candidateComponents).length) {
      delete candidates[name];
      continue;
    }

    if (mixedImportance(candidateComponents))
      continue;

    replaceWithShorthand(properties, candidateComponents, name, sourceMaps, validator);
  }
}

function compactShortands(properties, sourceMaps, validator) {
  var candidates = {};

  if (properties.length < 3)
    return;

  for (var i = 0, l = properties.length; i < l; i++) {
    var property = properties[i];
    if (property.unused)
      continue;

    if (property.hack)
      continue;

    if (property.variable)
      continue;

    var descriptor = compactable[property.name];
    if (!descriptor || !descriptor.componentOf)
      continue;

    if (property.shorthand) {
      invalidateOrCompact(properties, i, candidates, sourceMaps, validator);
    } else {
      var componentOf = descriptor.componentOf;
      candidates[componentOf] = candidates[componentOf] || {};
      candidates[componentOf][property.name] = property;
    }
  }

  invalidateOrCompact(properties, i, candidates, sourceMaps, validator);
}

module.exports = compactShortands;