Spaces:
Running
Running
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; | |