274 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
var Marker = require('../../tokenizer/marker');
 | 
						|
var split = require('../../utils/split');
 | 
						|
 | 
						|
var DEEP_SELECTOR_PATTERN = /\/deep\//;
 | 
						|
var DOUBLE_COLON_PATTERN = /^::/;
 | 
						|
var VENDOR_PREFIXED_PATTERN = /:(-moz-|-ms-|-o-|-webkit-)/;
 | 
						|
 | 
						|
var NOT_PSEUDO = ':not';
 | 
						|
var PSEUDO_CLASSES_WITH_ARGUMENTS = [
 | 
						|
  ':dir',
 | 
						|
  ':lang',
 | 
						|
  ':not',
 | 
						|
  ':nth-child',
 | 
						|
  ':nth-last-child',
 | 
						|
  ':nth-last-of-type',
 | 
						|
  ':nth-of-type'
 | 
						|
];
 | 
						|
var RELATION_PATTERN = /[>+~]/;
 | 
						|
var UNMIXABLE_PSEUDO_CLASSES = [
 | 
						|
  ':after',
 | 
						|
  ':before',
 | 
						|
  ':first-letter',
 | 
						|
  ':first-line',
 | 
						|
  ':lang'
 | 
						|
];
 | 
						|
var UNMIXABLE_PSEUDO_ELEMENTS = [
 | 
						|
  '::after',
 | 
						|
  '::before',
 | 
						|
  '::first-letter',
 | 
						|
  '::first-line'
 | 
						|
];
 | 
						|
 | 
						|
var Level = {
 | 
						|
  DOUBLE_QUOTE: 'double-quote',
 | 
						|
  SINGLE_QUOTE: 'single-quote',
 | 
						|
  ROOT: 'root'
 | 
						|
};
 | 
						|
 | 
						|
function isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) {
 | 
						|
  var singleSelectors = split(selector, Marker.COMMA);
 | 
						|
  var singleSelector;
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  for (i = 0, l = singleSelectors.length; i < l; i++) {
 | 
						|
    singleSelector = singleSelectors[i];
 | 
						|
 | 
						|
    if (singleSelector.length === 0
 | 
						|
        || isDeepSelector(singleSelector)
 | 
						|
        || isVendorPrefixed(singleSelector)
 | 
						|
        || (singleSelector.indexOf(Marker.COLON) > -1
 | 
						|
          && !areMergeable(
 | 
						|
            singleSelector,
 | 
						|
            extractPseudoFrom(singleSelector),
 | 
						|
            mergeablePseudoClasses,
 | 
						|
            mergeablePseudoElements,
 | 
						|
            multiplePseudoMerging
 | 
						|
          ))) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
function isDeepSelector(selector) {
 | 
						|
  return DEEP_SELECTOR_PATTERN.test(selector);
 | 
						|
}
 | 
						|
 | 
						|
function isVendorPrefixed(selector) {
 | 
						|
  return VENDOR_PREFIXED_PATTERN.test(selector);
 | 
						|
}
 | 
						|
 | 
						|
function extractPseudoFrom(selector) {
 | 
						|
  var list = [];
 | 
						|
  var character;
 | 
						|
  var buffer = [];
 | 
						|
  var level = Level.ROOT;
 | 
						|
  var roundBracketLevel = 0;
 | 
						|
  var isQuoted;
 | 
						|
  var isEscaped;
 | 
						|
  var isPseudo = false;
 | 
						|
  var isRelation;
 | 
						|
  var wasColon = false;
 | 
						|
  var index;
 | 
						|
  var len;
 | 
						|
 | 
						|
  for (index = 0, len = selector.length; index < len; index++) {
 | 
						|
    character = selector[index];
 | 
						|
 | 
						|
    isRelation = !isEscaped && RELATION_PATTERN.test(character);
 | 
						|
    isQuoted = level == Level.DOUBLE_QUOTE || level == Level.SINGLE_QUOTE;
 | 
						|
 | 
						|
    if (isEscaped) {
 | 
						|
      buffer.push(character);
 | 
						|
    } else if (character == Marker.DOUBLE_QUOTE && level == Level.ROOT) {
 | 
						|
      buffer.push(character);
 | 
						|
      level = Level.DOUBLE_QUOTE;
 | 
						|
    } else if (character == Marker.DOUBLE_QUOTE && level == Level.DOUBLE_QUOTE) {
 | 
						|
      buffer.push(character);
 | 
						|
      level = Level.ROOT;
 | 
						|
    } else if (character == Marker.SINGLE_QUOTE && level == Level.ROOT) {
 | 
						|
      buffer.push(character);
 | 
						|
      level = Level.SINGLE_QUOTE;
 | 
						|
    } else if (character == Marker.SINGLE_QUOTE && level == Level.SINGLE_QUOTE) {
 | 
						|
      buffer.push(character);
 | 
						|
      level = Level.ROOT;
 | 
						|
    } else if (isQuoted) {
 | 
						|
      buffer.push(character);
 | 
						|
    } else if (character == Marker.OPEN_ROUND_BRACKET) {
 | 
						|
      buffer.push(character);
 | 
						|
      roundBracketLevel++;
 | 
						|
    } else if (character == Marker.CLOSE_ROUND_BRACKET && roundBracketLevel == 1 && isPseudo) {
 | 
						|
      buffer.push(character);
 | 
						|
      list.push(buffer.join(''));
 | 
						|
      roundBracketLevel--;
 | 
						|
      buffer = [];
 | 
						|
      isPseudo = false;
 | 
						|
    } else if (character == Marker.CLOSE_ROUND_BRACKET) {
 | 
						|
      buffer.push(character);
 | 
						|
      roundBracketLevel--;
 | 
						|
    } else if (character == Marker.COLON && roundBracketLevel === 0 && isPseudo && !wasColon) {
 | 
						|
      list.push(buffer.join(''));
 | 
						|
      buffer = [];
 | 
						|
      buffer.push(character);
 | 
						|
    } else if (character == Marker.COLON && roundBracketLevel === 0 && !wasColon) {
 | 
						|
      buffer = [];
 | 
						|
      buffer.push(character);
 | 
						|
      isPseudo = true;
 | 
						|
    } else if (character == Marker.SPACE && roundBracketLevel === 0 && isPseudo) {
 | 
						|
      list.push(buffer.join(''));
 | 
						|
      buffer = [];
 | 
						|
      isPseudo = false;
 | 
						|
    } else if (isRelation && roundBracketLevel === 0 && isPseudo) {
 | 
						|
      list.push(buffer.join(''));
 | 
						|
      buffer = [];
 | 
						|
      isPseudo = false;
 | 
						|
    } else {
 | 
						|
      buffer.push(character);
 | 
						|
    }
 | 
						|
 | 
						|
    isEscaped = character == Marker.BACK_SLASH;
 | 
						|
    wasColon = character == Marker.COLON;
 | 
						|
  }
 | 
						|
 | 
						|
  if (buffer.length > 0 && isPseudo) {
 | 
						|
    list.push(buffer.join(''));
 | 
						|
  }
 | 
						|
 | 
						|
  return list;
 | 
						|
}
 | 
						|
 | 
						|
function areMergeable(selector, matches, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) {
 | 
						|
  return areAllowed(matches, mergeablePseudoClasses, mergeablePseudoElements)
 | 
						|
    && needArguments(matches)
 | 
						|
    && (matches.length < 2 || !someIncorrectlyChained(selector, matches))
 | 
						|
    && (matches.length < 2 || multiplePseudoMerging && allMixable(matches));
 | 
						|
}
 | 
						|
 | 
						|
function areAllowed(matches, mergeablePseudoClasses, mergeablePseudoElements) {
 | 
						|
  var match;
 | 
						|
  var name;
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  for (i = 0, l = matches.length; i < l; i++) {
 | 
						|
    match = matches[i];
 | 
						|
    name = match.indexOf(Marker.OPEN_ROUND_BRACKET) > -1
 | 
						|
      ? match.substring(0, match.indexOf(Marker.OPEN_ROUND_BRACKET))
 | 
						|
      : match;
 | 
						|
 | 
						|
    if (mergeablePseudoClasses.indexOf(name) === -1 && mergeablePseudoElements.indexOf(name) === -1) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
function needArguments(matches) {
 | 
						|
  var match;
 | 
						|
  var name;
 | 
						|
  var bracketOpensAt;
 | 
						|
  var hasArguments;
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  for (i = 0, l = matches.length; i < l; i++) {
 | 
						|
    match = matches[i];
 | 
						|
 | 
						|
    bracketOpensAt = match.indexOf(Marker.OPEN_ROUND_BRACKET);
 | 
						|
    hasArguments = bracketOpensAt > -1;
 | 
						|
    name = hasArguments
 | 
						|
      ? match.substring(0, bracketOpensAt)
 | 
						|
      : match;
 | 
						|
 | 
						|
    if (hasArguments && PSEUDO_CLASSES_WITH_ARGUMENTS.indexOf(name) == -1) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!hasArguments && PSEUDO_CLASSES_WITH_ARGUMENTS.indexOf(name) > -1) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
function someIncorrectlyChained(selector, matches) {
 | 
						|
  var positionInSelector = 0;
 | 
						|
  var match;
 | 
						|
  var matchAt;
 | 
						|
  var nextMatch;
 | 
						|
  var nextMatchAt;
 | 
						|
  var name;
 | 
						|
  var nextName;
 | 
						|
  var areChained;
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  for (i = 0, l = matches.length; i < l; i++) {
 | 
						|
    match = matches[i];
 | 
						|
    nextMatch = matches[i + 1];
 | 
						|
 | 
						|
    if (!nextMatch) {
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    matchAt = selector.indexOf(match, positionInSelector);
 | 
						|
    nextMatchAt = selector.indexOf(match, matchAt + 1);
 | 
						|
    positionInSelector = nextMatchAt;
 | 
						|
    areChained = matchAt + match.length == nextMatchAt;
 | 
						|
 | 
						|
    if (areChained) {
 | 
						|
      name = match.indexOf(Marker.OPEN_ROUND_BRACKET) > -1
 | 
						|
        ? match.substring(0, match.indexOf(Marker.OPEN_ROUND_BRACKET))
 | 
						|
        : match;
 | 
						|
      nextName = nextMatch.indexOf(Marker.OPEN_ROUND_BRACKET) > -1
 | 
						|
        ? nextMatch.substring(0, nextMatch.indexOf(Marker.OPEN_ROUND_BRACKET))
 | 
						|
        : nextMatch;
 | 
						|
 | 
						|
      if (name != NOT_PSEUDO || nextName != NOT_PSEUDO) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
function allMixable(matches) {
 | 
						|
  var unmixableMatches = 0;
 | 
						|
  var match;
 | 
						|
  var i, l;
 | 
						|
 | 
						|
  for (i = 0, l = matches.length; i < l; i++) {
 | 
						|
    match = matches[i];
 | 
						|
 | 
						|
    if (isPseudoElement(match)) {
 | 
						|
      unmixableMatches += UNMIXABLE_PSEUDO_ELEMENTS.indexOf(match) > -1 ? 1 : 0;
 | 
						|
    } else {
 | 
						|
      unmixableMatches += UNMIXABLE_PSEUDO_CLASSES.indexOf(match) > -1 ? 1 : 0;
 | 
						|
    }
 | 
						|
 | 
						|
    if (unmixableMatches > 1) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
function isPseudoElement(pseudo) {
 | 
						|
  return DOUBLE_COLON_PATTERN.test(pseudo);
 | 
						|
}
 | 
						|
 | 
						|
module.exports = isMergeable;
 |