code:
var InvalidPropertyError = require('../invalid-property-error');

var wrapSingle = require('../wrap-for-optimizing').single;

var Token = require('../../tokenizer/token');
var Marker = require('../../tokenizer/marker');

var formatPosition = require('../../utils/format-position');

function _anyIsInherit(values) {
  var i, l;

  for (i = 0, l = values.length; i < l; i++) {
    if (values[i][1] == 'inherit') {
      return true;
    }
  }

  return false;
}

function _colorFilter(validator) {
  return function(value) {
    return value[1] == 'invert' || validator.isColor(value[1]) || validator.isPrefixed(value[1]);
  };
}

function _styleFilter(validator) {
  return function(value) {
    return value[1] != 'inherit' && validator.isStyleKeyword(value[1]) && !validator.isColorFunction(value[1]);
  };
}

function _wrapDefault(name, property, configuration) {
  var descriptor = configuration[name];
  if (descriptor.doubleValues && descriptor.defaultValue.length == 2) {
    return wrapSingle([
      Token.PROPERTY,
      [Token.PROPERTY_NAME, name],
      [Token.PROPERTY_VALUE, descriptor.defaultValue[0]],
      [Token.PROPERTY_VALUE, descriptor.defaultValue[1]]
    ]);
  } if (descriptor.doubleValues && descriptor.defaultValue.length == 1) {
    return wrapSingle([
      Token.PROPERTY,
      [Token.PROPERTY_NAME, name],
      [Token.PROPERTY_VALUE, descriptor.defaultValue[0]]
    ]);
  }
  return wrapSingle([
    Token.PROPERTY,
    [Token.PROPERTY_NAME, name],
    [Token.PROPERTY_VALUE, descriptor.defaultValue]
  ]);
}

function _widthFilter(validator) {
  return function(value) {
    return value[1] != 'inherit'
      && (validator.isWidth(value[1]) || validator.isUnit(value[1]) || validator.isDynamicUnit(value[1]))
      && !validator.isStyleKeyword(value[1])
      && !validator.isColorFunction(value[1]);
  };
}

function animation(property, configuration, validator) {
  var duration = _wrapDefault(property.name + '-duration', property, configuration);
  var timing = _wrapDefault(property.name + '-timing-function', property, configuration);
  var delay = _wrapDefault(property.name + '-delay', property, configuration);
  var iteration = _wrapDefault(property.name + '-iteration-count', property, configuration);
  var direction = _wrapDefault(property.name + '-direction', property, configuration);
  var fill = _wrapDefault(property.name + '-fill-mode', property, configuration);
  var play = _wrapDefault(property.name + '-play-state', property, configuration);
  var name = _wrapDefault(property.name + '-name', property, configuration);
  var components = [duration, timing, delay, iteration, direction, fill, play, name];
  var values = property.value;
  var value;
  var durationSet = false;
  var timingSet = false;
  var delaySet = false;
  var iterationSet = false;
  var directionSet = false;
  var fillSet = false;
  var playSet = false;
  var nameSet = false;
  var i;
  var l;

  if (property.value.length == 1 && property.value[0][1] == 'inherit') {
    // eslint-disable-next-line max-len
    duration.value = timing.value = delay.value = iteration.value = direction.value = fill.value = play.value = name.value = property.value;
    return components;
  }

  if (values.length > 1 && _anyIsInherit(values)) {
    throw new InvalidPropertyError('Invalid animation values at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  }

  for (i = 0, l = values.length; i < l; i++) {
    value = values[i];

    if (validator.isTime(value[1]) && !durationSet) {
      duration.value = [value];
      durationSet = true;
    } else if (validator.isTime(value[1]) && !delaySet) {
      delay.value = [value];
      delaySet = true;
    } else if ((validator.isGlobal(value[1]) || validator.isTimingFunction(value[1])) && !timingSet) {
      timing.value = [value];
      timingSet = true;
    } else if ((validator.isAnimationIterationCountKeyword(value[1])
      || validator.isPositiveNumber(value[1]))
      && !iterationSet) {
      iteration.value = [value];
      iterationSet = true;
    } else if (validator.isAnimationDirectionKeyword(value[1]) && !directionSet) {
      direction.value = [value];
      directionSet = true;
    } else if (validator.isAnimationFillModeKeyword(value[1]) && !fillSet) {
      fill.value = [value];
      fillSet = true;
    } else if (validator.isAnimationPlayStateKeyword(value[1]) && !playSet) {
      play.value = [value];
      playSet = true;
    } else if ((validator.isAnimationNameKeyword(value[1]) || validator.isIdentifier(value[1])) && !nameSet) {
      name.value = [value];
      nameSet = true;
    } else {
      throw new InvalidPropertyError('Invalid animation value at ' + formatPosition(value[2][0]) + '. Ignoring.');
    }
  }

  return components;
}

function background(property, configuration, validator) {
  var image = _wrapDefault('background-image', property, configuration);
  var position = _wrapDefault('background-position', property, configuration);
  var size = _wrapDefault('background-size', property, configuration);
  var repeat = _wrapDefault('background-repeat', property, configuration);
  var attachment = _wrapDefault('background-attachment', property, configuration);
  var origin = _wrapDefault('background-origin', property, configuration);
  var clip = _wrapDefault('background-clip', property, configuration);
  var color = _wrapDefault('background-color', property, configuration);
  var components = [image, position, size, repeat, attachment, origin, clip, color];
  var values = property.value;

  var positionSet = false;
  var clipSet = false;
  var originSet = false;
  var repeatSet = false;

  var anyValueSet = false;

  if (property.value.length == 1 && property.value[0][1] == 'inherit') {
    // NOTE: 'inherit' is not a valid value for background-attachment
    color.value = image.value = repeat.value = position.value = size.value = origin.value = clip.value = property.value;
    return components;
  }

  if (property.value.length == 1 && property.value[0][1] == '0 0') {
    return components;
  }

  for (var i = values.length - 1; i >= 0; i--) {
    var value = values[i];

    if (validator.isBackgroundAttachmentKeyword(value[1])) {
      attachment.value = [value];
      anyValueSet = true;
    } else if (validator.isBackgroundClipKeyword(value[1]) || validator.isBackgroundOriginKeyword(value[1])) {
      if (clipSet) {
        origin.value = [value];
        originSet = true;
      } else {
        clip.value = [value];
        clipSet = true;
      }
      anyValueSet = true;
    } else if (validator.isBackgroundRepeatKeyword(value[1])) {
      if (repeatSet) {
        repeat.value.unshift(value);
      } else {
        repeat.value = [value];
        repeatSet = true;
      }
      anyValueSet = true;
    } else if (validator.isBackgroundPositionKeyword(value[1])
    || validator.isBackgroundSizeKeyword(value[1])
    || validator.isUnit(value[1])
    || validator.isDynamicUnit(value[1])) {
      if (i > 0) {
        var previousValue = values[i - 1];

        if (previousValue[1] == Marker.FORWARD_SLASH) {
          size.value = [value];
        } else if (i > 1 && values[i - 2][1] == Marker.FORWARD_SLASH) {
          size.value = [previousValue, value];
          i -= 2;
        } else {
          if (!positionSet) { position.value = []; }

          position.value.unshift(value);
          positionSet = true;
        }
      } else {
        if (!positionSet) { position.value = []; }

        position.value.unshift(value);
        positionSet = true;
      }
      anyValueSet = true;
    } else if ((color.value[0][1] == configuration[color.name].defaultValue || color.value[0][1] == 'none') && (validator.isColor(value[1]) || validator.isPrefixed(value[1]))) {
      color.value = [value];
      anyValueSet = true;
    } else if (validator.isUrl(value[1]) || validator.isFunction(value[1])) {
      image.value = [value];
      anyValueSet = true;
    }
  }

  if (clipSet && !originSet) { origin.value = clip.value.slice(0); }

  if (!anyValueSet) {
    throw new InvalidPropertyError('Invalid background value at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  }

  return components;
}

function borderRadius(property, configuration) {
  var values = property.value;
  var splitAt = -1;

  for (var i = 0, l = values.length; i < l; i++) {
    if (values[i][1] == Marker.FORWARD_SLASH) {
      splitAt = i;
      break;
    }
  }

  if (splitAt === 0 || splitAt === values.length - 1) {
    throw new InvalidPropertyError('Invalid border-radius value at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  }

  var target = _wrapDefault(property.name, property, configuration);
  target.value = splitAt > -1
    ? values.slice(0, splitAt)
    : values.slice(0);
  target.components = fourValues(target, configuration);

  var remainder = _wrapDefault(property.name, property, configuration);
  remainder.value = splitAt > -1
    ? values.slice(splitAt + 1)
    : values.slice(0);
  remainder.components = fourValues(remainder, configuration);

  for (var j = 0; j < 4; j++) {
    target.components[j].multiplex = true;
    target.components[j].value = target.components[j].value.concat(remainder.components[j].value);
  }

  return target.components;
}

function font(property, configuration, validator) {
  var style = _wrapDefault('font-style', property, configuration);
  var variant = _wrapDefault('font-variant', property, configuration);
  var weight = _wrapDefault('font-weight', property, configuration);
  var stretch = _wrapDefault('font-stretch', property, configuration);
  var size = _wrapDefault('font-size', property, configuration);
  var height = _wrapDefault('line-height', property, configuration);
  var family = _wrapDefault('font-family', property, configuration);
  var components = [style, variant, weight, stretch, size, height, family];
  var values = property.value;
  var fuzzyMatched = 4; // style, variant, weight, and stretch
  var index = 0;
  var isStretchSet = false;
  var isStretchValid;
  var isStyleSet = false;
  var isStyleValid;
  var isVariantSet = false;
  var isVariantValid;
  var isWeightSet = false;
  var isWeightValid;
  var appendableFamilyName = false;

  if (!values[index]) {
    throw new InvalidPropertyError('Missing font values at ' + formatPosition(property.all[property.position][1][2][0]) + '. Ignoring.');
  }

  if (values.length == 1 && values[0][1] == 'inherit') {
    style.value = variant.value = weight.value = stretch.value = size.value = height.value = family.value = values;
    return components;
  }

  if (values.length == 1
    && (validator.isFontKeyword(values[0][1])
    || validator.isGlobal(values[0][1])
    || validator.isPrefixed(values[0][1]))
  ) {
    values[0][1] = Marker.INTERNAL + values[0][1];
    style.value = variant.value = weight.value = stretch.value = size.value = height.value = family.value = values;
    return components;
  }

  if (values.length < 2 || !_anyIsFontSize(values, validator) || !_anyIsFontFamily(values, validator)) {
    throw new InvalidPropertyError('Invalid font values at ' + formatPosition(property.all[property.position][1][2][0]) + '. Ignoring.');
  }

  if (values.length > 1 && _anyIsInherit(values)) {
    throw new InvalidPropertyError('Invalid font values at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  }

  // fuzzy match style, variant, weight, and stretch on first elements
  while (index < fuzzyMatched) {
    isStretchValid = validator.isFontStretchKeyword(values[index][1]) || validator.isGlobal(values[index][1]);
    isStyleValid = validator.isFontStyleKeyword(values[index][1]) || validator.isGlobal(values[index][1]);
    isVariantValid = validator.isFontVariantKeyword(values[index][1]) || validator.isGlobal(values[index][1]);
    isWeightValid = validator.isFontWeightKeyword(values[index][1]) || validator.isGlobal(values[index][1]);

    if (isStyleValid && !isStyleSet) {
      style.value = [values[index]];
      isStyleSet = true;
    } else if (isVariantValid && !isVariantSet) {
      variant.value = [values[index]];
      isVariantSet = true;
    } else if (isWeightValid && !isWeightSet) {
      weight.value = [values[index]];
      isWeightSet = true;
    } else if (isStretchValid && !isStretchSet) {
      stretch.value = [values[index]];
      isStretchSet = true;
    } else if (isStyleValid
      && isStyleSet
      || isVariantValid
      && isVariantSet
      || isWeightValid
      && isWeightSet
      || isStretchValid
      && isStretchSet) {
      throw new InvalidPropertyError('Invalid font style / variant / weight / stretch value at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
    } else {
      break;
    }

    index++;
  }

  // now comes font-size ...
  if (validator.isFontSizeKeyword(values[index][1])
    || validator.isUnit(values[index][1])
    && !validator.isDynamicUnit(values[index][1])) {
    size.value = [values[index]];
    index++;
  } else {
    throw new InvalidPropertyError('Missing font size at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  }

  if (!values[index]) {
    throw new InvalidPropertyError('Missing font family at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  }

  // ... and perhaps line-height
  if (values[index]
    && values[index][1] == Marker.FORWARD_SLASH
    && values[index + 1]
    && (validator.isLineHeightKeyword(values[index + 1][1])
    || validator.isUnit(values[index + 1][1])
    || validator.isNumber(values[index + 1][1]))) {
    height.value = [values[index + 1]];
    index++;
    index++;
  }

  // ... and whatever comes next is font-family
  family.value = [];

  while (values[index]) {
    if (values[index][1] == Marker.COMMA) {
      appendableFamilyName = false;
    } else {
      if (appendableFamilyName) {
        family.value[family.value.length - 1][1] += Marker.SPACE + values[index][1];
      } else {
        family.value.push(values[index]);
      }

      appendableFamilyName = true;
    }

    index++;
  }

  if (family.value.length === 0) {
    throw new InvalidPropertyError('Missing font family at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  }

  return components;
}

function _anyIsFontSize(values, validator) {
  var value;
  var i, l;

  for (i = 0, l = values.length; i < l; i++) {
    value = values[i];

    if (validator.isFontSizeKeyword(value[1])
      || validator.isUnit(value[1])
      && !validator.isDynamicUnit(value[1])
      || validator.isFunction(value[1])) {
      return true;
    }
  }

  return false;
}

function _anyIsFontFamily(values, validator) {
  var value;
  var i, l;

  for (i = 0, l = values.length; i < l; i++) {
    value = values[i];

    if (validator.isIdentifier(value[1]) || validator.isQuotedText(value[1])) {
      return true;
    }
  }

  return false;
}

function fourValues(property, configuration) {
  var componentNames = configuration[property.name].components;
  var components = [];
  var value = property.value;

  if (value.length < 1) { return []; }

  if (value.length < 2) { value[1] = value[0].slice(0); }
  if (value.length < 3) { value[2] = value[0].slice(0); }
  if (value.length < 4) { value[3] = value[1].slice(0); }

  for (var i = componentNames.length - 1; i >= 0; i--) {
    var component = wrapSingle([
      Token.PROPERTY,
      [Token.PROPERTY_NAME, componentNames[i]]
    ]);
    component.value = [value[i]];
    components.unshift(component);
  }

  return components;
}

function multiplex(splitWith) {
  return function(property, configuration, validator) {
    var splitsAt = [];
    var values = property.value;
    var i, j, l, m;

    // find split commas
    for (i = 0, l = values.length; i < l; i++) {
      if (values[i][1] == ',') { splitsAt.push(i); }
    }

    if (splitsAt.length === 0) { return splitWith(property, configuration, validator); }

    var splitComponents = [];

    // split over commas, and into components
    for (i = 0, l = splitsAt.length; i <= l; i++) {
      var from = i === 0 ? 0 : splitsAt[i - 1] + 1;
      var to = i < l ? splitsAt[i] : values.length;

      var _property = _wrapDefault(property.name, property, configuration);
      _property.value = values.slice(from, to);

      if (_property.value.length > 0) {
        splitComponents.push(splitWith(_property, configuration, validator));
      }
    }

    var components = splitComponents[0];

    // group component values from each split
    for (i = 0, l = components.length; i < l; i++) {
      components[i].multiplex = true;

      for (j = 1, m = splitComponents.length; j < m; j++) {
        components[i].value.push([Token.PROPERTY_VALUE, Marker.COMMA]);
        Array.prototype.push.apply(components[i].value, splitComponents[j][i].value);
      }
    }

    return components;
  };
}

function listStyle(property, configuration, validator) {
  var type = _wrapDefault('list-style-type', property, configuration);
  var position = _wrapDefault('list-style-position', property, configuration);
  var image = _wrapDefault('list-style-image', property, configuration);
  var components = [type, position, image];

  if (property.value.length == 1 && property.value[0][1] == 'inherit') {
    type.value = position.value = image.value = [property.value[0]];
    return components;
  }

  var values = property.value.slice(0);
  var total = values.length;
  var index = 0;

  // `image` first...
  for (index = 0, total = values.length; index < total; index++) {
    if (validator.isUrl(values[index][1]) || values[index][1] == '0') {
      image.value = [values[index]];
      values.splice(index, 1);
      break;
    }
  }

  // ... then `position`
  for (index = 0, total = values.length; index < total; index++) {
    if (validator.isListStylePositionKeyword(values[index][1])) {
      position.value = [values[index]];
      values.splice(index, 1);
      break;
    }
  }

  // ... and what's left is a `type`
  if (values.length > 0 && (validator.isListStyleTypeKeyword(values[0][1]) || validator.isIdentifier(values[0][1]))) {
    type.value = [values[0]];
  }

  return components;
}

function transition(property, configuration, validator) {
  var prop = _wrapDefault(property.name + '-property', property, configuration);
  var duration = _wrapDefault(property.name + '-duration', property, configuration);
  var timing = _wrapDefault(property.name + '-timing-function', property, configuration);
  var delay = _wrapDefault(property.name + '-delay', property, configuration);
  var components = [prop, duration, timing, delay];
  var values = property.value;
  var value;
  var durationSet = false;
  var delaySet = false;
  var propSet = false;
  var timingSet = false;
  var i;
  var l;

  if (property.value.length == 1 && property.value[0][1] == 'inherit') {
    prop.value = duration.value = timing.value = delay.value = property.value;
    return components;
  }

  if (values.length > 1 && _anyIsInherit(values)) {
    throw new InvalidPropertyError('Invalid animation values at ' + formatPosition(values[0][2][0]) + '. Ignoring.');
  }

  for (i = 0, l = values.length; i < l; i++) {
    value = values[i];

    if (validator.isTime(value[1]) && !durationSet) {
      duration.value = [value];
      durationSet = true;
    } else if (validator.isTime(value[1]) && !delaySet) {
      delay.value = [value];
      delaySet = true;
    } else if ((validator.isGlobal(value[1]) || validator.isTimingFunction(value[1])) && !timingSet) {
      timing.value = [value];
      timingSet = true;
    } else if (validator.isIdentifier(value[1]) && !propSet) {
      prop.value = [value];
      propSet = true;
    } else {
      throw new InvalidPropertyError('Invalid animation value at ' + formatPosition(value[2][0]) + '. Ignoring.');
    }
  }

  return components;
}

function widthStyleColor(property, configuration, validator) {
  var descriptor = configuration[property.name];
  var components = [
    _wrapDefault(descriptor.components[0], property, configuration),
    _wrapDefault(descriptor.components[1], property, configuration),
    _wrapDefault(descriptor.components[2], property, configuration)
  ];
  var color, style, width;

  for (var i = 0; i < 3; i++) {
    var component = components[i];

    if (component.name.indexOf('color') > 0) { color = component; } else if (component.name.indexOf('style') > 0) { style = component; } else { width = component; }
  }

  if ((property.value.length == 1 && property.value[0][1] == 'inherit')
      || (property.value.length == 3 && property.value[0][1] == 'inherit' && property.value[1][1] == 'inherit' && property.value[2][1] == 'inherit')) {
    color.value = style.value = width.value = [property.value[0]];
    return components;
  }

  var values = property.value.slice(0);
  var match, matches;

  // NOTE: usually users don't follow the required order of parts in this shorthand,
  // so we'll try to parse it caring as little about order as possible

  if (values.length > 0) {
    matches = values.filter(_widthFilter(validator));
    match = matches.length > 1 && (matches[0][1] == 'none' || matches[0][1] == 'auto') ? matches[1] : matches[0];
    if (match) {
      width.value = [match];
      values.splice(values.indexOf(match), 1);
    }
  }

  if (values.length > 0) {
    match = values.filter(_styleFilter(validator))[0];
    if (match) {
      style.value = [match];
      values.splice(values.indexOf(match), 1);
    }
  }

  if (values.length > 0) {
    match = values.filter(_colorFilter(validator))[0];
    if (match) {
      color.value = [match];
      values.splice(values.indexOf(match), 1);
    }
  }

  return components;
}

module.exports = {
  animation: animation,
  background: background,
  border: widthStyleColor,
  borderRadius: borderRadius,
  font: font,
  fourValues: fourValues,
  listStyle: listStyle,
  multiplex: multiplex,
  outline: widthStyleColor,
  transition: transition
};


code:
var understandable = require('./properties/understandable');

function animationIterationCount(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true)
    && !(validator.isAnimationIterationCountKeyword(value2) || validator.isPositiveNumber(value2))) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  }

  return validator.isAnimationIterationCountKeyword(value2) || validator.isPositiveNumber(value2);
}

function animationName(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true)
    && !(validator.isAnimationNameKeyword(value2) || validator.isIdentifier(value2))) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  }

  return validator.isAnimationNameKeyword(value2) || validator.isIdentifier(value2);
}

function areSameFunction(validator, value1, value2) {
  if (!validator.isFunction(value1) || !validator.isFunction(value2)) {
    return false;
  }

  var function1Name = value1.substring(0, value1.indexOf('('));
  var function2Name = value2.substring(0, value2.indexOf('('));

  var function1Value = value1.substring(function1Name.length + 1, value1.length - 1);
  var function2Value = value2.substring(function2Name.length + 1, value2.length - 1);

  if (validator.isFunction(function1Value) || validator.isFunction(function2Value)) {
    return function1Name === function2Name && areSameFunction(validator, function1Value, function2Value);
  }
  return function1Name === function2Name;
}

function backgroundPosition(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true)
    && !(validator.isBackgroundPositionKeyword(value2) || validator.isGlobal(value2))) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  } if (validator.isBackgroundPositionKeyword(value2) || validator.isGlobal(value2)) {
    return true;
  }

  return unit(validator, value1, value2);
}

function backgroundSize(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true)
    && !(validator.isBackgroundSizeKeyword(value2) || validator.isGlobal(value2))) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  } if (validator.isBackgroundSizeKeyword(value2) || validator.isGlobal(value2)) {
    return true;
  }

  return unit(validator, value1, value2);
}

function color(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true) && !validator.isColor(value2)) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  } if (!validator.colorOpacity && (validator.isRgbColor(value1) || validator.isHslColor(value1))) {
    return false;
  } if (!validator.colorOpacity && (validator.isRgbColor(value2) || validator.isHslColor(value2))) {
    return false;
  } if (!validator.colorHexAlpha && (validator.isHexAlphaColor(value1) || validator.isHexAlphaColor(value2))) {
    return false;
  } if (validator.isColor(value1) && validator.isColor(value2)) {
    return true;
  }

  return sameFunctionOrValue(validator, value1, value2);
}

function components(overrideCheckers) {
  return function(validator, value1, value2, position) {
    return overrideCheckers[position](validator, value1, value2);
  };
}

function fontFamily(validator, value1, value2) {
  return understandable(validator, value1, value2, 0, true);
}

function image(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true) && !validator.isImage(value2)) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  } if (validator.isImage(value2)) {
    return true;
  } if (validator.isImage(value1)) {
    return false;
  }

  return sameFunctionOrValue(validator, value1, value2);
}

function keyword(propertyName) {
  return function(validator, value1, value2) {
    if (!understandable(validator, value1, value2, 0, true) && !validator.isKeyword(propertyName)(value2)) {
      return false;
    } if (validator.isVariable(value1) && validator.isVariable(value2)) {
      return true;
    }

    return validator.isKeyword(propertyName)(value2);
  };
}

function keywordWithGlobal(propertyName) {
  return function(validator, value1, value2) {
    if (!understandable(validator, value1, value2, 0, true)
      && !(validator.isKeyword(propertyName)(value2) || validator.isGlobal(value2))) {
      return false;
    } if (validator.isVariable(value1) && validator.isVariable(value2)) {
      return true;
    }

    return validator.isKeyword(propertyName)(value2) || validator.isGlobal(value2);
  };
}

function propertyName(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true) && !validator.isIdentifier(value2)) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  }

  return validator.isIdentifier(value2);
}

function sameFunctionOrValue(validator, value1, value2) {
  return areSameFunction(validator, value1, value2)
    ? true
    : value1 === value2;
}

function textShadow(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true)
    && !(validator.isUnit(value2)
    || validator.isColor(value2)
    || validator.isGlobal(value2))) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  }

  return validator.isUnit(value2) || validator.isColor(value2) || validator.isGlobal(value2);
}

function time(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true) && !validator.isTime(value2)) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  } if (validator.isTime(value1) && !validator.isTime(value2)) {
    return false;
  } if (validator.isTime(value2)) {
    return true;
  } if (validator.isTime(value1)) {
    return false;
  } if (validator.isFunction(value1)
    && !validator.isPrefixed(value1)
    && validator.isFunction(value2)
    && !validator.isPrefixed(value2)) {
    return true;
  }

  return sameFunctionOrValue(validator, value1, value2);
}

function timingFunction(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true)
    && !(validator.isTimingFunction(value2) || validator.isGlobal(value2))) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  }

  return validator.isTimingFunction(value2) || validator.isGlobal(value2);
}

function unit(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true) && !validator.isUnit(value2)) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  } if (validator.isUnit(value1) && !validator.isUnit(value2)) {
    return false;
  } if (validator.isUnit(value2)) {
    return true;
  } if (validator.isUnit(value1)) {
    return false;
  } if (validator.isFunction(value1)
    && !validator.isPrefixed(value1)
    && validator.isFunction(value2)
    && !validator.isPrefixed(value2)) {
    return true;
  }

  return sameFunctionOrValue(validator, value1, value2);
}

function unitOrKeywordWithGlobal(propertyName) {
  var byKeyword = keywordWithGlobal(propertyName);

  return function(validator, value1, value2) {
    return unit(validator, value1, value2) || byKeyword(validator, value1, value2);
  };
}

function unitOrNumber(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true)
    && !(validator.isUnit(value2)
    || validator.isNumber(value2))) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  } if ((validator.isUnit(value1)
    || validator.isNumber(value1))
    && !(validator.isUnit(value2)
    || validator.isNumber(value2))) {
    return false;
  } if (validator.isUnit(value2) || validator.isNumber(value2)) {
    return true;
  } if (validator.isUnit(value1) || validator.isNumber(value1)) {
    return false;
  } if (validator.isFunction(value1)
    && !validator.isPrefixed(value1)
    && validator.isFunction(value2)
    && !validator.isPrefixed(value2)) {
    return true;
  }

  return sameFunctionOrValue(validator, value1, value2);
}

function zIndex(validator, value1, value2) {
  if (!understandable(validator, value1, value2, 0, true) && !validator.isZIndex(value2)) {
    return false;
  } if (validator.isVariable(value1) && validator.isVariable(value2)) {
    return true;
  }

  return validator.isZIndex(value2);
}

module.exports = {
  generic: {
    color: color,
    components: components,
    image: image,
    propertyName: propertyName,
    time: time,
    timingFunction: timingFunction,
    unit: unit,
    unitOrNumber: unitOrNumber
  },
  property: {
    animationDirection: keywordWithGlobal('animation-direction'),
    animationFillMode: keyword('animation-fill-mode'),
    animationIterationCount: animationIterationCount,
    animationName: animationName,
    animationPlayState: keywordWithGlobal('animation-play-state'),
    backgroundAttachment: keyword('background-attachment'),
    backgroundClip: keywordWithGlobal('background-clip'),
    backgroundOrigin: keyword('background-origin'),
    backgroundPosition: backgroundPosition,
    backgroundRepeat: keyword('background-repeat'),
    backgroundSize: backgroundSize,
    bottom: unitOrKeywordWithGlobal('bottom'),
    borderCollapse: keyword('border-collapse'),
    borderStyle: keywordWithGlobal('*-style'),
    clear: keywordWithGlobal('clear'),
    cursor: keywordWithGlobal('cursor'),
    display: keywordWithGlobal('display'),
    float: keywordWithGlobal('float'),
    left: unitOrKeywordWithGlobal('left'),
    fontFamily: fontFamily,
    fontStretch: keywordWithGlobal('font-stretch'),
    fontStyle: keywordWithGlobal('font-style'),
    fontVariant: keywordWithGlobal('font-variant'),
    fontWeight: keywordWithGlobal('font-weight'),
    listStyleType: keywordWithGlobal('list-style-type'),
    listStylePosition: keywordWithGlobal('list-style-position'),
    outlineStyle: keywordWithGlobal('*-style'),
    overflow: keywordWithGlobal('overflow'),
    position: keywordWithGlobal('position'),
    right: unitOrKeywordWithGlobal('right'),
    textAlign: keywordWithGlobal('text-align'),
    textDecoration: keywordWithGlobal('text-decoration'),
    textOverflow: keywordWithGlobal('text-overflow'),
    textShadow: textShadow,
    top: unitOrKeywordWithGlobal('top'),
    transform: sameFunctionOrValue,
    verticalAlign: unitOrKeywordWithGlobal('vertical-align'),
    visibility: keywordWithGlobal('visibility'),
    whiteSpace: keywordWithGlobal('white-space'),
    zIndex: zIndex
  }
};

license: mit


code:
var sameVendorPrefixes = require('../../vendor-prefixes').same;

function understandable(validator, value1, value2, _position, isPaired) {
  if (!sameVendorPrefixes(value1, value2)) {
    return false;
  }

  if (isPaired && validator.isVariable(value1) !== validator.isVariable(value2)) {
    return false;
  }

  return true;
}

module.exports = understandable;


code:
var shallowClone = require('../clone').shallow;

var Token = require('../../tokenizer/token');
var Marker = require('../../tokenizer/marker');

function isInheritOnly(values) {
  for (var i = 0, l = values.length; i < l; i++) {
    var value = values[i][1];

    if (value != 'inherit' && value != Marker.COMMA && value != Marker.FORWARD_SLASH) { return false; }
  }

  return true;
}

function background(property, configuration, lastInMultiplex) {
  var components = property.components;
  var restored = [];
  var needsOne, needsBoth;

  function restoreValue(component) {
    Array.prototype.unshift.apply(restored, component.value);
  }

  function isDefaultValue(component) {
    var descriptor = configuration[component.name];

    if (descriptor.doubleValues && descriptor.defaultValue.length == 1) {
      return component.value[0][1] == descriptor.defaultValue[0]
        && (component.value[1]
          ? component.value[1][1] == descriptor.defaultValue[0]
          : true);
    } if (descriptor.doubleValues && descriptor.defaultValue.length != 1) {
      return component.value[0][1] == descriptor.defaultValue[0]
      && ((component.value[1] ? component.value[1][1] : component.value[0][1])
        == descriptor.defaultValue[1]);
    }
    return component.value[0][1] == descriptor.defaultValue;
  }

  for (var i = components.length - 1; i >= 0; i--) {
    var component = components[i];
    var isDefault = isDefaultValue(component);

    if (component.name == 'background-clip') {
      var originComponent = components[i - 1];
      var isOriginDefault = isDefaultValue(originComponent);

      needsOne = component.value[0][1] == originComponent.value[0][1];

      needsBoth = !needsOne && (
        (isOriginDefault && !isDefault)
        || (!isOriginDefault && !isDefault)
        || (!isOriginDefault && isDefault && component.value[0][1] != originComponent.value[0][1]));

      if (needsOne) {
        restoreValue(originComponent);
      } else if (needsBoth) {
        restoreValue(component);
        restoreValue(originComponent);
      }

      i--;
    } else if (component.name == 'background-size') {
      var positionComponent = components[i - 1];
      var isPositionDefault = isDefaultValue(positionComponent);

      needsOne = !isPositionDefault && isDefault;

      needsBoth = !needsOne
        && (isPositionDefault && !isDefault || !isPositionDefault && !isDefault);

      if (needsOne) {
        restoreValue(positionComponent);
      } else if (needsBoth) {
        restoreValue(component);
        restored.unshift([Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]);
        restoreValue(positionComponent);
      } else if (positionComponent.value.length == 1) {
        restoreValue(positionComponent);
      }

      i--;
    } else {
      if (isDefault || configuration[component.name].multiplexLastOnly && !lastInMultiplex) { continue; }

      restoreValue(component);
    }
  }

  if (restored.length === 0 && property.value.length == 1 && property.value[0][1] == '0') { restored.push(property.value[0]); }

  if (restored.length === 0) { restored.push([Token.PROPERTY_VALUE, configuration[property.name].defaultValue]); }

  if (isInheritOnly(restored)) { return [restored[0]]; }

  return restored;
}

function borderRadius(property) {
  if (property.multiplex) {
    var horizontal = shallowClone(property);
    var vertical = shallowClone(property);

    for (var i = 0; i < 4; i++) {
      var component = property.components[i];

      var horizontalComponent = shallowClone(property);
      horizontalComponent.value = [component.value[0]];
      horizontal.components.push(horizontalComponent);

      var verticalComponent = shallowClone(property);
      // FIXME: only shorthand compactor (see breakup#borderRadius) knows that border radius
      // longhands have two values, whereas tokenizer does not care about populating 2nd value
      // if it's missing, hence this fallback
      verticalComponent.value = [component.value[1] || component.value[0]];
      vertical.components.push(verticalComponent);
    }

    var horizontalValues = fourValues(horizontal);
    var verticalValues = fourValues(vertical);

    if (horizontalValues.length == verticalValues.length
        && horizontalValues[0][1] == verticalValues[0][1]
        && (horizontalValues.length > 1 ? horizontalValues[1][1] == verticalValues[1][1] : true)
        && (horizontalValues.length > 2 ? horizontalValues[2][1] == verticalValues[2][1] : true)
        && (horizontalValues.length > 3 ? horizontalValues[3][1] == verticalValues[3][1] : true)) {
      return horizontalValues;
    }
    return horizontalValues.concat([[Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]]).concat(verticalValues);
  }
  return fourValues(property);
}

function font(property, configuration) {
  var components = property.components;
  var restored = [];
  var component;
  var componentIndex = 0;
  var fontFamilyIndex = 0;

  if (property.value[0][1].indexOf(Marker.INTERNAL) === 0) {
    property.value[0][1] = property.value[0][1].substring(Marker.INTERNAL.length);
    return property.value;
  }

  // first four components are optional
  while (componentIndex < 4) {
    component = components[componentIndex];

    if (component.value[0][1] != configuration[component.name].defaultValue) {
      Array.prototype.push.apply(restored, component.value);
    }

    componentIndex++;
  }

  // then comes font-size
  Array.prototype.push.apply(restored, components[componentIndex].value);
  componentIndex++;

  // then may come line-height
  if (components[componentIndex].value[0][1] != configuration[components[componentIndex].name].defaultValue) {
    Array.prototype.push.apply(restored, [[Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]]);
    Array.prototype.push.apply(restored, components[componentIndex].value);
  }

  componentIndex++;

  // then comes font-family
  while (components[componentIndex].value[fontFamilyIndex]) {
    restored.push(components[componentIndex].value[fontFamilyIndex]);

    if (components[componentIndex].value[fontFamilyIndex + 1]) {
      restored.push([Token.PROPERTY_VALUE, Marker.COMMA]);
    }

    fontFamilyIndex++;
  }

  if (isInheritOnly(restored)) {
    return [restored[0]];
  }

  return restored;
}

function fourValues(property) {
  var components = property.components;
  var value1 = components[0].value[0];
  var value2 = components[1].value[0];
  var value3 = components[2].value[0];
  var value4 = components[3].value[0];

  if (value1[1] == value2[1] && value1[1] == value3[1] && value1[1] == value4[1]) {
    return [value1];
  } if (value1[1] == value3[1] && value2[1] == value4[1]) {
    return [value1, value2];
  } if (value2[1] == value4[1]) {
    return [value1, value2, value3];
  }
  return [value1, value2, value3, value4];
}

function multiplex(restoreWith) {
  return function(property, configuration) {
    if (!property.multiplex) { return restoreWith(property, configuration, true); }

    var multiplexSize = 0;
    var restored = [];
    var componentMultiplexSoFar = {};
    var i, l;

    // At this point we don't know what's the multiplex size, e.g. how many background layers are there
    for (i = 0, l = property.components[0].value.length; i < l; i++) {
      if (property.components[0].value[i][1] == Marker.COMMA) { multiplexSize++; }
    }

    for (i = 0; i <= multiplexSize; i++) {
      var _property = shallowClone(property);

      // We split multiplex into parts and restore them one by one
      for (var j = 0, m = property.components.length; j < m; j++) {
        var componentToClone = property.components[j];
        var _component = shallowClone(componentToClone);
        _property.components.push(_component);

        // The trick is some properties has more than one value, so we iterate over values looking for
        // a multiplex separator - a comma
        for (var k = componentMultiplexSoFar[_component.name] || 0, n = componentToClone.value.length; k < n; k++) {
          if (componentToClone.value[k][1] == Marker.COMMA) {
            componentMultiplexSoFar[_component.name] = k + 1;
            break;
          }

          _component.value.push(componentToClone.value[k]);
        }
      }

      // No we can restore shorthand value
      var lastInMultiplex = i == multiplexSize;
      var _restored = restoreWith(_property, configuration, lastInMultiplex);
      Array.prototype.push.apply(restored, _restored);

      if (i < multiplexSize) { restored.push([Token.PROPERTY_VALUE, Marker.COMMA]); }
    }

    return restored;
  };
}

function withoutDefaults(property, configuration) {
  var components = property.components;
  var restored = [];

  for (var i = components.length - 1; i >= 0; i--) {
    var component = components[i];
    var descriptor = configuration[component.name];

    if (component.value[0][1] != descriptor.defaultValue || ('keepUnlessDefault' in descriptor) && !isDefault(components, configuration, descriptor.keepUnlessDefault)) {
      restored.unshift(component.value[0]);
    }
  }

  if (restored.length === 0) { restored.push([Token.PROPERTY_VALUE, configuration[property.name].defaultValue]); }

  if (isInheritOnly(restored)) { return [restored[0]]; }

  return restored;
}

function isDefault(components, configuration, propertyName) {
  var component;
  var i, l;

  for (i = 0, l = components.length; i < l; i++) {
    component = components[i];

    if (component.name == propertyName && component.value[0][1] == configuration[propertyName].defaultValue) {
      return true;
    }
  }

  return false;
}

module.exports = {
  background: background,
  borderRadius: borderRadius,
  font: font,
  fourValues: fourValues,
  multiplex: multiplex,
  withoutDefaults: withoutDefaults
};


code:
var Hack = {
  ASTERISK: 'asterisk',
  BANG: 'bang',
  BACKSLASH: 'backslash',
  UNDERSCORE: 'underscore'
};

module.exports = Hack;


code:
function InvalidPropertyError(message) {
  this.name = 'InvalidPropertyError';
  this.message = message;
  this.stack = (new Error()).stack;
}

InvalidPropertyError.prototype = Object.create(Error.prototype);
InvalidPropertyError.prototype.constructor = InvalidPropertyError;

module.exports = InvalidPropertyError;


code:
function level0Optimize(tokens) {
  // noop as level 0 means no optimizations!
  return tokens;
}

module.exports = level0Optimize;


code:
var sortSelectors = require('./sort-selectors');
var tidyRules = require('./tidy-rules');
var tidyBlock = require('./tidy-block');
var tidyAtRule = require('./tidy-at-rule');

var Hack = require('../hack');
var removeUnused = require('../remove-unused');
var restoreFromOptimizing = require('../restore-from-optimizing');
var wrapForOptimizing = require('../wrap-for-optimizing').all;

var configuration = require('../configuration');
var optimizers = require('./value-optimizers');

var OptimizationLevel = require('../../options/optimization-level').OptimizationLevel;

var Token = require('../../tokenizer/token');
var Marker = require('../../tokenizer/marker');

var formatPosition = require('../../utils/format-position');

var serializeRules = require('../../writer/one-time').rules;

var CHARSET_TOKEN = '@charset';
var CHARSET_REGEXP = new RegExp('^' + CHARSET_TOKEN, 'i');

var DEFAULT_ROUNDING_PRECISION = require('../../options/rounding-precision').DEFAULT;

var VARIABLE_PROPERTY_NAME_PATTERN = /^--\S+$/;
var PROPERTY_NAME_PATTERN = /^(?:-chrome-|-[\w-]+\w|\w[\w-]+\w|\w{1,})$/;
var IMPORT_PREFIX_PATTERN = /^@import/i;
var URL_PREFIX_PATTERN = /^url\(/i;

function startsAsUrl(value) {
  return URL_PREFIX_PATTERN.test(value);
}

function isImport(token) {
  return IMPORT_PREFIX_PATTERN.test(token[1]);
}

function isLegacyFilter(property) {
  var value;

  if (property.name == 'filter' || property.name == '-ms-filter') {
    value = property.value[0][1];

    return value.indexOf('progid') > -1
      || value.indexOf('alpha') === 0
      || value.indexOf('chroma') === 0;
  }
  return false;
}

function noop() {}

function noopValueOptimizer(_name, value, _options) { return value; }

function optimizeBody(rule, properties, context) {
  var options = context.options;
  var valueOptimizers;
  var property, name, type, value;
  var propertyToken;
  var propertyOptimizer;
  var serializedRule = serializeRules(rule);
  var _properties = wrapForOptimizing(properties);
  var pluginValueOptimizers = context.options.plugins.level1Value;
  var pluginPropertyOptimizers = context.options.plugins.level1Property;
  var isVariable;
  var i, l;

  for (i = 0, l = _properties.length; i < l; i++) {
    var j, k, m, n;

    property = _properties[i];
    name = property.name;
    propertyOptimizer = configuration[name] && configuration[name].propertyOptimizer || noop;
    valueOptimizers = configuration[name] && configuration[name].valueOptimizers || [optimizers.whiteSpace];
    isVariable = VARIABLE_PROPERTY_NAME_PATTERN.test(name);

    if (isVariable) {
      valueOptimizers = options.variableOptimizers.length > 0
        ? options.variableOptimizers
        : [optimizers.whiteSpace];
    }

    if (!isVariable && !PROPERTY_NAME_PATTERN.test(name)) {
      propertyToken = property.all[property.position];
      context.warnings.push('Invalid property name \'' + name + '\' at ' + formatPosition(propertyToken[1][2][0]) + '. Ignoring.');
      property.unused = true;
      continue;
    }

    if (property.value.length === 0) {
      propertyToken = property.all[property.position];
      context.warnings.push('Empty property \'' + name + '\' at ' + formatPosition(propertyToken[1][2][0]) + '. Ignoring.');
      property.unused = true;
      continue;
    }

    if (property.hack && (
      (property.hack[0] == Hack.ASTERISK || property.hack[0] == Hack.UNDERSCORE)
        && !options.compatibility.properties.iePrefixHack
        || property.hack[0] == Hack.BACKSLASH && !options.compatibility.properties.ieSuffixHack
        || property.hack[0] == Hack.BANG && !options.compatibility.properties.ieBangHack)) {
      property.unused = true;
      continue;
    }

    if (!options.compatibility.properties.ieFilters && isLegacyFilter(property)) {
      property.unused = true;
      continue;
    }

    if (property.block) {
      optimizeBody(rule, property.value[0][1], context);
      continue;
    }

    for (j = 0, m = property.value.length; j < m; j++) {
      type = property.value[j][0];
      value = property.value[j][1];

      if (type == Token.PROPERTY_BLOCK) {
        property.unused = true;
        context.warnings.push('Invalid value token at ' + formatPosition(value[0][1][2][0]) + '. Ignoring.');
        break;
      }

      if (startsAsUrl(value) && !context.validator.isUrl(value)) {
        property.unused = true;
        context.warnings.push('Broken URL \'' + value + '\' at ' + formatPosition(property.value[j][2][0]) + '. Ignoring.');
        break;
      }

      for (k = 0, n = valueOptimizers.length; k < n; k++) {
        value = valueOptimizers[k](name, value, options);
      }

      for (k = 0, n = pluginValueOptimizers.length; k < n; k++) {
        value = pluginValueOptimizers[k](name, value, options);
      }

      property.value[j][1] = value;
    }

    propertyOptimizer(serializedRule, property, options);

    for (j = 0, m = pluginPropertyOptimizers.length; j < m; j++) {
      pluginPropertyOptimizers[j](serializedRule, property, options);
    }
  }

  restoreFromOptimizing(_properties);
  removeUnused(_properties);
  removeComments(properties, options);
}

function removeComments(tokens, options) {
  var token;
  var i;

  for (i = 0; i < tokens.length; i++) {
    token = tokens[i];

    if (token[0] != Token.COMMENT) {
      continue;
    }

    optimizeComment(token, options);

    if (token[1].length === 0) {
      tokens.splice(i, 1);
      i--;
    }
  }
}

function optimizeComment(token, options) {
  if (token[1][2] == Marker.EXCLAMATION && (options.level[OptimizationLevel.One].specialComments == 'all' || options.commentsKept < options.level[OptimizationLevel.One].specialComments)) {
    options.commentsKept++;
    return;
  }

  token[1] = [];
}

function cleanupCharsets(tokens) {
  var hasCharset = false;

  for (var i = 0, l = tokens.length; i < l; i++) {
    var token = tokens[i];

    if (token[0] != Token.AT_RULE) { continue; }

    if (!CHARSET_REGEXP.test(token[1])) { continue; }

    if (hasCharset || token[1].indexOf(CHARSET_TOKEN) == -1) {
      tokens.splice(i, 1);
      i--;
      l--;
    } else {
      hasCharset = true;
      tokens.splice(i, 1);
      tokens.unshift([Token.AT_RULE, token[1].replace(CHARSET_REGEXP, CHARSET_TOKEN)]);
    }
  }
}

function buildUnitRegexp(options) {
  var units = ['px', 'em', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', '%'];
  var otherUnits = ['ch', 'rem', 'vh', 'vm', 'vmax', 'vmin', 'vw'];

  otherUnits.forEach(function(unit) {
    if (options.compatibility.units[unit]) {
      units.push(unit);
    }
  });

  return new RegExp('(^|\\s|\\(|,)0(?:' + units.join('|') + ')(\\W|$)', 'g');
}

function buildPrecisionOptions(roundingPrecision) {
  var precisionOptions = {
    matcher: null,
    units: {}
  };
  var optimizable = [];
  var unit;
  var value;

  for (unit in roundingPrecision) {
    value = roundingPrecision[unit];

    if (value != DEFAULT_ROUNDING_PRECISION) {
      precisionOptions.units[unit] = {};
      precisionOptions.units[unit].value = value;
      precisionOptions.units[unit].multiplier = 10 ** value;

      optimizable.push(unit);
    }
  }

  if (optimizable.length > 0) {
    precisionOptions.enabled = true;
    precisionOptions.decimalPointMatcher = new RegExp('(\\d)\\.($|' + optimizable.join('|') + ')($|\\W)', 'g');
    precisionOptions.zeroMatcher = new RegExp('(\\d*)(\\.\\d+)(' + optimizable.join('|') + ')', 'g');
  }

  return precisionOptions;
}

function buildVariableOptimizers(options) {
  return options.level[OptimizationLevel.One].variableValueOptimizers.map(function(optimizer) {
    if (typeof (optimizer) == 'string') {
      return optimizers[optimizer] || noopValueOptimizer;
    }

    return optimizer;
  });
}

function level1Optimize(tokens, context) {
  var options = context.options;
  var levelOptions = options.level[OptimizationLevel.One];
  var ie7Hack = options.compatibility.selectors.ie7Hack;
  var adjacentSpace = options.compatibility.selectors.adjacentSpace;
  var spaceAfterClosingBrace = options.compatibility.properties.spaceAfterClosingBrace;
  var format = options.format;
  var mayHaveCharset = false;
  var afterRules = false;

  options.unitsRegexp = options.unitsRegexp || buildUnitRegexp(options);
  options.precision = options.precision || buildPrecisionOptions(levelOptions.roundingPrecision);
  options.commentsKept = options.commentsKept || 0;
  options.variableOptimizers = options.variableOptimizers || buildVariableOptimizers(options);

  for (var i = 0, l = tokens.length; i < l; i++) {
    var token = tokens[i];

    switch (token[0]) {
    case Token.AT_RULE:
      token[1] = isImport(token) && afterRules ? '' : token[1];
      token[1] = levelOptions.tidyAtRules ? tidyAtRule(token[1]) : token[1];
      mayHaveCharset = true;
      break;
    case Token.AT_RULE_BLOCK:
      optimizeBody(token[1], token[2], context);
      afterRules = true;
      break;
    case Token.NESTED_BLOCK:
      token[1] = levelOptions.tidyBlockScopes ? tidyBlock(token[1], spaceAfterClosingBrace) : token[1];
      level1Optimize(token[2], context);
      afterRules = true;
      break;
    case Token.COMMENT:
      optimizeComment(token, options);
      break;
    case Token.RULE:
      token[1] = levelOptions.tidySelectors
        ? tidyRules(token[1], !ie7Hack, adjacentSpace, format, context.warnings)
        : token[1];
      token[1] = token[1].length > 1 ? sortSelectors(token[1], levelOptions.selectorsSortingMethod) : token[1];
      optimizeBody(token[1], token[2], context);
      afterRules = true;
      break;
    }

    if (token[0] == Token.COMMENT
      && token[1].length === 0
      || levelOptions.removeEmpty
      && (token[1].length === 0 || (token[2] && token[2].length === 0))) {
      tokens.splice(i, 1);
      i--;
      l--;
    }
  }

  if (levelOptions.cleanupCharsets && mayHaveCharset) {
    cleanupCharsets(tokens);
  }

  return tokens;
}

module.exports = level1Optimize;


code:
module.exports = {
  background: require('./property-optimizers/background').level1.property,
  boxShadow: require('./property-optimizers/box-shadow').level1.property,
  borderRadius: require('./property-optimizers/border-radius').level1.property,
  filter: require('./property-optimizers/filter').level1.property,
  fontWeight: require('./property-optimizers/font-weight').level1.property,
  margin: require('./property-optimizers/margin').level1.property,
  outline: require('./property-optimizers/outline').level1.property,
  padding: require('./property-optimizers/padding').level1.property
};


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var plugin = {
  level1: {
    property: function background(_rule, property, options) {
      var values = property.value;

      if (!options.level[OptimizationLevel.One].optimizeBackground) {
        return;
      }

      if (values.length == 1 && values[0][1] == 'none') {
        values[0][1] = '0 0';
      }

      if (values.length == 1 && values[0][1] == 'transparent') {
        values[0][1] = '0 0';
      }
    }
  }
};

module.exports = plugin;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var plugin = {
  level1: {
    property: function borderRadius(_rule, property, options) {
      var values = property.value;

      if (!options.level[OptimizationLevel.One].optimizeBorderRadius) {
        return;
      }

      if (values.length == 3 && values[1][1] == '/' && values[0][1] == values[2][1]) {
        property.value.splice(1);
        property.dirty = true;
      } else if (values.length == 5 && values[2][1] == '/' && values[0][1] == values[3][1] && values[1][1] == values[4][1]) {
        property.value.splice(2);
        property.dirty = true;
      } else if (values.length == 7 && values[3][1] == '/' && values[0][1] == values[4][1] && values[1][1] == values[5][1] && values[2][1] == values[6][1]) {
        property.value.splice(3);
        property.dirty = true;
      } else if (values.length == 9 && values[4][1] == '/' && values[0][1] == values[5][1] && values[1][1] == values[6][1] && values[2][1] == values[7][1] && values[3][1] == values[8][1]) {
        property.value.splice(4);
        property.dirty = true;
      }
    }
  }
};

module.exports = plugin;


code:
var plugin = {
  level1: {
    property: function boxShadow(_rule, property) {
      var values = property.value;

      // remove multiple zeros
      if (values.length == 4 && values[0][1] === '0' && values[1][1] === '0' && values[2][1] === '0' && values[3][1] === '0') {
        property.value.splice(2);
        property.dirty = true;
      }
    }
  }
};

module.exports = plugin;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var ALPHA_OR_CHROMA_FILTER_PATTERN = /progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\W)/;
var NO_SPACE_AFTER_COMMA_PATTERN = /,(\S)/g;
var WHITESPACE_AROUND_EQUALS_PATTERN = / ?= ?/g;

var plugin = {
  level1: {
    property: function filter(_rule, property, options) {
      if (!options.compatibility.properties.ieFilters) {
        return;
      }

      if (!options.level[OptimizationLevel.One].optimizeFilter) {
        return;
      }

      if (property.value.length == 1) {
        property.value[0][1] = property.value[0][1].replace(
          ALPHA_OR_CHROMA_FILTER_PATTERN,
          function(match, filter, suffix) {
            return filter.toLowerCase() + suffix;
          }
        );
      }

      property.value[0][1] = property.value[0][1]
        .replace(NO_SPACE_AFTER_COMMA_PATTERN, ', $1')
        .replace(WHITESPACE_AROUND_EQUALS_PATTERN, '=');
    }
  }
};

module.exports = plugin;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var plugin = {
  level1: {
    property: function fontWeight(_rule, property, options) {
      var value = property.value[0][1];

      if (!options.level[OptimizationLevel.One].optimizeFontWeight) {
        return;
      }

      if (value == 'normal') {
        value = '400';
      } else if (value == 'bold') {
        value = '700';
      }

      property.value[0][1] = value;
    }
  }
};

module.exports = plugin;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var plugin = {
  level1: {
    property: function margin(_rule, property, options) {
      var values = property.value;

      if (!options.level[OptimizationLevel.One].replaceMultipleZeros) {
        return;
      }

      // remove multiple zeros
      if (values.length == 4 && values[0][1] === '0' && values[1][1] === '0' && values[2][1] === '0' && values[3][1] === '0') {
        property.value.splice(1);
        property.dirty = true;
      }
    }
  }
};

module.exports = plugin;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var plugin = {
  level1: {
    property: function outline(_rule, property, options) {
      var values = property.value;

      if (!options.level[OptimizationLevel.One].optimizeOutline) {
        return;
      }

      if (values.length == 1 && values[0][1] == 'none') {
        values[0][1] = '0';
      }
    }
  }
};

module.exports = plugin;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

function isNegative(value) {
  return value && value[1][0] == '-' && parseFloat(value[1]) < 0;
}

var plugin = {
  level1: {
    property: function padding(_rule, property, options) {
      var values = property.value;

      // remove multiple zeros
      if (values.length == 4 && values[0][1] === '0' && values[1][1] === '0' && values[2][1] === '0' && values[3][1] === '0') {
        property.value.splice(1);
        property.dirty = true;
      }

      // remove negative paddings
      if (options.level[OptimizationLevel.One].removeNegativePaddings
        && (
          isNegative(property.value[0])
          || isNegative(property.value[1])
          || isNegative(property.value[2])
          || isNegative(property.value[3])
        )) {
        property.unused = true;
      }
    }
  }
};

module.exports = plugin;


code:
var naturalCompare = require('../../utils/natural-compare');

function naturalSorter(scope1, scope2) {
  return naturalCompare(scope1[1], scope2[1]);
}

function standardSorter(scope1, scope2) {
  return scope1[1] > scope2[1] ? 1 : -1;
}

function sortSelectors(selectors, method) {
  switch (method) {
  case 'natural':
    return selectors.sort(naturalSorter);
  case 'standard':
    return selectors.sort(standardSorter);
  case 'none':
  case false:
    return selectors;
  }
}

module.exports = sortSelectors;


code:
function tidyAtRule(value) {
  return value
    .replace(/\s+/g, ' ')
    .replace(/url\(\s+/g, 'url(')
    .replace(/\s+\)/g, ')')
    .trim();
}

module.exports = tidyAtRule;


code:
var SUPPORTED_COMPACT_BLOCK_MATCHER = /^@media\W/;
var SUPPORTED_QUOTE_REMOVAL_MATCHER = /^@(?:keyframes|-moz-keyframes|-o-keyframes|-webkit-keyframes)\W/;

function tidyBlock(values, spaceAfterClosingBrace) {
  var withoutSpaceAfterClosingBrace;
  var withoutQuotes;
  var i;

  for (i = values.length - 1; i >= 0; i--) {
    withoutSpaceAfterClosingBrace = !spaceAfterClosingBrace && SUPPORTED_COMPACT_BLOCK_MATCHER.test(values[i][1]);
    withoutQuotes = SUPPORTED_QUOTE_REMOVAL_MATCHER.test(values[i][1]);

    values[i][1] = values[i][1]
      .replace(/\n|\r\n/g, ' ')
      .replace(/\s+/g, ' ')
      .replace(/(,|:|\() /g, '$1')
      .replace(/ \)/g, ')');

    if (withoutQuotes) {
      values[i][1] = values[i][1]
        .replace(/'([a-zA-Z][a-zA-Z\d\-_]+)'/, '$1')
        .replace(/"([a-zA-Z][a-zA-Z\d\-_]+)"/, '$1');
    }

    if (withoutSpaceAfterClosingBrace) {
      values[i][1] = values[i][1]
        .replace(/\) /g, ')');
    }
  }

  return values;
}

module.exports = tidyBlock;


code:
var Spaces = require('../../options/format').Spaces;
var Marker = require('../../tokenizer/marker');
var formatPosition = require('../../utils/format-position');

var CASE_ATTRIBUTE_PATTERN = /[\s"'][iI]\s*\]/;
var CASE_RESTORE_PATTERN = /([\d\w])([iI])\]/g;
var DOUBLE_QUOTE_CASE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"([iI])/g;
var DOUBLE_QUOTE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"(\s|\])/g;
var HTML_COMMENT_PATTERN = /^(?:(?:<!--|-->)\s*)+/;
var SINGLE_QUOTE_CASE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'([iI])/g;
var SINGLE_QUOTE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'(\s|\])/g;
var RELATION_PATTERN = /[>+~]/;
var WHITESPACE_PATTERN = /\s/;

var ASTERISK_PLUS_HTML_HACK = '*+html ';
var ASTERISK_FIRST_CHILD_PLUS_HTML_HACK = '*:first-child+html ';
var LESS_THAN = '<';

var PSEUDO_CLASSES_WITH_SELECTORS = [
  ':current',
  ':future',
  ':has',
  ':host',
  ':host-context',
  ':is',
  ':not',
  ':past',
  ':where'
];

function hasInvalidCharacters(value) {
  var isEscaped;
  var isInvalid = false;
  var character;
  var isQuote = false;
  var i, l;

  for (i = 0, l = value.length; i < l; i++) {
    character = value[i];

    if (isEscaped) {
      // continue as always
    } else if (character == Marker.SINGLE_QUOTE || character == Marker.DOUBLE_QUOTE) {
      isQuote = !isQuote;
    } else if (!isQuote
      && (character == Marker.CLOSE_CURLY_BRACKET
        || character == Marker.EXCLAMATION
        || character == LESS_THAN
        || character == Marker.SEMICOLON)
    ) {
      isInvalid = true;
      break;
    } else if (!isQuote && i === 0 && RELATION_PATTERN.test(character)) {
      isInvalid = true;
      break;
    }

    isEscaped = character == Marker.BACK_SLASH;
  }

  return isInvalid;
}

function removeWhitespace(value, format) {
  var stripped = [];
  var character;
  var isNewLineNix;
  var isNewLineWin;
  var isEscaped;
  var wasEscaped;
  var isQuoted;
  var isSingleQuoted;
  var isDoubleQuoted;
  var isAttribute;
  var isRelation;
  var isWhitespace;
  var isSpaceAwarePseudoClass;
  var roundBracketLevel = 0;
  var wasComma = false;
  var wasRelation = false;
  var wasWhitespace = false;
  var withCaseAttribute = CASE_ATTRIBUTE_PATTERN.test(value);
  var spaceAroundRelation = format && format.spaces[Spaces.AroundSelectorRelation];
  var i, l;

  for (i = 0, l = value.length; i < l; i++) {
    character = value[i];

    isNewLineNix = character == Marker.NEW_LINE_NIX;
    isNewLineWin = character == Marker.NEW_LINE_NIX && value[i - 1] == Marker.CARRIAGE_RETURN;
    isQuoted = isSingleQuoted || isDoubleQuoted;
    isRelation = !isAttribute && !isEscaped && roundBracketLevel === 0 && RELATION_PATTERN.test(character);
    isWhitespace = WHITESPACE_PATTERN.test(character);
    isSpaceAwarePseudoClass = roundBracketLevel == 1 && character == Marker.CLOSE_ROUND_BRACKET
      ? false
      : isSpaceAwarePseudoClass
        || (roundBracketLevel === 0 && character == Marker.COLON && isPseudoClassWithSelectors(value, i));

    if (wasEscaped && isQuoted && isNewLineWin) {
      // swallow escaped new windows lines in comments
      stripped.pop();
      stripped.pop();
    } else if (isEscaped && isQuoted && isNewLineNix) {
      // swallow escaped new *nix lines in comments
      stripped.pop();
    } else if (isEscaped) {
      stripped.push(character);
    } else if (character == Marker.OPEN_SQUARE_BRACKET && !isQuoted) {
      stripped.push(character);
      isAttribute = true;
    } else if (character == Marker.CLOSE_SQUARE_BRACKET && !isQuoted) {
      stripped.push(character);
      isAttribute = false;
    } else if (character == Marker.OPEN_ROUND_BRACKET && !isQuoted) {
      stripped.push(character);
      roundBracketLevel++;
    } else if (character == Marker.CLOSE_ROUND_BRACKET && !isQuoted) {
      stripped.push(character);
      roundBracketLevel--;
    } else if (character == Marker.SINGLE_QUOTE && !isQuoted) {
      stripped.push(character);
      isSingleQuoted = true;
    } else if (character == Marker.DOUBLE_QUOTE && !isQuoted) {
      stripped.push(character);
      isDoubleQuoted = true;
    } else if (character == Marker.SINGLE_QUOTE && isQuoted) {
      stripped.push(character);
      isSingleQuoted = false;
    } else if (character == Marker.DOUBLE_QUOTE && isQuoted) {
      stripped.push(character);
      isDoubleQuoted = false;
    } else if (isWhitespace && wasRelation && !spaceAroundRelation) {
      continue;
    } else if (!isWhitespace && wasRelation && spaceAroundRelation) {
      stripped.push(Marker.SPACE);
      stripped.push(character);
    } else if (isWhitespace && !wasWhitespace && wasComma && roundBracketLevel > 0 && isSpaceAwarePseudoClass) {
      // skip space
    } else if (isWhitespace && !wasWhitespace && roundBracketLevel > 0 && isSpaceAwarePseudoClass) {
      stripped.push(character);
    } else if (isWhitespace && (isAttribute || roundBracketLevel > 0) && !isQuoted) {
      // skip space
    } else if (isWhitespace && wasWhitespace && !isQuoted) {
      // skip extra space
    } else if ((isNewLineWin || isNewLineNix) && (isAttribute || roundBracketLevel > 0) && isQuoted) {
      // skip newline
    } else if (isRelation && wasWhitespace && !spaceAroundRelation) {
      stripped.pop();
      stripped.push(character);
    } else if (isRelation && !wasWhitespace && spaceAroundRelation) {
      stripped.push(Marker.SPACE);
      stripped.push(character);
    } else if (isWhitespace) {
      stripped.push(Marker.SPACE);
    } else {
      stripped.push(character);
    }

    wasEscaped = isEscaped;
    isEscaped = character == Marker.BACK_SLASH;
    wasRelation = isRelation;
    wasWhitespace = isWhitespace;
    wasComma = character == Marker.COMMA;
  }

  return withCaseAttribute
    ? stripped.join('').replace(CASE_RESTORE_PATTERN, '$1 $2]')
    : stripped.join('');
}

function isPseudoClassWithSelectors(value, colonPosition) {
  var pseudoClass = value.substring(colonPosition, value.indexOf(Marker.OPEN_ROUND_BRACKET, colonPosition));

  return PSEUDO_CLASSES_WITH_SELECTORS.indexOf(pseudoClass) > -1;
}

function removeQuotes(value) {
  if (value.indexOf('\'') == -1 && value.indexOf('"') == -1) {
    return value;
  }

  return value
    .replace(SINGLE_QUOTE_CASE_PATTERN, '=$1 $2')
    .replace(SINGLE_QUOTE_PATTERN, '=$1$2')
    .replace(DOUBLE_QUOTE_CASE_PATTERN, '=$1 $2')
    .replace(DOUBLE_QUOTE_PATTERN, '=$1$2');
}

function replacePseudoClasses(value) {
  return value
    .replace('nth-child(1)', 'first-child')
    .replace('nth-of-type(1)', 'first-of-type')
    .replace('nth-of-type(even)', 'nth-of-type(2n)')
    .replace('nth-child(even)', 'nth-child(2n)')
    .replace('nth-of-type(2n+1)', 'nth-of-type(odd)')
    .replace('nth-child(2n+1)', 'nth-child(odd)')
    .replace('nth-last-child(1)', 'last-child')
    .replace('nth-last-of-type(1)', 'last-of-type')
    .replace('nth-last-of-type(even)', 'nth-last-of-type(2n)')
    .replace('nth-last-child(even)', 'nth-last-child(2n)')
    .replace('nth-last-of-type(2n+1)', 'nth-last-of-type(odd)')
    .replace('nth-last-child(2n+1)', 'nth-last-child(odd)');
}

function tidyRules(rules, removeUnsupported, adjacentSpace, format, warnings) {
  var list = [];
  var repeated = [];

  function removeHTMLComment(rule, match) {
    warnings.push('HTML comment \'' + match + '\' at ' + formatPosition(rule[2][0]) + '. Removing.');
    return '';
  }

  for (var i = 0, l = rules.length; i < l; i++) {
    var rule = rules[i];
    var reduced = rule[1];

    reduced = reduced.replace(HTML_COMMENT_PATTERN, removeHTMLComment.bind(null, rule));

    if (hasInvalidCharacters(reduced)) {
      warnings.push('Invalid selector \'' + rule[1] + '\' at ' + formatPosition(rule[2][0]) + '. Ignoring.');
      continue;
    }

    reduced = removeWhitespace(reduced, format);
    reduced = removeQuotes(reduced);

    if (adjacentSpace && reduced.indexOf('nav') > 0) {
      reduced = reduced.replace(/\+nav(\S|$)/, '+ nav$1');
    }

    if (removeUnsupported && reduced.indexOf(ASTERISK_PLUS_HTML_HACK) > -1) {
      continue;
    }

    if (removeUnsupported && reduced.indexOf(ASTERISK_FIRST_CHILD_PLUS_HTML_HACK) > -1) {
      continue;
    }

    if (reduced.indexOf('*') > -1) {
      reduced = reduced
        .replace(/\*([:#.[])/g, '$1')
        .replace(/^(:first-child)?\+html/, '*$1+html');
    }

    if (repeated.indexOf(reduced) > -1) {
      continue;
    }

    reduced = replacePseudoClasses(reduced);

    rule[1] = reduced;
    repeated.push(reduced);
    list.push(rule);
  }

  if (list.length == 1 && list[0][1].length === 0) {
    warnings.push('Empty selector \'' + list[0][1] + '\' at ' + formatPosition(list[0][2][0]) + '. Ignoring.');
    list = [];
  }

  return list;
}

module.exports = tidyRules;


code:
module.exports = {
  color: require('./value-optimizers/color').level1.value,
  degrees: require('./value-optimizers/degrees').level1.value,
  fraction: require('./value-optimizers/fraction').level1.value,
  precision: require('./value-optimizers/precision').level1.value,
  textQuotes: require('./value-optimizers/text-quotes').level1.value,
  time: require('./value-optimizers/time').level1.value,
  unit: require('./value-optimizers/unit').level1.value,
  urlPrefix: require('./value-optimizers/url-prefix').level1.value,
  urlQuotes: require('./value-optimizers/url-quotes').level1.value,
  urlWhiteSpace: require('./value-optimizers/url-whitespace').level1.value,
  whiteSpace: require('./value-optimizers/whitespace').level1.value,
  zero: require('./value-optimizers/zero').level1.value
};


code:
var shortenHex = require('./color/shorten-hex');
var shortenHsl = require('./color/shorten-hsl');
var shortenRgb = require('./color/shorten-rgb');

var split = require('../../../utils/split');

var ANY_COLOR_FUNCTION_PATTERN = /(rgb|rgba|hsl|hsla)\(([^()]+)\)/gi;
var COLOR_PREFIX_PATTERN = /#|rgb|hsl/gi;
var HEX_LONG_PATTERN = /(^|[^='"])#([0-9a-f]{6})/gi;
var HEX_SHORT_PATTERN = /(^|[^='"])#([0-9a-f]{3})/gi;
var HEX_VALUE_PATTERN = /[0-9a-f]/i;
var HSL_PATTERN = /hsl\((-?\d+),(-?\d+)%?,(-?\d+)%?\)/gi;
var RGBA_HSLA_PATTERN = /(rgb|hsl)a?\((-?\d+),(-?\d+%?),(-?\d+%?),(0*[1-9]+[0-9]*(\.?\d*)?)\)/gi;
var RGB_PATTERN = /rgb\((-?\d+),(-?\d+),(-?\d+)\)/gi;
var TRANSPARENT_FUNCTION_PATTERN = /(?:rgba|hsla)\(0,0%?,0%?,0\)/g;

var plugin = {
  level1: {
    value: function color(name, value, options) {
      if (!options.compatibility.properties.colors) {
        return value;
      }

      if (!value.match(COLOR_PREFIX_PATTERN)) {
        return shortenHex(value);
      }

      value = value
        .replace(RGBA_HSLA_PATTERN, function(match, colorFn, p1, p2, p3, alpha) {
          return (parseInt(alpha) >= 1 ? colorFn + '(' + [p1, p2, p3].join(',') + ')' : match);
        })
        .replace(RGB_PATTERN, function(match, red, green, blue) {
          return shortenRgb(red, green, blue);
        })
        .replace(HSL_PATTERN, function(match, hue, saturation, lightness) {
          return shortenHsl(hue, saturation, lightness);
        })
        .replace(HEX_LONG_PATTERN, function(match, prefix, color, at, inputValue) {
          var suffix = inputValue[at + match.length];

          if (suffix && HEX_VALUE_PATTERN.test(suffix)) {
            return match;
          } if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5]) {
            return (prefix + '#' + color[0] + color[2] + color[4]).toLowerCase();
          }
          return (prefix + '#' + color).toLowerCase();
        })
        .replace(HEX_SHORT_PATTERN, function(match, prefix, color) {
          return prefix + '#' + color.toLowerCase();
        })
        .replace(ANY_COLOR_FUNCTION_PATTERN, function(match, colorFunction, colorDef) {
          var tokens = colorDef.split(',');
          var colorFnLowercase = colorFunction && colorFunction.toLowerCase();
          var applies = (colorFnLowercase == 'hsl' && tokens.length == 3)
            || (colorFnLowercase == 'hsla' && tokens.length == 4)
            || (colorFnLowercase == 'rgb' && tokens.length === 3 && colorDef.indexOf('%') > 0)
            || (colorFnLowercase == 'rgba' && tokens.length == 4 && tokens[0].indexOf('%') > 0);

          if (!applies) {
            return match;
          }

          if (tokens[1].indexOf('%') == -1) {
            tokens[1] += '%';
          }

          if (tokens[2].indexOf('%') == -1) {
            tokens[2] += '%';
          }

          return colorFunction + '(' + tokens.join(',') + ')';
        });

      if (options.compatibility.colors.opacity && name.indexOf('background') == -1) {
        value = value.replace(TRANSPARENT_FUNCTION_PATTERN, function(match) {
          if (split(value, ',').pop().indexOf('gradient(') > -1) {
            return match;
          }

          return 'transparent';
        });
      }

      return shortenHex(value);
    }
  }
};

module.exports = plugin;


code:
var COLORS = {
  aliceblue: '#f0f8ff',
  antiquewhite: '#faebd7',
  aqua: '#0ff',
  aquamarine: '#7fffd4',
  azure: '#f0ffff',
  beige: '#f5f5dc',
  bisque: '#ffe4c4',
  black: '#000',
  blanchedalmond: '#ffebcd',
  blue: '#00f',
  blueviolet: '#8a2be2',
  brown: '#a52a2a',
  burlywood: '#deb887',
  cadetblue: '#5f9ea0',
  chartreuse: '#7fff00',
  chocolate: '#d2691e',
  coral: '#ff7f50',
  cornflowerblue: '#6495ed',
  cornsilk: '#fff8dc',
  crimson: '#dc143c',
  cyan: '#0ff',
  darkblue: '#00008b',
  darkcyan: '#008b8b',
  darkgoldenrod: '#b8860b',
  darkgray: '#a9a9a9',
  darkgreen: '#006400',
  darkgrey: '#a9a9a9',
  darkkhaki: '#bdb76b',
  darkmagenta: '#8b008b',
  darkolivegreen: '#556b2f',
  darkorange: '#ff8c00',
  darkorchid: '#9932cc',
  darkred: '#8b0000',
  darksalmon: '#e9967a',
  darkseagreen: '#8fbc8f',
  darkslateblue: '#483d8b',
  darkslategray: '#2f4f4f',
  darkslategrey: '#2f4f4f',
  darkturquoise: '#00ced1',
  darkviolet: '#9400d3',
  deeppink: '#ff1493',
  deepskyblue: '#00bfff',
  dimgray: '#696969',
  dimgrey: '#696969',
  dodgerblue: '#1e90ff',
  firebrick: '#b22222',
  floralwhite: '#fffaf0',
  forestgreen: '#228b22',
  fuchsia: '#f0f',
  gainsboro: '#dcdcdc',
  ghostwhite: '#f8f8ff',
  gold: '#ffd700',
  goldenrod: '#daa520',
  gray: '#808080',
  green: '#008000',
  greenyellow: '#adff2f',
  grey: '#808080',
  honeydew: '#f0fff0',
  hotpink: '#ff69b4',
  indianred: '#cd5c5c',
  indigo: '#4b0082',
  ivory: '#fffff0',
  khaki: '#f0e68c',
  lavender: '#e6e6fa',
  lavenderblush: '#fff0f5',
  lawngreen: '#7cfc00',
  lemonchiffon: '#fffacd',
  lightblue: '#add8e6',
  lightcoral: '#f08080',
  lightcyan: '#e0ffff',
  lightgoldenrodyellow: '#fafad2',
  lightgray: '#d3d3d3',
  lightgreen: '#90ee90',
  lightgrey: '#d3d3d3',
  lightpink: '#ffb6c1',
  lightsalmon: '#ffa07a',
  lightseagreen: '#20b2aa',
  lightskyblue: '#87cefa',
  lightslategray: '#778899',
  lightslategrey: '#778899',
  lightsteelblue: '#b0c4de',
  lightyellow: '#ffffe0',
  lime: '#0f0',
  limegreen: '#32cd32',
  linen: '#faf0e6',
  magenta: '#ff00ff',
  maroon: '#800000',
  mediumaquamarine: '#66cdaa',
  mediumblue: '#0000cd',
  mediumorchid: '#ba55d3',
  mediumpurple: '#9370db',
  mediumseagreen: '#3cb371',
  mediumslateblue: '#7b68ee',
  mediumspringgreen: '#00fa9a',
  mediumturquoise: '#48d1cc',
  mediumvioletred: '#c71585',
  midnightblue: '#191970',
  mintcream: '#f5fffa',
  mistyrose: '#ffe4e1',
  moccasin: '#ffe4b5',
  navajowhite: '#ffdead',
  navy: '#000080',
  oldlace: '#fdf5e6',
  olive: '#808000',
  olivedrab: '#6b8e23',
  orange: '#ffa500',
  orangered: '#ff4500',
  orchid: '#da70d6',
  palegoldenrod: '#eee8aa',
  palegreen: '#98fb98',
  paleturquoise: '#afeeee',
  palevioletred: '#db7093',
  papayawhip: '#ffefd5',
  peachpuff: '#ffdab9',
  peru: '#cd853f',
  pink: '#ffc0cb',
  plum: '#dda0dd',
  powderblue: '#b0e0e6',
  purple: '#800080',
  rebeccapurple: '#663399',
  red: '#f00',
  rosybrown: '#bc8f8f',
  royalblue: '#4169e1',
  saddlebrown: '#8b4513',
  salmon: '#fa8072',
  sandybrown: '#f4a460',
  seagreen: '#2e8b57',
  seashell: '#fff5ee',
  sienna: '#a0522d',
  silver: '#c0c0c0',
  skyblue: '#87ceeb',
  slateblue: '#6a5acd',
  slategray: '#708090',
  slategrey: '#708090',
  snow: '#fffafa',
  springgreen: '#00ff7f',
  steelblue: '#4682b4',
  tan: '#d2b48c',
  teal: '#008080',
  thistle: '#d8bfd8',
  tomato: '#ff6347',
  turquoise: '#40e0d0',
  violet: '#ee82ee',
  wheat: '#f5deb3',
  white: '#fff',
  whitesmoke: '#f5f5f5',
  yellow: '#ff0',
  yellowgreen: '#9acd32'
};

var toHex = {};
var toName = {};

for (var name in COLORS) {
  var hex = COLORS[name];

  if (name.length < hex.length) {
    toName[hex] = name;
  } else {
    toHex[name] = hex;
  }
}

var toHexPattern = new RegExp('(^| |,|\\))(' + Object.keys(toHex).join('|') + ')( |,|\\)|$)', 'ig');
var toNamePattern = new RegExp('(' + Object.keys(toName).join('|') + ')([^a-f0-9]|$)', 'ig');

function hexConverter(match, prefix, colorValue, suffix) {
  return prefix + toHex[colorValue.toLowerCase()] + suffix;
}

function nameConverter(match, colorValue, suffix) {
  return toName[colorValue.toLowerCase()] + suffix;
}

function shortenHex(value) {
  var hasHex = value.indexOf('#') > -1;
  var shortened = value.replace(toHexPattern, hexConverter);

  if (shortened != value) {
    shortened = shortened.replace(toHexPattern, hexConverter);
  }

  return hasHex
    ? shortened.replace(toNamePattern, nameConverter)
    : shortened;
}

module.exports = shortenHex;


code:
// HSL to RGB converter. Both methods adapted from:
// http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript

function hslToRgb(h, s, l) {
  var r, g, b;

  // normalize hue orientation b/w 0 and 360 degrees
  h %= 360;
  if (h < 0) { h += 360; }
  h = ~~h / 360;

  if (s < 0) { s = 0; } else if (s > 100) { s = 100; }
  s = ~~s / 100;

  if (l < 0) { l = 0; } else if (l > 100) { l = 100; }
  l = ~~l / 100;

  if (s === 0) {
    r = g = b = l; // achromatic
  } else {
    var q = l < 0.5
      ? l * (1 + s)
      : l + s - l * s;
    var p = 2 * l - q;
    r = hueToRgb(p, q, h + 1 / 3);
    g = hueToRgb(p, q, h);
    b = hueToRgb(p, q, h - 1 / 3);
  }

  return [~~(r * 255), ~~(g * 255), ~~(b * 255)];
}

function hueToRgb(p, q, t) {
  if (t < 0) { t += 1; }
  if (t > 1) { t -= 1; }
  if (t < 1 / 6) { return p + (q - p) * 6 * t; }
  if (t < 1 / 2) { return q; }
  if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; }
  return p;
}

function shortenHsl(hue, saturation, lightness) {
  var asRgb = hslToRgb(hue, saturation, lightness);
  var redAsHex = asRgb[0].toString(16);
  var greenAsHex = asRgb[1].toString(16);
  var blueAsHex = asRgb[2].toString(16);

  return '#'
    + ((redAsHex.length == 1 ? '0' : '') + redAsHex)
    + ((greenAsHex.length == 1 ? '0' : '') + greenAsHex)
    + ((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
}

module.exports = shortenHsl;


code:
function shortenRgb(red, green, blue) {
  var normalizedRed = Math.max(0, Math.min(parseInt(red), 255));
  var normalizedGreen = Math.max(0, Math.min(parseInt(green), 255));
  var normalizedBlue = Math.max(0, Math.min(parseInt(blue), 255));

  // Credit: Asen  http://jsbin.com/UPUmaGOc/2/edit?js,console
  return '#' + ('00000' + (normalizedRed << 16 | normalizedGreen << 8 | normalizedBlue).toString(16)).slice(-6);
}

module.exports = shortenRgb;


code:
var ZERO_DEG_PATTERN = /\(0deg\)/g;

var plugin = {
  level1: {
    value: function degrees(_name, value, options) {
      if (!options.compatibility.properties.zeroUnits) {
        return value;
      }

      if (value.indexOf('0deg') == -1) {
        return value;
      }

      return value.replace(ZERO_DEG_PATTERN, '(0)');
    }
  }
};

module.exports = plugin;


code:
var split = require('../../../utils/split');
var startsAsUrl = require('./starts-as-url');

var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var EXPRESSION_PATTERN = /^expression\(.*\)$/;
var ANY_FUNCTION_PATTERN = /^(-(?:moz|ms|o|webkit)-[a-z-]+|[a-z-]+)\((.+)\)$/;
var TOKEN_SEPARATOR_PATTERN = /([\s,/])/;

var DOT_ZERO_PATTERN = /(^|\D)\.0+(\D|$)/g;
var FRACTION_PATTERN = /\.([1-9]*)0+(\D|$)/g;
var LEADING_ZERO_FRACTION_PATTERN = /(^|\D)0\.(\d)/g;
var MINUS_ZERO_FRACTION_PATTERN = /([^\w\d-]|^)-0([^.]|$)/g;
var ZERO_PREFIXED_UNIT_PATTERN = /(^|\s)0+([1-9])/g;

function optimizeRecursively(value) {
  var functionTokens;
  var tokens;

  if (startsAsUrl(value)) {
    return value;
  }

  if (EXPRESSION_PATTERN.test(value)) {
    return value;
  }

  functionTokens = ANY_FUNCTION_PATTERN.exec(value);

  if (!functionTokens) {
    return optimizeFractions(value);
  }

  tokens = split(functionTokens[2], TOKEN_SEPARATOR_PATTERN)
    .map(function(token) { return optimizeRecursively(token); });

  return functionTokens[1] + '(' + tokens.join('') + ')';
}

function optimizeFractions(value) {
  if (value.indexOf('0') == -1) {
    return value;
  }

  if (value.indexOf('-') > -1) {
    value = value
      .replace(MINUS_ZERO_FRACTION_PATTERN, '$10$2')
      .replace(MINUS_ZERO_FRACTION_PATTERN, '$10$2');
  }

  return value
    .replace(ZERO_PREFIXED_UNIT_PATTERN, '$1$2')
    .replace(DOT_ZERO_PATTERN, '$10$2')
    .replace(FRACTION_PATTERN, function(match, nonZeroPart, suffix) {
      return (nonZeroPart.length > 0 ? '.' : '') + nonZeroPart + suffix;
    })
    .replace(LEADING_ZERO_FRACTION_PATTERN, '$1.$2');
}

var plugin = {
  level1: {
    value: function fraction(name, value, options) {
      if (!options.level[OptimizationLevel.One].replaceZeroUnits) {
        return value;
      }

      return optimizeRecursively(value);
    }
  }
};

module.exports = plugin;


code:
var plugin = {
  level1: {
    value: function precision(_name, value, options) {
      if (!options.precision.enabled || value.indexOf('.') === -1) {
        return value;
      }

      return value
        .replace(options.precision.decimalPointMatcher, '$1$2$3')
        .replace(options.precision.zeroMatcher, function(match, integerPart, fractionPart, unit) {
          var multiplier = options.precision.units[unit].multiplier;
          var parsedInteger = parseInt(integerPart);
          var integer = Number.isNaN(parsedInteger) ? 0 : parsedInteger;
          var fraction = parseFloat(fractionPart);

          return Math.round((integer + fraction) * multiplier) / multiplier + unit;
        });
    }
  }
};

module.exports = plugin;


code:
var URL_PREFIX_PATTERN = /^url\(/i;

function startsAsUrl(value) {
  return URL_PREFIX_PATTERN.test(value);
}

module.exports = startsAsUrl;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var LOCAL_PREFIX_PATTERN = /^local\(/i;
var QUOTED_PATTERN = /^('.*'|".*")$/;
var QUOTED_BUT_SAFE_PATTERN = /^['"][a-zA-Z][a-zA-Z\d\-_]+['"]$/;
// eslint-disable-next-line max-len
var GENERIC_FONT_FAMILY_PATTERN = /^['"](?:cursive|default|emoji|fangsong|fantasy|inherit|initial|math|monospace|revert|revert-layer|sans-serif|serif|system-ui|ui-monospace|ui-rounded|ui-sans-serif|ui-serif|unset)['"]$/;

var plugin = {
  level1: {
    value: function textQuotes(name, value, options) {
      if ((name == 'font-family' || name == 'font') && GENERIC_FONT_FAMILY_PATTERN.test(value)) {
        return value;
      }

      if (!options.level[OptimizationLevel.One].removeQuotes) {
        return value;
      }

      if (!QUOTED_PATTERN.test(value) && !LOCAL_PREFIX_PATTERN.test(value)) {
        return value;
      }

      return QUOTED_BUT_SAFE_PATTERN.test(value)
        ? value.substring(1, value.length - 1)
        : value;
    }
  }
};

module.exports = plugin;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var TIME_VALUE = /^(-?[\d.]+)(m?s)$/;

var plugin = {
  level1: {
    value: function time(name, value, options) {
      if (!options.level[OptimizationLevel.One].replaceTimeUnits) {
        return value;
      }

      if (!TIME_VALUE.test(value)) {
        return value;
      }

      return value.replace(TIME_VALUE, function(match, val, unit) {
        var newValue;

        if (unit == 'ms') {
          newValue = parseInt(val) / 1000 + 's';
        } else if (unit == 's') {
          newValue = parseFloat(val) * 1000 + 'ms';
        }

        return newValue.length < match.length ? newValue : match;
      });
    }
  }
};

module.exports = plugin;


code:
var WHOLE_PIXEL_VALUE = /(?:^|\s|\()(-?\d+)px/;

var plugin = {
  level1: {
    value: function unit(_name, value, options) {
      if (!WHOLE_PIXEL_VALUE.test(value)) {
        return value;
      }

      return value.replace(WHOLE_PIXEL_VALUE, function(match, val) {
        var newValue;
        var intVal = parseInt(val);

        if (intVal === 0) {
          return match;
        }

        if (options.compatibility.properties.shorterLengthUnits
          && options.compatibility.units.pt
          && intVal * 3 % 4 === 0) {
          newValue = intVal * 3 / 4 + 'pt';
        }

        if (options.compatibility.properties.shorterLengthUnits
          && options.compatibility.units.pc
          && intVal % 16 === 0) {
          newValue = intVal / 16 + 'pc';
        }

        if (options.compatibility.properties.shorterLengthUnits
          && options.compatibility.units.in
          && intVal % 96 === 0) {
          newValue = intVal / 96 + 'in';
        }

        if (newValue) {
          newValue = match.substring(0, match.indexOf(val)) + newValue;
        }

        return newValue && newValue.length < match.length ? newValue : match;
      });
    }
  }
};

module.exports = plugin;


code:
var startsAsUrl = require('./starts-as-url');

var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var URL_PREFIX_PATTERN = /^url\(/i;

var plugin = {
  level1: {
    value: function urlPrefix(_name, value, options) {
      if (!options.level[OptimizationLevel.One].normalizeUrls) {
        return value;
      }

      if (!startsAsUrl(value)) {
        return value;
      }

      return value.replace(URL_PREFIX_PATTERN, 'url(');
    }
  }
};

module.exports = plugin;


code:
var QUOTED_URL_PATTERN = /^url\(['"].+['"]\)$/;
var QUOTED_URL_WITH_WHITESPACE_PATTERN = /^url\(['"].*[*\s()'"].*['"]\)$/;
var QUOTES_PATTERN = /["']/g;
var URL_DATA_PATTERN = /^url\(['"]data:[^;]+;charset/;

var plugin = {
  level1: {
    value: function urlQuotes(_name, value, options) {
      if (options.compatibility.properties.urlQuotes) {
        return value;
      }

      return QUOTED_URL_PATTERN.test(value)
        && !QUOTED_URL_WITH_WHITESPACE_PATTERN.test(value)
        && !URL_DATA_PATTERN.test(value)
        ? value.replace(QUOTES_PATTERN, '')
        : value;
    }
  }
};

module.exports = plugin;


code:
var startsAsUrl = require('./starts-as-url');

var WHITESPACE_PATTERN = /\\?\n|\\?\r\n/g;
var WHITESPACE_PREFIX_PATTERN = /(\()\s+/g;
var WHITESPACE_SUFFIX_PATTERN = /\s+(\))/g;

var plugin = {
  level1: {
    value: function urlWhitespace(_name, value) {
      if (!startsAsUrl(value)) {
        return value;
      }

      return value
        .replace(WHITESPACE_PATTERN, '')
        .replace(WHITESPACE_PREFIX_PATTERN, '$1')
        .replace(WHITESPACE_SUFFIX_PATTERN, '$1');
    }
  }
};

module.exports = plugin;


code:
var OptimizationLevel = require('../../../options/optimization-level').OptimizationLevel;

var Marker = require('../../../tokenizer/marker');

var CALC_DIVISION_WHITESPACE_PATTERN = /\) ?\/ ?/g;
var COMMA_AND_SPACE_PATTERN = /, /g;
var LINE_BREAK_PATTERN = /\r?\n/g;
var MULTI_WHITESPACE_PATTERN = /\s+/g;
var FUNCTION_CLOSING_BRACE_WHITESPACE_PATTERN = /\s+(;?\))/g;
var FUNCTION_OPENING_BRACE_WHITESPACE_PATTERN = /(\(;?)\s+/g;
var VARIABLE_NAME_PATTERN = /^--\S+$/;
var VARIABLE_VALUE_PATTERN = /^var\(\s*--\S+\s*\)$/;

var plugin = {
  level1: {
    value: function whitespace(name, value, options) {
      if (!options.level[OptimizationLevel.One].removeWhitespace) {
        return value;
      }

      if (VARIABLE_NAME_PATTERN.test(name) && !VARIABLE_VALUE_PATTERN.test(value)) {
        return value;
      }

      if ((value.indexOf(' ') == -1 && value.indexOf('\n') == -1) || value.indexOf('expression') === 0) {
        return value;
      }

      if (value.indexOf(Marker.SINGLE_QUOTE) > -1 || value.indexOf(Marker.DOUBLE_QUOTE) > -1) {
        return value;
      }

      value = value.replace(LINE_BREAK_PATTERN, '');
      value = value.replace(MULTI_WHITESPACE_PATTERN, ' ');

      if (value.indexOf('calc') > -1) {
        value = value.replace(CALC_DIVISION_WHITESPACE_PATTERN, ')/ ');
      }

      return value
        .replace(FUNCTION_OPENING_BRACE_WHITESPACE_PATTERN, '$1')
        .replace(FUNCTION_CLOSING_BRACE_WHITESPACE_PATTERN, '$1')
        .replace(COMMA_AND_SPACE_PATTERN, ',');
    }
  }
};

module.exports = plugin;


code:
var split = require('../../../utils/split');

var ANY_FUNCTION_PATTERN = /^(-(?:moz|ms|o|webkit)-[a-z-]+|[a-z-]+)\((.+)\)$/;
var SKIP_FUNCTION_PATTERN = /^(?:-moz-calc|-webkit-calc|calc|rgb|hsl|rgba|hsla|min|max|clamp|expression)\(/;
var TOKEN_SEPARATOR_PATTERN = /([\s,/])/;

function removeRecursively(value, options) {
  var functionTokens;
  var tokens;

  if (SKIP_FUNCTION_PATTERN.test(value)) {
    return value;
  }

  functionTokens = ANY_FUNCTION_PATTERN.exec(value);

  if (!functionTokens) {
    return removeZeros(value, options);
  }

  tokens = split(functionTokens[2], TOKEN_SEPARATOR_PATTERN)
    .map(function(token) { return removeRecursively(token, options); });

  return functionTokens[1] + '(' + tokens.join('') + ')';
}

function removeZeros(value, options) {
  return value
    .replace(options.unitsRegexp, '$10$2')
    .replace(options.unitsRegexp, '$10$2');
}

var plugin = {
  level1: {
    value: function zero(name, value, options) {
      if (!options.compatibility.properties.zeroUnits) {
        return value;
      }

      if (value.indexOf('%') > 0 && (name == 'height' || name == 'max-height' || name == 'width' || name == 'max-width')) {
        return value;
      }

      return removeRecursively(value, options);
    }
  }
};

module.exports = plugin;


code:
// This extractor is used in level 2 optimizations
// IMPORTANT: Mind Token class and this code is not related!
// Properties will be tokenized in one step, see #429

var Token = require('../../tokenizer/token');
var serializeRules = require('../../writer/one-time').rules;
var serializeValue = require('../../writer/one-time').value;

function extractProperties(token) {
  var properties = [];
  var inSpecificSelector;
  var property;
  var name;
  var value;
  var i, l;

  if (token[0] == Token.RULE) {
    inSpecificSelector = !/[.+>~]/.test(serializeRules(token[1]));

    for (i = 0, l = token[2].length; i < l; i++) {
      property = token[2][i];

      if (property[0] != Token.PROPERTY) { continue; }

      name = property[1][1];
      if (name.length === 0) { continue; }

      value = serializeValue(property, i);

      properties.push([
        name,
        value,
        findNameRoot(name),
        token[2][i],
        name + ':' + value,
        token[1],
        inSpecificSelector
      ]);
    }
  } else if (token[0] == Token.NESTED_BLOCK) {
    for (i = 0, l = token[2].length; i < l; i++) {
      properties = properties.concat(extractProperties(token[2][i]));
    }
  }

  return properties;
}

function findNameRoot(name) {
  if (name == 'list-style') { return name; }
  if (name.indexOf('-radius') > 0) { return 'border-radius'; }
  if (name == 'border-collapse' || name == 'border-spacing' || name == 'border-image') { return name; }
  if (name.indexOf('border-') === 0 && /^border-\w+-\w+$/.test(name)) { return name.match(/border-\w+/)[0]; }
  if (name.indexOf('border-') === 0 && /^border-\w+$/.test(name)) { return 'border'; }
  if (name.indexOf('text-') === 0) { return name; }
  if (name == '-chrome-') { return name; }

  return name.replace(/^-\w+-/, '').match(/([a-zA-Z]+)/)[0].toLowerCase();
}

module.exports = extractProperties;