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