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.
259 lines
8.2 KiB
259 lines
8.2 KiB
var Spaces = require('../../options/format').Spaces; |
|
var Marker = require('../../tokenizer/marker'); |
|
var formatPosition = require('../../utils/format-position'); |
|
|
|
var CASE_ATTRIBUTE_PATTERN = /[\s"'][iI]\s*\]/; |
|
var CASE_RESTORE_PATTERN = /([\d\w])([iI])\]/g; |
|
var DOUBLE_QUOTE_CASE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"([iI])/g; |
|
var DOUBLE_QUOTE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"(\s|\])/g; |
|
var HTML_COMMENT_PATTERN = /^(?:(?:<!--|-->)\s*)+/; |
|
var SINGLE_QUOTE_CASE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'([iI])/g; |
|
var SINGLE_QUOTE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'(\s|\])/g; |
|
var RELATION_PATTERN = /[>+~]/; |
|
var WHITESPACE_PATTERN = /\s/; |
|
|
|
var ASTERISK_PLUS_HTML_HACK = '*+html '; |
|
var ASTERISK_FIRST_CHILD_PLUS_HTML_HACK = '*:first-child+html '; |
|
var LESS_THAN = '<'; |
|
|
|
var PSEUDO_CLASSES_WITH_SELECTORS = [ |
|
':current', |
|
':future', |
|
':has', |
|
':host', |
|
':host-context', |
|
':is', |
|
':not', |
|
':past', |
|
':where' |
|
]; |
|
|
|
function hasInvalidCharacters(value) { |
|
var isEscaped; |
|
var isInvalid = false; |
|
var character; |
|
var isQuote = false; |
|
var i, l; |
|
|
|
for (i = 0, l = value.length; i < l; i++) { |
|
character = value[i]; |
|
|
|
if (isEscaped) { |
|
// continue as always |
|
} else if (character == Marker.SINGLE_QUOTE || character == Marker.DOUBLE_QUOTE) { |
|
isQuote = !isQuote; |
|
} else if (!isQuote |
|
&& (character == Marker.CLOSE_CURLY_BRACKET |
|
|| character == Marker.EXCLAMATION |
|
|| character == LESS_THAN |
|
|| character == Marker.SEMICOLON) |
|
) { |
|
isInvalid = true; |
|
break; |
|
} else if (!isQuote && i === 0 && RELATION_PATTERN.test(character)) { |
|
isInvalid = true; |
|
break; |
|
} |
|
|
|
isEscaped = character == Marker.BACK_SLASH; |
|
} |
|
|
|
return isInvalid; |
|
} |
|
|
|
function removeWhitespace(value, format) { |
|
var stripped = []; |
|
var character; |
|
var isNewLineNix; |
|
var isNewLineWin; |
|
var isEscaped; |
|
var wasEscaped; |
|
var isQuoted; |
|
var isSingleQuoted; |
|
var isDoubleQuoted; |
|
var isAttribute; |
|
var isRelation; |
|
var isWhitespace; |
|
var isSpaceAwarePseudoClass; |
|
var roundBracketLevel = 0; |
|
var wasComma = false; |
|
var wasRelation = false; |
|
var wasWhitespace = false; |
|
var withCaseAttribute = CASE_ATTRIBUTE_PATTERN.test(value); |
|
var spaceAroundRelation = format && format.spaces[Spaces.AroundSelectorRelation]; |
|
var i, l; |
|
|
|
for (i = 0, l = value.length; i < l; i++) { |
|
character = value[i]; |
|
|
|
isNewLineNix = character == Marker.NEW_LINE_NIX; |
|
isNewLineWin = character == Marker.NEW_LINE_NIX && value[i - 1] == Marker.CARRIAGE_RETURN; |
|
isQuoted = isSingleQuoted || isDoubleQuoted; |
|
isRelation = !isAttribute && !isEscaped && roundBracketLevel === 0 && RELATION_PATTERN.test(character); |
|
isWhitespace = WHITESPACE_PATTERN.test(character); |
|
isSpaceAwarePseudoClass = roundBracketLevel == 1 && character == Marker.CLOSE_ROUND_BRACKET |
|
? false |
|
: isSpaceAwarePseudoClass |
|
|| (roundBracketLevel === 0 && character == Marker.COLON && isPseudoClassWithSelectors(value, i)); |
|
|
|
if (wasEscaped && isQuoted && isNewLineWin) { |
|
// swallow escaped new windows lines in comments |
|
stripped.pop(); |
|
stripped.pop(); |
|
} else if (isEscaped && isQuoted && isNewLineNix) { |
|
// swallow escaped new *nix lines in comments |
|
stripped.pop(); |
|
} else if (isEscaped) { |
|
stripped.push(character); |
|
} else if (character == Marker.OPEN_SQUARE_BRACKET && !isQuoted) { |
|
stripped.push(character); |
|
isAttribute = true; |
|
} else if (character == Marker.CLOSE_SQUARE_BRACKET && !isQuoted) { |
|
stripped.push(character); |
|
isAttribute = false; |
|
} else if (character == Marker.OPEN_ROUND_BRACKET && !isQuoted) { |
|
stripped.push(character); |
|
roundBracketLevel++; |
|
} else if (character == Marker.CLOSE_ROUND_BRACKET && !isQuoted) { |
|
stripped.push(character); |
|
roundBracketLevel--; |
|
} else if (character == Marker.SINGLE_QUOTE && !isQuoted) { |
|
stripped.push(character); |
|
isSingleQuoted = true; |
|
} else if (character == Marker.DOUBLE_QUOTE && !isQuoted) { |
|
stripped.push(character); |
|
isDoubleQuoted = true; |
|
} else if (character == Marker.SINGLE_QUOTE && isQuoted) { |
|
stripped.push(character); |
|
isSingleQuoted = false; |
|
} else if (character == Marker.DOUBLE_QUOTE && isQuoted) { |
|
stripped.push(character); |
|
isDoubleQuoted = false; |
|
} else if (isWhitespace && wasRelation && !spaceAroundRelation) { |
|
continue; |
|
} else if (!isWhitespace && wasRelation && spaceAroundRelation) { |
|
stripped.push(Marker.SPACE); |
|
stripped.push(character); |
|
} else if (isWhitespace && !wasWhitespace && wasComma && roundBracketLevel > 0 && isSpaceAwarePseudoClass) { |
|
// skip space |
|
} else if (isWhitespace && !wasWhitespace && roundBracketLevel > 0 && isSpaceAwarePseudoClass) { |
|
stripped.push(character); |
|
} else if (isWhitespace && (isAttribute || roundBracketLevel > 0) && !isQuoted) { |
|
// skip space |
|
} else if (isWhitespace && wasWhitespace && !isQuoted) { |
|
// skip extra space |
|
} else if ((isNewLineWin || isNewLineNix) && (isAttribute || roundBracketLevel > 0) && isQuoted) { |
|
// skip newline |
|
} else if (isRelation && wasWhitespace && !spaceAroundRelation) { |
|
stripped.pop(); |
|
stripped.push(character); |
|
} else if (isRelation && !wasWhitespace && spaceAroundRelation) { |
|
stripped.push(Marker.SPACE); |
|
stripped.push(character); |
|
} else if (isWhitespace) { |
|
stripped.push(Marker.SPACE); |
|
} else { |
|
stripped.push(character); |
|
} |
|
|
|
wasEscaped = isEscaped; |
|
isEscaped = character == Marker.BACK_SLASH; |
|
wasRelation = isRelation; |
|
wasWhitespace = isWhitespace; |
|
wasComma = character == Marker.COMMA; |
|
} |
|
|
|
return withCaseAttribute |
|
? stripped.join('').replace(CASE_RESTORE_PATTERN, '$1 $2]') |
|
: stripped.join(''); |
|
} |
|
|
|
function isPseudoClassWithSelectors(value, colonPosition) { |
|
var pseudoClass = value.substring(colonPosition, value.indexOf(Marker.OPEN_ROUND_BRACKET, colonPosition)); |
|
|
|
return PSEUDO_CLASSES_WITH_SELECTORS.indexOf(pseudoClass) > -1; |
|
} |
|
|
|
function removeQuotes(value) { |
|
if (value.indexOf('\'') == -1 && value.indexOf('"') == -1) { |
|
return value; |
|
} |
|
|
|
return value |
|
.replace(SINGLE_QUOTE_CASE_PATTERN, '=$1 $2') |
|
.replace(SINGLE_QUOTE_PATTERN, '=$1$2') |
|
.replace(DOUBLE_QUOTE_CASE_PATTERN, '=$1 $2') |
|
.replace(DOUBLE_QUOTE_PATTERN, '=$1$2'); |
|
} |
|
|
|
function replacePseudoClasses(value) { |
|
return value |
|
.replace('nth-child(1)', 'first-child') |
|
.replace('nth-of-type(1)', 'first-of-type') |
|
.replace('nth-of-type(even)', 'nth-of-type(2n)') |
|
.replace('nth-child(even)', 'nth-child(2n)') |
|
.replace('nth-of-type(2n+1)', 'nth-of-type(odd)') |
|
.replace('nth-child(2n+1)', 'nth-child(odd)'); |
|
} |
|
|
|
function tidyRules(rules, removeUnsupported, adjacentSpace, format, warnings) { |
|
var list = []; |
|
var repeated = []; |
|
|
|
function removeHTMLComment(rule, match) { |
|
warnings.push('HTML comment \'' + match + '\' at ' + formatPosition(rule[2][0]) + '. Removing.'); |
|
return ''; |
|
} |
|
|
|
for (var i = 0, l = rules.length; i < l; i++) { |
|
var rule = rules[i]; |
|
var reduced = rule[1]; |
|
|
|
reduced = reduced.replace(HTML_COMMENT_PATTERN, removeHTMLComment.bind(null, rule)); |
|
|
|
if (hasInvalidCharacters(reduced)) { |
|
warnings.push('Invalid selector \'' + rule[1] + '\' at ' + formatPosition(rule[2][0]) + '. Ignoring.'); |
|
continue; |
|
} |
|
|
|
reduced = removeWhitespace(reduced, format); |
|
reduced = removeQuotes(reduced); |
|
|
|
if (adjacentSpace && reduced.indexOf('nav') > 0) { |
|
reduced = reduced.replace(/\+nav(\S|$)/, '+ nav$1'); |
|
} |
|
|
|
if (removeUnsupported && reduced.indexOf(ASTERISK_PLUS_HTML_HACK) > -1) { |
|
continue; |
|
} |
|
|
|
if (removeUnsupported && reduced.indexOf(ASTERISK_FIRST_CHILD_PLUS_HTML_HACK) > -1) { |
|
continue; |
|
} |
|
|
|
if (reduced.indexOf('*') > -1) { |
|
reduced = reduced |
|
.replace(/\*([:#.[])/g, '$1') |
|
.replace(/^(:first-child)?\+html/, '*$1+html'); |
|
} |
|
|
|
if (repeated.indexOf(reduced) > -1) { |
|
continue; |
|
} |
|
|
|
reduced = replacePseudoClasses(reduced); |
|
|
|
rule[1] = reduced; |
|
repeated.push(reduced); |
|
list.push(rule); |
|
} |
|
|
|
if (list.length == 1 && list[0][1].length === 0) { |
|
warnings.push('Empty selector \'' + list[0][1] + '\' at ' + formatPosition(list[0][2][0]) + '. Ignoring.'); |
|
list = []; |
|
} |
|
|
|
return list; |
|
} |
|
|
|
module.exports = tidyRules;
|
|
|