You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
7.0 KiB
273 lines
7.0 KiB
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;
|
|
|