309 lines
7.1 KiB
JavaScript
309 lines
7.1 KiB
JavaScript
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;
|