[79a0317] | 1 | var Spaces = require('../../options/format').Spaces;
|
---|
| 2 | var Marker = require('../../tokenizer/marker');
|
---|
| 3 | var formatPosition = require('../../utils/format-position');
|
---|
| 4 |
|
---|
| 5 | var CASE_ATTRIBUTE_PATTERN = /[\s"'][iI]\s*\]/;
|
---|
| 6 | var CASE_RESTORE_PATTERN = /([\d\w])([iI])\]/g;
|
---|
| 7 | var DOUBLE_QUOTE_CASE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"([iI])/g;
|
---|
| 8 | var DOUBLE_QUOTE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"(\s|\])/g;
|
---|
| 9 | var HTML_COMMENT_PATTERN = /^(?:(?:<!--|-->)\s*)+/;
|
---|
| 10 | var SINGLE_QUOTE_CASE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'([iI])/g;
|
---|
| 11 | var SINGLE_QUOTE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'(\s|\])/g;
|
---|
| 12 | var RELATION_PATTERN = /[>+~]/;
|
---|
| 13 | var WHITESPACE_PATTERN = /\s/;
|
---|
| 14 |
|
---|
| 15 | var ASTERISK_PLUS_HTML_HACK = '*+html ';
|
---|
| 16 | var ASTERISK_FIRST_CHILD_PLUS_HTML_HACK = '*:first-child+html ';
|
---|
| 17 | var LESS_THAN = '<';
|
---|
| 18 |
|
---|
| 19 | var PSEUDO_CLASSES_WITH_SELECTORS = [
|
---|
| 20 | ':current',
|
---|
| 21 | ':future',
|
---|
| 22 | ':has',
|
---|
| 23 | ':host',
|
---|
| 24 | ':host-context',
|
---|
| 25 | ':is',
|
---|
| 26 | ':not',
|
---|
| 27 | ':past',
|
---|
| 28 | ':where'
|
---|
| 29 | ];
|
---|
| 30 |
|
---|
| 31 | function hasInvalidCharacters(value) {
|
---|
| 32 | var isEscaped;
|
---|
| 33 | var isInvalid = false;
|
---|
| 34 | var character;
|
---|
| 35 | var isQuote = false;
|
---|
| 36 | var i, l;
|
---|
| 37 |
|
---|
| 38 | for (i = 0, l = value.length; i < l; i++) {
|
---|
| 39 | character = value[i];
|
---|
| 40 |
|
---|
| 41 | if (isEscaped) {
|
---|
| 42 | // continue as always
|
---|
| 43 | } else if (character == Marker.SINGLE_QUOTE || character == Marker.DOUBLE_QUOTE) {
|
---|
| 44 | isQuote = !isQuote;
|
---|
| 45 | } else if (!isQuote
|
---|
| 46 | && (character == Marker.CLOSE_CURLY_BRACKET
|
---|
| 47 | || character == Marker.EXCLAMATION
|
---|
| 48 | || character == LESS_THAN
|
---|
| 49 | || character == Marker.SEMICOLON)
|
---|
| 50 | ) {
|
---|
| 51 | isInvalid = true;
|
---|
| 52 | break;
|
---|
| 53 | } else if (!isQuote && i === 0 && RELATION_PATTERN.test(character)) {
|
---|
| 54 | isInvalid = true;
|
---|
| 55 | break;
|
---|
| 56 | }
|
---|
| 57 |
|
---|
| 58 | isEscaped = character == Marker.BACK_SLASH;
|
---|
| 59 | }
|
---|
| 60 |
|
---|
| 61 | return isInvalid;
|
---|
| 62 | }
|
---|
| 63 |
|
---|
| 64 | function removeWhitespace(value, format) {
|
---|
| 65 | var stripped = [];
|
---|
| 66 | var character;
|
---|
| 67 | var isNewLineNix;
|
---|
| 68 | var isNewLineWin;
|
---|
| 69 | var isEscaped;
|
---|
| 70 | var wasEscaped;
|
---|
| 71 | var isQuoted;
|
---|
| 72 | var isSingleQuoted;
|
---|
| 73 | var isDoubleQuoted;
|
---|
| 74 | var isAttribute;
|
---|
| 75 | var isRelation;
|
---|
| 76 | var isWhitespace;
|
---|
| 77 | var isSpaceAwarePseudoClass;
|
---|
| 78 | var roundBracketLevel = 0;
|
---|
| 79 | var wasComma = false;
|
---|
| 80 | var wasRelation = false;
|
---|
| 81 | var wasWhitespace = false;
|
---|
| 82 | var withCaseAttribute = CASE_ATTRIBUTE_PATTERN.test(value);
|
---|
| 83 | var spaceAroundRelation = format && format.spaces[Spaces.AroundSelectorRelation];
|
---|
| 84 | var i, l;
|
---|
| 85 |
|
---|
| 86 | for (i = 0, l = value.length; i < l; i++) {
|
---|
| 87 | character = value[i];
|
---|
| 88 |
|
---|
| 89 | isNewLineNix = character == Marker.NEW_LINE_NIX;
|
---|
| 90 | isNewLineWin = character == Marker.NEW_LINE_NIX && value[i - 1] == Marker.CARRIAGE_RETURN;
|
---|
| 91 | isQuoted = isSingleQuoted || isDoubleQuoted;
|
---|
| 92 | isRelation = !isAttribute && !isEscaped && roundBracketLevel === 0 && RELATION_PATTERN.test(character);
|
---|
| 93 | isWhitespace = WHITESPACE_PATTERN.test(character);
|
---|
| 94 | isSpaceAwarePseudoClass = roundBracketLevel == 1 && character == Marker.CLOSE_ROUND_BRACKET
|
---|
| 95 | ? false
|
---|
| 96 | : isSpaceAwarePseudoClass
|
---|
| 97 | || (roundBracketLevel === 0 && character == Marker.COLON && isPseudoClassWithSelectors(value, i));
|
---|
| 98 |
|
---|
| 99 | if (wasEscaped && isQuoted && isNewLineWin) {
|
---|
| 100 | // swallow escaped new windows lines in comments
|
---|
| 101 | stripped.pop();
|
---|
| 102 | stripped.pop();
|
---|
| 103 | } else if (isEscaped && isQuoted && isNewLineNix) {
|
---|
| 104 | // swallow escaped new *nix lines in comments
|
---|
| 105 | stripped.pop();
|
---|
| 106 | } else if (isEscaped) {
|
---|
| 107 | stripped.push(character);
|
---|
| 108 | } else if (character == Marker.OPEN_SQUARE_BRACKET && !isQuoted) {
|
---|
| 109 | stripped.push(character);
|
---|
| 110 | isAttribute = true;
|
---|
| 111 | } else if (character == Marker.CLOSE_SQUARE_BRACKET && !isQuoted) {
|
---|
| 112 | stripped.push(character);
|
---|
| 113 | isAttribute = false;
|
---|
| 114 | } else if (character == Marker.OPEN_ROUND_BRACKET && !isQuoted) {
|
---|
| 115 | stripped.push(character);
|
---|
| 116 | roundBracketLevel++;
|
---|
| 117 | } else if (character == Marker.CLOSE_ROUND_BRACKET && !isQuoted) {
|
---|
| 118 | stripped.push(character);
|
---|
| 119 | roundBracketLevel--;
|
---|
| 120 | } else if (character == Marker.SINGLE_QUOTE && !isQuoted) {
|
---|
| 121 | stripped.push(character);
|
---|
| 122 | isSingleQuoted = true;
|
---|
| 123 | } else if (character == Marker.DOUBLE_QUOTE && !isQuoted) {
|
---|
| 124 | stripped.push(character);
|
---|
| 125 | isDoubleQuoted = true;
|
---|
| 126 | } else if (character == Marker.SINGLE_QUOTE && isQuoted) {
|
---|
| 127 | stripped.push(character);
|
---|
| 128 | isSingleQuoted = false;
|
---|
| 129 | } else if (character == Marker.DOUBLE_QUOTE && isQuoted) {
|
---|
| 130 | stripped.push(character);
|
---|
| 131 | isDoubleQuoted = false;
|
---|
| 132 | } else if (isWhitespace && wasRelation && !spaceAroundRelation) {
|
---|
| 133 | continue;
|
---|
| 134 | } else if (!isWhitespace && wasRelation && spaceAroundRelation) {
|
---|
| 135 | stripped.push(Marker.SPACE);
|
---|
| 136 | stripped.push(character);
|
---|
| 137 | } else if (isWhitespace && !wasWhitespace && wasComma && roundBracketLevel > 0 && isSpaceAwarePseudoClass) {
|
---|
| 138 | // skip space
|
---|
| 139 | } else if (isWhitespace && !wasWhitespace && roundBracketLevel > 0 && isSpaceAwarePseudoClass) {
|
---|
| 140 | stripped.push(character);
|
---|
| 141 | } else if (isWhitespace && (isAttribute || roundBracketLevel > 0) && !isQuoted) {
|
---|
| 142 | // skip space
|
---|
| 143 | } else if (isWhitespace && wasWhitespace && !isQuoted) {
|
---|
| 144 | // skip extra space
|
---|
| 145 | } else if ((isNewLineWin || isNewLineNix) && (isAttribute || roundBracketLevel > 0) && isQuoted) {
|
---|
| 146 | // skip newline
|
---|
| 147 | } else if (isRelation && wasWhitespace && !spaceAroundRelation) {
|
---|
| 148 | stripped.pop();
|
---|
| 149 | stripped.push(character);
|
---|
| 150 | } else if (isRelation && !wasWhitespace && spaceAroundRelation) {
|
---|
| 151 | stripped.push(Marker.SPACE);
|
---|
| 152 | stripped.push(character);
|
---|
| 153 | } else if (isWhitespace) {
|
---|
| 154 | stripped.push(Marker.SPACE);
|
---|
| 155 | } else {
|
---|
| 156 | stripped.push(character);
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | wasEscaped = isEscaped;
|
---|
| 160 | isEscaped = character == Marker.BACK_SLASH;
|
---|
| 161 | wasRelation = isRelation;
|
---|
| 162 | wasWhitespace = isWhitespace;
|
---|
| 163 | wasComma = character == Marker.COMMA;
|
---|
| 164 | }
|
---|
| 165 |
|
---|
| 166 | return withCaseAttribute
|
---|
| 167 | ? stripped.join('').replace(CASE_RESTORE_PATTERN, '$1 $2]')
|
---|
| 168 | : stripped.join('');
|
---|
| 169 | }
|
---|
| 170 |
|
---|
| 171 | function isPseudoClassWithSelectors(value, colonPosition) {
|
---|
| 172 | var pseudoClass = value.substring(colonPosition, value.indexOf(Marker.OPEN_ROUND_BRACKET, colonPosition));
|
---|
| 173 |
|
---|
| 174 | return PSEUDO_CLASSES_WITH_SELECTORS.indexOf(pseudoClass) > -1;
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | function removeQuotes(value) {
|
---|
| 178 | if (value.indexOf('\'') == -1 && value.indexOf('"') == -1) {
|
---|
| 179 | return value;
|
---|
| 180 | }
|
---|
| 181 |
|
---|
| 182 | return value
|
---|
| 183 | .replace(SINGLE_QUOTE_CASE_PATTERN, '=$1 $2')
|
---|
| 184 | .replace(SINGLE_QUOTE_PATTERN, '=$1$2')
|
---|
| 185 | .replace(DOUBLE_QUOTE_CASE_PATTERN, '=$1 $2')
|
---|
| 186 | .replace(DOUBLE_QUOTE_PATTERN, '=$1$2');
|
---|
| 187 | }
|
---|
| 188 |
|
---|
| 189 | function replacePseudoClasses(value) {
|
---|
| 190 | return value
|
---|
| 191 | .replace('nth-child(1)', 'first-child')
|
---|
| 192 | .replace('nth-of-type(1)', 'first-of-type')
|
---|
| 193 | .replace('nth-of-type(even)', 'nth-of-type(2n)')
|
---|
| 194 | .replace('nth-child(even)', 'nth-child(2n)')
|
---|
| 195 | .replace('nth-of-type(2n+1)', 'nth-of-type(odd)')
|
---|
| 196 | .replace('nth-child(2n+1)', 'nth-child(odd)')
|
---|
| 197 | .replace('nth-last-child(1)', 'last-child')
|
---|
| 198 | .replace('nth-last-of-type(1)', 'last-of-type')
|
---|
| 199 | .replace('nth-last-of-type(even)', 'nth-last-of-type(2n)')
|
---|
| 200 | .replace('nth-last-child(even)', 'nth-last-child(2n)')
|
---|
| 201 | .replace('nth-last-of-type(2n+1)', 'nth-last-of-type(odd)')
|
---|
| 202 | .replace('nth-last-child(2n+1)', 'nth-last-child(odd)');
|
---|
| 203 | }
|
---|
| 204 |
|
---|
| 205 | function tidyRules(rules, removeUnsupported, adjacentSpace, format, warnings) {
|
---|
| 206 | var list = [];
|
---|
| 207 | var repeated = [];
|
---|
| 208 |
|
---|
| 209 | function removeHTMLComment(rule, match) {
|
---|
| 210 | warnings.push('HTML comment \'' + match + '\' at ' + formatPosition(rule[2][0]) + '. Removing.');
|
---|
| 211 | return '';
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | for (var i = 0, l = rules.length; i < l; i++) {
|
---|
| 215 | var rule = rules[i];
|
---|
| 216 | var reduced = rule[1];
|
---|
| 217 |
|
---|
| 218 | reduced = reduced.replace(HTML_COMMENT_PATTERN, removeHTMLComment.bind(null, rule));
|
---|
| 219 |
|
---|
| 220 | if (hasInvalidCharacters(reduced)) {
|
---|
| 221 | warnings.push('Invalid selector \'' + rule[1] + '\' at ' + formatPosition(rule[2][0]) + '. Ignoring.');
|
---|
| 222 | continue;
|
---|
| 223 | }
|
---|
| 224 |
|
---|
| 225 | reduced = removeWhitespace(reduced, format);
|
---|
| 226 | reduced = removeQuotes(reduced);
|
---|
| 227 |
|
---|
| 228 | if (adjacentSpace && reduced.indexOf('nav') > 0) {
|
---|
| 229 | reduced = reduced.replace(/\+nav(\S|$)/, '+ nav$1');
|
---|
| 230 | }
|
---|
| 231 |
|
---|
| 232 | if (removeUnsupported && reduced.indexOf(ASTERISK_PLUS_HTML_HACK) > -1) {
|
---|
| 233 | continue;
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | if (removeUnsupported && reduced.indexOf(ASTERISK_FIRST_CHILD_PLUS_HTML_HACK) > -1) {
|
---|
| 237 | continue;
|
---|
| 238 | }
|
---|
| 239 |
|
---|
| 240 | if (reduced.indexOf('*') > -1) {
|
---|
| 241 | reduced = reduced
|
---|
| 242 | .replace(/\*([:#.[])/g, '$1')
|
---|
| 243 | .replace(/^(:first-child)?\+html/, '*$1+html');
|
---|
| 244 | }
|
---|
| 245 |
|
---|
| 246 | if (repeated.indexOf(reduced) > -1) {
|
---|
| 247 | continue;
|
---|
| 248 | }
|
---|
| 249 |
|
---|
| 250 | reduced = replacePseudoClasses(reduced);
|
---|
| 251 |
|
---|
| 252 | rule[1] = reduced;
|
---|
| 253 | repeated.push(reduced);
|
---|
| 254 | list.push(rule);
|
---|
| 255 | }
|
---|
| 256 |
|
---|
| 257 | if (list.length == 1 && list[0][1].length === 0) {
|
---|
| 258 | warnings.push('Empty selector \'' + list[0][1] + '\' at ' + formatPosition(list[0][2][0]) + '. Ignoring.');
|
---|
| 259 | list = [];
|
---|
| 260 | }
|
---|
| 261 |
|
---|
| 262 | return list;
|
---|
| 263 | }
|
---|
| 264 |
|
---|
| 265 | module.exports = tidyRules;
|
---|