normalizer.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /*!
  2. * Stylus - Normalizer
  3. * Copyright (c) Automattic <developer.wordpress.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var Visitor = require('./')
  10. , nodes = require('../nodes')
  11. , utils = require('../utils');
  12. /**
  13. * Initialize a new `Normalizer` with the given `root` Node.
  14. *
  15. * This visitor implements the first stage of the duel-stage
  16. * compiler, tasked with stripping the "garbage" from
  17. * the evaluated nodes, ditching null rules, resolving
  18. * ruleset selectors etc. This step performs the logic
  19. * necessary to facilitate the "@extend" functionality,
  20. * as these must be resolved _before_ buffering output.
  21. *
  22. * @param {Node} root
  23. * @api public
  24. */
  25. var Normalizer = module.exports = function Normalizer(root, options) {
  26. options = options || {};
  27. Visitor.call(this, root);
  28. this.hoist = options['hoist atrules'];
  29. this.stack = [];
  30. this.map = {};
  31. this.imports = [];
  32. };
  33. /**
  34. * Inherit from `Visitor.prototype`.
  35. */
  36. Normalizer.prototype.__proto__ = Visitor.prototype;
  37. /**
  38. * Normalize the node tree.
  39. *
  40. * @return {Node}
  41. * @api private
  42. */
  43. Normalizer.prototype.normalize = function(){
  44. var ret = this.visit(this.root);
  45. if (this.hoist) {
  46. // hoist @import
  47. if (this.imports.length) ret.nodes = this.imports.concat(ret.nodes);
  48. // hoist @charset
  49. if (this.charset) ret.nodes = [this.charset].concat(ret.nodes);
  50. }
  51. return ret;
  52. };
  53. /**
  54. * Bubble up the given `node`.
  55. *
  56. * @param {Node} node
  57. * @api private
  58. */
  59. Normalizer.prototype.bubble = function(node){
  60. var props = []
  61. , other = []
  62. , self = this;
  63. function filterProps(block) {
  64. block.nodes.forEach(function(node) {
  65. node = self.visit(node);
  66. switch (node.nodeName) {
  67. case 'property':
  68. props.push(node);
  69. break;
  70. case 'block':
  71. filterProps(node);
  72. break;
  73. default:
  74. other.push(node);
  75. }
  76. });
  77. }
  78. filterProps(node.block);
  79. if (props.length) {
  80. var selector = new nodes.Selector([new nodes.Literal('&')]);
  81. selector.lineno = node.lineno;
  82. selector.column = node.column;
  83. selector.filename = node.filename;
  84. selector.val = '&';
  85. var group = new nodes.Group;
  86. group.lineno = node.lineno;
  87. group.column = node.column;
  88. group.filename = node.filename;
  89. var block = new nodes.Block(node.block, group);
  90. block.lineno = node.lineno;
  91. block.column = node.column;
  92. block.filename = node.filename;
  93. props.forEach(function(prop){
  94. block.push(prop);
  95. });
  96. group.push(selector);
  97. group.block = block;
  98. node.block.nodes = [];
  99. node.block.push(group);
  100. other.forEach(function(n){
  101. node.block.push(n);
  102. });
  103. var group = this.closestGroup(node.block);
  104. if (group) node.group = group.clone();
  105. node.bubbled = true;
  106. }
  107. };
  108. /**
  109. * Return group closest to the given `block`.
  110. *
  111. * @param {Block} block
  112. * @return {Group}
  113. * @api private
  114. */
  115. Normalizer.prototype.closestGroup = function(block){
  116. var parent = block.parent
  117. , node;
  118. while (parent && (node = parent.node)) {
  119. if ('group' == node.nodeName) return node;
  120. parent = node.block && node.block.parent;
  121. }
  122. };
  123. /**
  124. * Visit Root.
  125. */
  126. Normalizer.prototype.visitRoot = function(block){
  127. var ret = new nodes.Root
  128. , node;
  129. for (var i = 0; i < block.nodes.length; ++i) {
  130. node = block.nodes[i];
  131. switch (node.nodeName) {
  132. case 'null':
  133. case 'expression':
  134. case 'function':
  135. case 'unit':
  136. case 'atblock':
  137. continue;
  138. default:
  139. this.rootIndex = i;
  140. ret.push(this.visit(node));
  141. }
  142. }
  143. return ret;
  144. };
  145. /**
  146. * Visit Property.
  147. */
  148. Normalizer.prototype.visitProperty = function(prop){
  149. this.visit(prop.expr);
  150. return prop;
  151. };
  152. /**
  153. * Visit Expression.
  154. */
  155. Normalizer.prototype.visitExpression = function(expr){
  156. expr.nodes = expr.nodes.map(function(node){
  157. // returns `block` literal if mixin's block
  158. // is used as part of a property value
  159. if ('block' == node.nodeName) {
  160. var literal = new nodes.Literal('block');
  161. literal.lineno = expr.lineno;
  162. literal.column = expr.column;
  163. return literal;
  164. }
  165. return node;
  166. });
  167. return expr;
  168. };
  169. /**
  170. * Visit Block.
  171. */
  172. Normalizer.prototype.visitBlock = function(block){
  173. var node;
  174. if (block.hasProperties) {
  175. for (var i = 0, len = block.nodes.length; i < len; ++i) {
  176. node = block.nodes[i];
  177. switch (node.nodeName) {
  178. case 'null':
  179. case 'expression':
  180. case 'function':
  181. case 'group':
  182. case 'unit':
  183. case 'atblock':
  184. continue;
  185. default:
  186. block.nodes[i] = this.visit(node);
  187. }
  188. }
  189. }
  190. // nesting
  191. for (var i = 0, len = block.nodes.length; i < len; ++i) {
  192. node = block.nodes[i];
  193. block.nodes[i] = this.visit(node);
  194. }
  195. return block;
  196. };
  197. /**
  198. * Visit Group.
  199. */
  200. Normalizer.prototype.visitGroup = function(group){
  201. var stack = this.stack
  202. , map = this.map
  203. , parts;
  204. // normalize interpolated selectors with comma
  205. group.nodes.forEach(function(selector, i){
  206. if (!~selector.val.indexOf(',')) return;
  207. if (~selector.val.indexOf('\\,')) {
  208. selector.val = selector.val.replace(/\\,/g, ',');
  209. return;
  210. }
  211. parts = selector.val.split(',');
  212. var root = '/' == selector.val.charAt(0)
  213. , part, s;
  214. for (var k = 0, len = parts.length; k < len; ++k){
  215. part = parts[k].trim();
  216. if (root && k > 0 && !~part.indexOf('&')) {
  217. part = '/' + part;
  218. }
  219. s = new nodes.Selector([new nodes.Literal(part)]);
  220. s.val = part;
  221. s.block = group.block;
  222. group.nodes[i++] = s;
  223. }
  224. });
  225. stack.push(group.nodes);
  226. var selectors = utils.compileSelectors(stack, true);
  227. // map for extension lookup
  228. selectors.forEach(function(selector){
  229. map[selector] = map[selector] || [];
  230. map[selector].push(group);
  231. });
  232. // extensions
  233. this.extend(group, selectors);
  234. stack.pop();
  235. return group;
  236. };
  237. /**
  238. * Visit Function.
  239. */
  240. Normalizer.prototype.visitFunction = function(){
  241. return nodes.null;
  242. };
  243. /**
  244. * Visit Media.
  245. */
  246. Normalizer.prototype.visitMedia = function(media){
  247. var medias = []
  248. , group = this.closestGroup(media.block)
  249. , parent;
  250. function mergeQueries(block) {
  251. block.nodes.forEach(function(node, i){
  252. switch (node.nodeName) {
  253. case 'media':
  254. node.val = media.val.merge(node.val);
  255. medias.push(node);
  256. block.nodes[i] = nodes.null;
  257. break;
  258. case 'block':
  259. mergeQueries(node);
  260. break;
  261. default:
  262. if (node.block && node.block.nodes)
  263. mergeQueries(node.block);
  264. }
  265. });
  266. }
  267. mergeQueries(media.block);
  268. this.bubble(media);
  269. if (medias.length) {
  270. medias.forEach(function(node){
  271. if (group) {
  272. group.block.push(node);
  273. } else {
  274. this.root.nodes.splice(++this.rootIndex, 0, node);
  275. }
  276. node = this.visit(node);
  277. parent = node.block.parent;
  278. if (node.bubbled && (!group || 'group' == parent.node.nodeName)) {
  279. node.group.block = node.block.nodes[0].block;
  280. node.block.nodes[0] = node.group;
  281. }
  282. }, this);
  283. }
  284. return media;
  285. };
  286. /**
  287. * Visit Supports.
  288. */
  289. Normalizer.prototype.visitSupports = function(node){
  290. this.bubble(node);
  291. return node;
  292. };
  293. /**
  294. * Visit Atrule.
  295. */
  296. Normalizer.prototype.visitAtrule = function(node){
  297. if (node.block) node.block = this.visit(node.block);
  298. return node;
  299. };
  300. /**
  301. * Visit Keyframes.
  302. */
  303. Normalizer.prototype.visitKeyframes = function(node){
  304. var frames = node.block.nodes.filter(function(frame){
  305. return frame.block && frame.block.hasProperties;
  306. });
  307. node.frames = frames.length;
  308. return node;
  309. };
  310. /**
  311. * Visit Import.
  312. */
  313. Normalizer.prototype.visitImport = function(node){
  314. this.imports.push(node);
  315. return this.hoist ? nodes.null : node;
  316. };
  317. /**
  318. * Visit Charset.
  319. */
  320. Normalizer.prototype.visitCharset = function(node){
  321. this.charset = node;
  322. return this.hoist ? nodes.null : node;
  323. };
  324. /**
  325. * Apply `group` extensions.
  326. *
  327. * @param {Group} group
  328. * @param {Array} selectors
  329. * @api private
  330. */
  331. Normalizer.prototype.extend = function(group, selectors){
  332. var map = this.map
  333. , self = this
  334. , parent = this.closestGroup(group.block);
  335. group.extends.forEach(function(extend){
  336. var groups = map[extend.selector];
  337. if (!groups) {
  338. if (extend.optional) return;
  339. groups = self._checkForPrefixedGroups(extend.selector);
  340. if(!groups) {
  341. var err = new Error('Failed to @extend "' + extend.selector + '"');
  342. err.lineno = extend.lineno;
  343. err.column = extend.column;
  344. throw err;
  345. }
  346. }
  347. selectors.forEach(function(selector){
  348. var node = new nodes.Selector;
  349. node.val = selector;
  350. node.inherits = false;
  351. groups.forEach(function(group){
  352. // prevent recursive extend
  353. if (!parent || (parent != group)) self.extend(group, selectors);
  354. group.push(node);
  355. });
  356. });
  357. });
  358. group.block = this.visit(group.block);
  359. };
  360. Normalizer.prototype._checkForPrefixedGroups = function (selector) {
  361. var prefix = [];
  362. var map = this.map;
  363. var result = null;
  364. for (var i = 0; i < this.stack.length; i++) {
  365. var stackElementArray=this.stack[i];
  366. var stackElement = stackElementArray[0];
  367. prefix.push(stackElement.val);
  368. var fullSelector = prefix.join(" ") + " " + selector;
  369. result = map[fullSelector];
  370. if (result)
  371. break;
  372. }
  373. return result;
  374. };