tidy-rules.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. var Spaces = require('../../options/format').Spaces;
  2. var Marker = require('../../tokenizer/marker');
  3. var formatPosition = require('../../utils/format-position');
  4. var CASE_ATTRIBUTE_PATTERN = /[\s"'][iI]\s*\]/;
  5. var CASE_RESTORE_PATTERN = /([\d\w])([iI])\]/g;
  6. var DOUBLE_QUOTE_CASE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"([iI])/g;
  7. var DOUBLE_QUOTE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"(\s|\])/g;
  8. var HTML_COMMENT_PATTERN = /^(?:(?:<!--|-->)\s*)+/;
  9. var SINGLE_QUOTE_CASE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'([iI])/g;
  10. var SINGLE_QUOTE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'(\s|\])/g;
  11. var RELATION_PATTERN = /[>\+~]/;
  12. var WHITESPACE_PATTERN = /\s/;
  13. var ASTERISK_PLUS_HTML_HACK = '*+html ';
  14. var ASTERISK_FIRST_CHILD_PLUS_HTML_HACK = '*:first-child+html ';
  15. var LESS_THAN = '<';
  16. function hasInvalidCharacters(value) {
  17. var isEscaped;
  18. var isInvalid = false;
  19. var character;
  20. var isQuote = false;
  21. var i, l;
  22. for (i = 0, l = value.length; i < l; i++) {
  23. character = value[i];
  24. if (isEscaped) {
  25. // continue as always
  26. } else if (character == Marker.SINGLE_QUOTE || character == Marker.DOUBLE_QUOTE) {
  27. isQuote = !isQuote;
  28. } else if (!isQuote && (character == Marker.CLOSE_CURLY_BRACKET || character == Marker.EXCLAMATION || character == LESS_THAN || character == Marker.SEMICOLON)) {
  29. isInvalid = true;
  30. break;
  31. } else if (!isQuote && i === 0 && RELATION_PATTERN.test(character)) {
  32. isInvalid = true;
  33. break;
  34. }
  35. isEscaped = character == Marker.BACK_SLASH;
  36. }
  37. return isInvalid;
  38. }
  39. function removeWhitespace(value, format) {
  40. var stripped = [];
  41. var character;
  42. var isNewLineNix;
  43. var isNewLineWin;
  44. var isEscaped;
  45. var wasEscaped;
  46. var isQuoted;
  47. var isSingleQuoted;
  48. var isDoubleQuoted;
  49. var isAttribute;
  50. var isRelation;
  51. var isWhitespace;
  52. var roundBracketLevel = 0;
  53. var wasRelation = false;
  54. var wasWhitespace = false;
  55. var withCaseAttribute = CASE_ATTRIBUTE_PATTERN.test(value);
  56. var spaceAroundRelation = format && format.spaces[Spaces.AroundSelectorRelation];
  57. var i, l;
  58. for (i = 0, l = value.length; i < l; i++) {
  59. character = value[i];
  60. isNewLineNix = character == Marker.NEW_LINE_NIX;
  61. isNewLineWin = character == Marker.NEW_LINE_NIX && value[i - 1] == Marker.CARRIAGE_RETURN;
  62. isQuoted = isSingleQuoted || isDoubleQuoted;
  63. isRelation = !isAttribute && !isEscaped && roundBracketLevel === 0 && RELATION_PATTERN.test(character);
  64. isWhitespace = WHITESPACE_PATTERN.test(character);
  65. if (wasEscaped && isQuoted && isNewLineWin) {
  66. // swallow escaped new windows lines in comments
  67. stripped.pop();
  68. stripped.pop();
  69. } else if (isEscaped && isQuoted && isNewLineNix) {
  70. // swallow escaped new *nix lines in comments
  71. stripped.pop();
  72. } else if (isEscaped) {
  73. stripped.push(character);
  74. } else if (character == Marker.OPEN_SQUARE_BRACKET && !isQuoted) {
  75. stripped.push(character);
  76. isAttribute = true;
  77. } else if (character == Marker.CLOSE_SQUARE_BRACKET && !isQuoted) {
  78. stripped.push(character);
  79. isAttribute = false;
  80. } else if (character == Marker.OPEN_ROUND_BRACKET && !isQuoted) {
  81. stripped.push(character);
  82. roundBracketLevel++;
  83. } else if (character == Marker.CLOSE_ROUND_BRACKET && !isQuoted) {
  84. stripped.push(character);
  85. roundBracketLevel--;
  86. } else if (character == Marker.SINGLE_QUOTE && !isQuoted) {
  87. stripped.push(character);
  88. isSingleQuoted = true;
  89. } else if (character == Marker.DOUBLE_QUOTE && !isQuoted) {
  90. stripped.push(character);
  91. isDoubleQuoted = true;
  92. } else if (character == Marker.SINGLE_QUOTE && isQuoted) {
  93. stripped.push(character);
  94. isSingleQuoted = false;
  95. } else if (character == Marker.DOUBLE_QUOTE && isQuoted) {
  96. stripped.push(character);
  97. isDoubleQuoted = false;
  98. } else if (isWhitespace && wasRelation && !spaceAroundRelation) {
  99. continue;
  100. } else if (!isWhitespace && wasRelation && spaceAroundRelation) {
  101. stripped.push(Marker.SPACE);
  102. stripped.push(character);
  103. } else if (isWhitespace && (isAttribute || roundBracketLevel > 0) && !isQuoted) {
  104. // skip space
  105. } else if (isWhitespace && wasWhitespace && !isQuoted) {
  106. // skip extra space
  107. } else if ((isNewLineWin || isNewLineNix) && (isAttribute || roundBracketLevel > 0) && isQuoted) {
  108. // skip newline
  109. } else if (isRelation && wasWhitespace && !spaceAroundRelation) {
  110. stripped.pop();
  111. stripped.push(character);
  112. } else if (isRelation && !wasWhitespace && spaceAroundRelation) {
  113. stripped.push(Marker.SPACE);
  114. stripped.push(character);
  115. } else if (isWhitespace) {
  116. stripped.push(Marker.SPACE);
  117. } else {
  118. stripped.push(character);
  119. }
  120. wasEscaped = isEscaped;
  121. isEscaped = character == Marker.BACK_SLASH;
  122. wasRelation = isRelation;
  123. wasWhitespace = isWhitespace;
  124. }
  125. return withCaseAttribute ?
  126. stripped.join('').replace(CASE_RESTORE_PATTERN, '$1 $2]') :
  127. stripped.join('');
  128. }
  129. function removeQuotes(value) {
  130. if (value.indexOf('\'') == -1 && value.indexOf('"') == -1) {
  131. return value;
  132. }
  133. return value
  134. .replace(SINGLE_QUOTE_CASE_PATTERN, '=$1 $2')
  135. .replace(SINGLE_QUOTE_PATTERN, '=$1$2')
  136. .replace(DOUBLE_QUOTE_CASE_PATTERN, '=$1 $2')
  137. .replace(DOUBLE_QUOTE_PATTERN, '=$1$2');
  138. }
  139. function tidyRules(rules, removeUnsupported, adjacentSpace, format, warnings) {
  140. var list = [];
  141. var repeated = [];
  142. function removeHTMLComment(rule, match) {
  143. warnings.push('HTML comment \'' + match + '\' at ' + formatPosition(rule[2][0]) + '. Removing.');
  144. return '';
  145. }
  146. for (var i = 0, l = rules.length; i < l; i++) {
  147. var rule = rules[i];
  148. var reduced = rule[1];
  149. reduced = reduced.replace(HTML_COMMENT_PATTERN, removeHTMLComment.bind(null, rule));
  150. if (hasInvalidCharacters(reduced)) {
  151. warnings.push('Invalid selector \'' + rule[1] + '\' at ' + formatPosition(rule[2][0]) + '. Ignoring.');
  152. continue;
  153. }
  154. reduced = removeWhitespace(reduced, format);
  155. reduced = removeQuotes(reduced);
  156. if (adjacentSpace && reduced.indexOf('nav') > 0) {
  157. reduced = reduced.replace(/\+nav(\S|$)/, '+ nav$1');
  158. }
  159. if (removeUnsupported && reduced.indexOf(ASTERISK_PLUS_HTML_HACK) > -1) {
  160. continue;
  161. }
  162. if (removeUnsupported && reduced.indexOf(ASTERISK_FIRST_CHILD_PLUS_HTML_HACK) > -1) {
  163. continue;
  164. }
  165. if (reduced.indexOf('*') > -1) {
  166. reduced = reduced
  167. .replace(/\*([:#\.\[])/g, '$1')
  168. .replace(/^(\:first\-child)?\+html/, '*$1+html');
  169. }
  170. if (repeated.indexOf(reduced) > -1) {
  171. continue;
  172. }
  173. rule[1] = reduced;
  174. repeated.push(reduced);
  175. list.push(rule);
  176. }
  177. if (list.length == 1 && list[0][1].length === 0) {
  178. warnings.push('Empty selector \'' + list[0][1] + '\' at ' + formatPosition(list[0][2][0]) + '. Ignoring.');
  179. list = [];
  180. }
  181. return list;
  182. }
  183. module.exports = tidyRules;