Spaces:
Sleeping
Sleeping
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)') | |
.replace('nth-last-child(1)', 'last-child') | |
.replace('nth-last-of-type(1)', 'last-of-type') | |
.replace('nth-last-of-type(even)', 'nth-last-of-type(2n)') | |
.replace('nth-last-child(even)', 'nth-last-child(2n)') | |
.replace('nth-last-of-type(2n+1)', 'nth-last-of-type(odd)') | |
.replace('nth-last-child(2n+1)', 'nth-last-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; | |