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