You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							259 lines
						
					
					
						
							6.8 KiB
						
					
					
				
			
		
		
	
	
							259 lines
						
					
					
						
							6.8 KiB
						
					
					
				var Marker = require('../../tokenizer/marker'); | 
						|
var split = require('../../utils/split'); | 
						|
 | 
						|
var DEEP_SELECTOR_PATTERN = /\/deep\//; | 
						|
var DOUBLE_COLON_PATTERN = /^::/; | 
						|
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) || | 
						|
        (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 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;
 | 
						|
 |