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.
266 lines
7.0 KiB
266 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;
|
|
|