108 lines
2.6 KiB
JavaScript
108 lines
2.6 KiB
JavaScript
var Marker = require('../../tokenizer/marker');
|
|
|
|
var Selector = {
|
|
ADJACENT_SIBLING: '+',
|
|
DESCENDANT: '>',
|
|
DOT: '.',
|
|
HASH: '#',
|
|
NON_ADJACENT_SIBLING: '~',
|
|
PSEUDO: ':',
|
|
};
|
|
|
|
var LETTER_PATTERN = /[a-zA-Z]/;
|
|
var NOT_PREFIX = ':not(';
|
|
var SEPARATOR_PATTERN = /[\s,\(>~\+]/;
|
|
|
|
function specificity(selector) {
|
|
var result = [0, 0, 0];
|
|
var character;
|
|
var isEscaped;
|
|
var isSingleQuoted;
|
|
var isDoubleQuoted;
|
|
var roundBracketLevel = 0;
|
|
var couldIntroduceNewTypeSelector;
|
|
var withinNotPseudoClass = false;
|
|
var wasPseudoClass = false;
|
|
var i, l;
|
|
|
|
for (i = 0, l = selector.length; i < l; i++) {
|
|
character = selector[i];
|
|
|
|
if (isEscaped) {
|
|
// noop
|
|
} else if (
|
|
character == Marker.SINGLE_QUOTE &&
|
|
!isDoubleQuoted &&
|
|
!isSingleQuoted
|
|
) {
|
|
isSingleQuoted = true;
|
|
} else if (
|
|
character == Marker.SINGLE_QUOTE &&
|
|
!isDoubleQuoted &&
|
|
isSingleQuoted
|
|
) {
|
|
isSingleQuoted = false;
|
|
} else if (
|
|
character == Marker.DOUBLE_QUOTE &&
|
|
!isDoubleQuoted &&
|
|
!isSingleQuoted
|
|
) {
|
|
isDoubleQuoted = true;
|
|
} else if (
|
|
character == Marker.DOUBLE_QUOTE &&
|
|
isDoubleQuoted &&
|
|
!isSingleQuoted
|
|
) {
|
|
isDoubleQuoted = false;
|
|
} else if (isSingleQuoted || isDoubleQuoted) {
|
|
continue;
|
|
} else if (roundBracketLevel > 0 && !withinNotPseudoClass) {
|
|
// noop
|
|
} else if (character == Marker.OPEN_ROUND_BRACKET) {
|
|
roundBracketLevel++;
|
|
} else if (
|
|
character == Marker.CLOSE_ROUND_BRACKET &&
|
|
roundBracketLevel == 1
|
|
) {
|
|
roundBracketLevel--;
|
|
withinNotPseudoClass = false;
|
|
} else if (character == Marker.CLOSE_ROUND_BRACKET) {
|
|
roundBracketLevel--;
|
|
} else if (character == Selector.HASH) {
|
|
result[0]++;
|
|
} else if (
|
|
character == Selector.DOT ||
|
|
character == Marker.OPEN_SQUARE_BRACKET
|
|
) {
|
|
result[1]++;
|
|
} else if (
|
|
character == Selector.PSEUDO &&
|
|
!wasPseudoClass &&
|
|
!isNotPseudoClass(selector, i)
|
|
) {
|
|
result[1]++;
|
|
withinNotPseudoClass = false;
|
|
} else if (character == Selector.PSEUDO) {
|
|
withinNotPseudoClass = true;
|
|
} else if (
|
|
(i === 0 || couldIntroduceNewTypeSelector) &&
|
|
LETTER_PATTERN.test(character)
|
|
) {
|
|
result[2]++;
|
|
}
|
|
|
|
isEscaped = character == Marker.BACK_SLASH;
|
|
wasPseudoClass = character == Selector.PSEUDO;
|
|
couldIntroduceNewTypeSelector =
|
|
!isEscaped && SEPARATOR_PATTERN.test(character);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function isNotPseudoClass(selector, index) {
|
|
return selector.indexOf(NOT_PREFIX, index) === index;
|
|
}
|
|
|
|
module.exports = specificity;
|