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