is-mergeable.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. var Marker = require('../../tokenizer/marker');
  2. var split = require('../../utils/split');
  3. var DEEP_SELECTOR_PATTERN = /\/deep\//;
  4. var DOUBLE_COLON_PATTERN = /^::/;
  5. var NOT_PSEUDO = ':not';
  6. var PSEUDO_CLASSES_WITH_ARGUMENTS = [
  7. ':dir',
  8. ':lang',
  9. ':not',
  10. ':nth-child',
  11. ':nth-last-child',
  12. ':nth-last-of-type',
  13. ':nth-of-type'
  14. ];
  15. var RELATION_PATTERN = /[>\+~]/;
  16. var Level = {
  17. DOUBLE_QUOTE: 'double-quote',
  18. SINGLE_QUOTE: 'single-quote',
  19. ROOT: 'root'
  20. };
  21. function isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements) {
  22. var singleSelectors = split(selector, Marker.COMMA);
  23. var singleSelector;
  24. var i, l;
  25. for (i = 0, l = singleSelectors.length; i < l; i++) {
  26. singleSelector = singleSelectors[i];
  27. if (singleSelector.length === 0 ||
  28. isDeepSelector(singleSelector) ||
  29. (singleSelector.indexOf(Marker.COLON) > -1 && !areMergeable(singleSelector, extractPseudoFrom(singleSelector), mergeablePseudoClasses, mergeablePseudoElements))) {
  30. return false;
  31. }
  32. }
  33. return true;
  34. }
  35. function isDeepSelector(selector) {
  36. return DEEP_SELECTOR_PATTERN.test(selector);
  37. }
  38. function extractPseudoFrom(selector) {
  39. var list = [];
  40. var character;
  41. var buffer = [];
  42. var level = Level.ROOT;
  43. var roundBracketLevel = 0;
  44. var isQuoted;
  45. var isEscaped;
  46. var isPseudo = false;
  47. var isRelation;
  48. var wasColon = false;
  49. var index;
  50. var len;
  51. for (index = 0, len = selector.length; index < len; index++) {
  52. character = selector[index];
  53. isRelation = !isEscaped && RELATION_PATTERN.test(character);
  54. isQuoted = level == Level.DOUBLE_QUOTE || level == Level.SINGLE_QUOTE;
  55. if (isEscaped) {
  56. buffer.push(character);
  57. } else if (character == Marker.DOUBLE_QUOTE && level == Level.ROOT) {
  58. buffer.push(character);
  59. level = Level.DOUBLE_QUOTE;
  60. } else if (character == Marker.DOUBLE_QUOTE && level == Level.DOUBLE_QUOTE) {
  61. buffer.push(character);
  62. level = Level.ROOT;
  63. } else if (character == Marker.SINGLE_QUOTE && level == Level.ROOT) {
  64. buffer.push(character);
  65. level = Level.SINGLE_QUOTE;
  66. } else if (character == Marker.SINGLE_QUOTE && level == Level.SINGLE_QUOTE) {
  67. buffer.push(character);
  68. level = Level.ROOT;
  69. } else if (isQuoted) {
  70. buffer.push(character);
  71. } else if (character == Marker.OPEN_ROUND_BRACKET) {
  72. buffer.push(character);
  73. roundBracketLevel++;
  74. } else if (character == Marker.CLOSE_ROUND_BRACKET && roundBracketLevel == 1 && isPseudo) {
  75. buffer.push(character);
  76. list.push(buffer.join(''));
  77. roundBracketLevel--;
  78. buffer = [];
  79. isPseudo = false;
  80. } else if (character == Marker.CLOSE_ROUND_BRACKET) {
  81. buffer.push(character);
  82. roundBracketLevel--;
  83. } else if (character == Marker.COLON && roundBracketLevel === 0 && isPseudo && !wasColon) {
  84. list.push(buffer.join(''));
  85. buffer = [];
  86. buffer.push(character);
  87. } else if (character == Marker.COLON && roundBracketLevel === 0 && !wasColon) {
  88. buffer = [];
  89. buffer.push(character);
  90. isPseudo = true;
  91. } else if (character == Marker.SPACE && roundBracketLevel === 0 && isPseudo) {
  92. list.push(buffer.join(''));
  93. buffer = [];
  94. isPseudo = false;
  95. } else if (isRelation && roundBracketLevel === 0 && isPseudo) {
  96. list.push(buffer.join(''));
  97. buffer = [];
  98. isPseudo = false;
  99. } else {
  100. buffer.push(character);
  101. }
  102. isEscaped = character == Marker.BACK_SLASH;
  103. wasColon = character == Marker.COLON;
  104. }
  105. if (buffer.length > 0 && isPseudo) {
  106. list.push(buffer.join(''));
  107. }
  108. return list;
  109. }
  110. function areMergeable(selector, matches, mergeablePseudoClasses, mergeablePseudoElements) {
  111. return areAllowed(matches, mergeablePseudoClasses, mergeablePseudoElements) &&
  112. needArguments(matches) &&
  113. (matches.length < 2 || !someIncorrectlyChained(selector, matches)) &&
  114. (matches.length < 2 || !someMixed(matches));
  115. }
  116. function areAllowed(matches, mergeablePseudoClasses, mergeablePseudoElements) {
  117. var match;
  118. var name;
  119. var i, l;
  120. for (i = 0, l = matches.length; i < l; i++) {
  121. match = matches[i];
  122. name = match.indexOf(Marker.OPEN_ROUND_BRACKET) > -1 ?
  123. match.substring(0, match.indexOf(Marker.OPEN_ROUND_BRACKET)) :
  124. match;
  125. if (mergeablePseudoClasses.indexOf(name) === -1 && mergeablePseudoElements.indexOf(name) === -1) {
  126. return false;
  127. }
  128. }
  129. return true;
  130. }
  131. function needArguments(matches) {
  132. var match;
  133. var name;
  134. var bracketOpensAt;
  135. var hasArguments;
  136. var i, l;
  137. for (i = 0, l = matches.length; i < l; i++) {
  138. match = matches[i];
  139. bracketOpensAt = match.indexOf(Marker.OPEN_ROUND_BRACKET);
  140. hasArguments = bracketOpensAt > -1;
  141. name = hasArguments ?
  142. match.substring(0, bracketOpensAt) :
  143. match;
  144. if (hasArguments && PSEUDO_CLASSES_WITH_ARGUMENTS.indexOf(name) == -1) {
  145. return false;
  146. }
  147. if (!hasArguments && PSEUDO_CLASSES_WITH_ARGUMENTS.indexOf(name) > -1) {
  148. return false;
  149. }
  150. }
  151. return true;
  152. }
  153. function someIncorrectlyChained(selector, matches) {
  154. var positionInSelector = 0;
  155. var match;
  156. var matchAt;
  157. var nextMatch;
  158. var nextMatchAt;
  159. var name;
  160. var nextName;
  161. var areChained;
  162. var i, l;
  163. for (i = 0, l = matches.length; i < l; i++) {
  164. match = matches[i];
  165. nextMatch = matches[i + 1];
  166. if (!nextMatch) {
  167. break;
  168. }
  169. matchAt = selector.indexOf(match, positionInSelector);
  170. nextMatchAt = selector.indexOf(match, matchAt + 1);
  171. positionInSelector = nextMatchAt;
  172. areChained = matchAt + match.length == nextMatchAt;
  173. if (areChained) {
  174. name = match.indexOf(Marker.OPEN_ROUND_BRACKET) > -1 ?
  175. match.substring(0, match.indexOf(Marker.OPEN_ROUND_BRACKET)) :
  176. match;
  177. nextName = nextMatch.indexOf(Marker.OPEN_ROUND_BRACKET) > -1 ?
  178. nextMatch.substring(0, nextMatch.indexOf(Marker.OPEN_ROUND_BRACKET)) :
  179. nextMatch;
  180. if (name != NOT_PSEUDO || nextName != NOT_PSEUDO) {
  181. return true;
  182. }
  183. }
  184. }
  185. return false;
  186. }
  187. function someMixed(matches) {
  188. var firstIsPseudoElement = DOUBLE_COLON_PATTERN.test(matches[0]);
  189. var match;
  190. var i, l;
  191. for (i = 0, l = matches.length; i < l; i++) {
  192. match = matches[i];
  193. if (DOUBLE_COLON_PATTERN.test(match) != firstIsPseudoElement) {
  194. return true;
  195. }
  196. }
  197. return false;
  198. }
  199. module.exports = isMergeable;