123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- /*!
- * Stylus - Normalizer
- * Copyright (c) Automattic <developer.wordpress.com>
- * MIT Licensed
- */
- /**
- * Module dependencies.
- */
- var Visitor = require('./')
- , nodes = require('../nodes')
- , utils = require('../utils');
- /**
- * Initialize a new `Normalizer` with the given `root` Node.
- *
- * This visitor implements the first stage of the duel-stage
- * compiler, tasked with stripping the "garbage" from
- * the evaluated nodes, ditching null rules, resolving
- * ruleset selectors etc. This step performs the logic
- * necessary to facilitate the "@extend" functionality,
- * as these must be resolved _before_ buffering output.
- *
- * @param {Node} root
- * @api public
- */
- var Normalizer = module.exports = function Normalizer(root, options) {
- options = options || {};
- Visitor.call(this, root);
- this.hoist = options['hoist atrules'];
- this.stack = [];
- this.map = {};
- this.imports = [];
- };
- /**
- * Inherit from `Visitor.prototype`.
- */
- Normalizer.prototype.__proto__ = Visitor.prototype;
- /**
- * Normalize the node tree.
- *
- * @return {Node}
- * @api private
- */
- Normalizer.prototype.normalize = function(){
- var ret = this.visit(this.root);
- if (this.hoist) {
- // hoist @import
- if (this.imports.length) ret.nodes = this.imports.concat(ret.nodes);
- // hoist @charset
- if (this.charset) ret.nodes = [this.charset].concat(ret.nodes);
- }
- return ret;
- };
- /**
- * Bubble up the given `node`.
- *
- * @param {Node} node
- * @api private
- */
- Normalizer.prototype.bubble = function(node){
- var props = []
- , other = []
- , self = this;
- function filterProps(block) {
- block.nodes.forEach(function(node) {
- node = self.visit(node);
- switch (node.nodeName) {
- case 'property':
- props.push(node);
- break;
- case 'block':
- filterProps(node);
- break;
- default:
- other.push(node);
- }
- });
- }
- filterProps(node.block);
- if (props.length) {
- var selector = new nodes.Selector([new nodes.Literal('&')]);
- selector.lineno = node.lineno;
- selector.column = node.column;
- selector.filename = node.filename;
- selector.val = '&';
- var group = new nodes.Group;
- group.lineno = node.lineno;
- group.column = node.column;
- group.filename = node.filename;
- var block = new nodes.Block(node.block, group);
- block.lineno = node.lineno;
- block.column = node.column;
- block.filename = node.filename;
- props.forEach(function(prop){
- block.push(prop);
- });
- group.push(selector);
- group.block = block;
- node.block.nodes = [];
- node.block.push(group);
- other.forEach(function(n){
- node.block.push(n);
- });
- var group = this.closestGroup(node.block);
- if (group) node.group = group.clone();
- node.bubbled = true;
- }
- };
- /**
- * Return group closest to the given `block`.
- *
- * @param {Block} block
- * @return {Group}
- * @api private
- */
- Normalizer.prototype.closestGroup = function(block){
- var parent = block.parent
- , node;
- while (parent && (node = parent.node)) {
- if ('group' == node.nodeName) return node;
- parent = node.block && node.block.parent;
- }
- };
- /**
- * Visit Root.
- */
- Normalizer.prototype.visitRoot = function(block){
- var ret = new nodes.Root
- , node;
- for (var i = 0; i < block.nodes.length; ++i) {
- node = block.nodes[i];
- switch (node.nodeName) {
- case 'null':
- case 'expression':
- case 'function':
- case 'unit':
- case 'atblock':
- continue;
- default:
- this.rootIndex = i;
- ret.push(this.visit(node));
- }
- }
- return ret;
- };
- /**
- * Visit Property.
- */
- Normalizer.prototype.visitProperty = function(prop){
- this.visit(prop.expr);
- return prop;
- };
- /**
- * Visit Expression.
- */
- Normalizer.prototype.visitExpression = function(expr){
- expr.nodes = expr.nodes.map(function(node){
- // returns `block` literal if mixin's block
- // is used as part of a property value
- if ('block' == node.nodeName) {
- var literal = new nodes.Literal('block');
- literal.lineno = expr.lineno;
- literal.column = expr.column;
- return literal;
- }
- return node;
- });
- return expr;
- };
- /**
- * Visit Block.
- */
- Normalizer.prototype.visitBlock = function(block){
- var node;
- if (block.hasProperties) {
- for (var i = 0, len = block.nodes.length; i < len; ++i) {
- node = block.nodes[i];
- switch (node.nodeName) {
- case 'null':
- case 'expression':
- case 'function':
- case 'group':
- case 'unit':
- case 'atblock':
- continue;
- default:
- block.nodes[i] = this.visit(node);
- }
- }
- }
- // nesting
- for (var i = 0, len = block.nodes.length; i < len; ++i) {
- node = block.nodes[i];
- block.nodes[i] = this.visit(node);
- }
- return block;
- };
- /**
- * Visit Group.
- */
- Normalizer.prototype.visitGroup = function(group){
- var stack = this.stack
- , map = this.map
- , parts;
- // normalize interpolated selectors with comma
- group.nodes.forEach(function(selector, i){
- if (!~selector.val.indexOf(',')) return;
- if (~selector.val.indexOf('\\,')) {
- selector.val = selector.val.replace(/\\,/g, ',');
- return;
- }
- parts = selector.val.split(',');
- var root = '/' == selector.val.charAt(0)
- , part, s;
- for (var k = 0, len = parts.length; k < len; ++k){
- part = parts[k].trim();
- if (root && k > 0 && !~part.indexOf('&')) {
- part = '/' + part;
- }
- s = new nodes.Selector([new nodes.Literal(part)]);
- s.val = part;
- s.block = group.block;
- group.nodes[i++] = s;
- }
- });
- stack.push(group.nodes);
- var selectors = utils.compileSelectors(stack, true);
- // map for extension lookup
- selectors.forEach(function(selector){
- map[selector] = map[selector] || [];
- map[selector].push(group);
- });
- // extensions
- this.extend(group, selectors);
- stack.pop();
- return group;
- };
- /**
- * Visit Function.
- */
- Normalizer.prototype.visitFunction = function(){
- return nodes.null;
- };
- /**
- * Visit Media.
- */
- Normalizer.prototype.visitMedia = function(media){
- var medias = []
- , group = this.closestGroup(media.block)
- , parent;
- function mergeQueries(block) {
- block.nodes.forEach(function(node, i){
- switch (node.nodeName) {
- case 'media':
- node.val = media.val.merge(node.val);
- medias.push(node);
- block.nodes[i] = nodes.null;
- break;
- case 'block':
- mergeQueries(node);
- break;
- default:
- if (node.block && node.block.nodes)
- mergeQueries(node.block);
- }
- });
- }
- mergeQueries(media.block);
- this.bubble(media);
- if (medias.length) {
- medias.forEach(function(node){
- if (group) {
- group.block.push(node);
- } else {
- this.root.nodes.splice(++this.rootIndex, 0, node);
- }
- node = this.visit(node);
- parent = node.block.parent;
- if (node.bubbled && (!group || 'group' == parent.node.nodeName)) {
- node.group.block = node.block.nodes[0].block;
- node.block.nodes[0] = node.group;
- }
- }, this);
- }
- return media;
- };
- /**
- * Visit Supports.
- */
- Normalizer.prototype.visitSupports = function(node){
- this.bubble(node);
- return node;
- };
- /**
- * Visit Atrule.
- */
- Normalizer.prototype.visitAtrule = function(node){
- if (node.block) node.block = this.visit(node.block);
- return node;
- };
- /**
- * Visit Keyframes.
- */
- Normalizer.prototype.visitKeyframes = function(node){
- var frames = node.block.nodes.filter(function(frame){
- return frame.block && frame.block.hasProperties;
- });
- node.frames = frames.length;
- return node;
- };
- /**
- * Visit Import.
- */
- Normalizer.prototype.visitImport = function(node){
- this.imports.push(node);
- return this.hoist ? nodes.null : node;
- };
- /**
- * Visit Charset.
- */
- Normalizer.prototype.visitCharset = function(node){
- this.charset = node;
- return this.hoist ? nodes.null : node;
- };
- /**
- * Apply `group` extensions.
- *
- * @param {Group} group
- * @param {Array} selectors
- * @api private
- */
- Normalizer.prototype.extend = function(group, selectors){
- var map = this.map
- , self = this
- , parent = this.closestGroup(group.block);
- group.extends.forEach(function(extend){
- var groups = map[extend.selector];
- if (!groups) {
- if (extend.optional) return;
- groups = self._checkForPrefixedGroups(extend.selector);
- if(!groups) {
- var err = new Error('Failed to @extend "' + extend.selector + '"');
- err.lineno = extend.lineno;
- err.column = extend.column;
- throw err;
- }
- }
- selectors.forEach(function(selector){
- var node = new nodes.Selector;
- node.val = selector;
- node.inherits = false;
- groups.forEach(function(group){
- // prevent recursive extend
- if (!parent || (parent != group)) self.extend(group, selectors);
- group.push(node);
- });
- });
- });
- group.block = this.visit(group.block);
- };
- Normalizer.prototype._checkForPrefixedGroups = function (selector) {
- var prefix = [];
- var map = this.map;
- var result = null;
- for (var i = 0; i < this.stack.length; i++) {
- var stackElementArray=this.stack[i];
- var stackElement = stackElementArray[0];
- prefix.push(stackElement.val);
- var fullSelector = prefix.join(" ") + " " + selector;
- result = map[fullSelector];
- if (result)
- break;
- }
- return result;
- };
|