| 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; |