hexo/node_modules/stylus/lib/visitor/compiler.js

589 lines
13 KiB
JavaScript

/*!
* Stylus - Compiler
* Copyright (c) Automattic <developer.wordpress.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Visitor = require('./')
, utils = require('../utils')
, fs = require('fs');
/**
* Initialize a new `Compiler` with the given `root` Node
* and the following `options`.
*
* Options:
*
* - `compress` Compress the CSS output (default: false)
*
* @param {Node} root
* @api public
*/
var Compiler = module.exports = function Compiler(root, options) {
options = options || {};
this.compress = options.compress;
this.firebug = options.firebug;
this.linenos = options.linenos;
this.spaces = options['indent spaces'] || 2;
this.indents = 1;
Visitor.call(this, root);
this.stack = [];
};
/**
* Inherit from `Visitor.prototype`.
*/
Compiler.prototype.__proto__ = Visitor.prototype;
/**
* Compile to css, and return a string of CSS.
*
* @return {String}
* @api private
*/
Compiler.prototype.compile = function(){
return this.visit(this.root);
};
/**
* Output `str`
*
* @param {String} str
* @param {Node} node
* @return {String}
* @api private
*/
Compiler.prototype.out = function(str, node){
return str;
};
/**
* Return indentation string.
*
* @return {String}
* @api private
*/
Compiler.prototype.__defineGetter__('indent', function(){
if (this.compress) return '';
return new Array(this.indents).join(Array(this.spaces + 1).join(' '));
});
/**
* Check if given `node` needs brackets.
*
* @param {Node} node
* @return {Boolean}
* @api private
*/
Compiler.prototype.needBrackets = function(node){
return 1 == this.indents
|| 'atrule' != node.nodeName
|| node.hasOnlyProperties;
};
/**
* Visit Root.
*/
Compiler.prototype.visitRoot = function(block){
this.buf = '';
for (var i = 0, len = block.nodes.length; i < len; ++i) {
var node = block.nodes[i];
if (this.linenos || this.firebug) this.debugInfo(node);
var ret = this.visit(node);
if (ret) this.buf += this.out(ret + '\n', node);
}
return this.buf;
};
/**
* Visit Block.
*/
Compiler.prototype.visitBlock = function(block){
var node
, separator = this.compress ? '' : '\n'
, needBrackets
, lastPropertyIndex;
if (block.hasProperties && !block.lacksRenderedSelectors) {
needBrackets = this.needBrackets(block.node);
if (this.compress) {
for (var i = block.nodes.length - 1; i >= 0; --i) {
if (block.nodes[i].nodeName === 'property') {
lastPropertyIndex = i;
break;
}
}
}
if (needBrackets) {
this.buf += this.out(this.compress ? '{' : ' {\n');
++this.indents;
}
for (var i = 0, len = block.nodes.length; i < len; ++i) {
this.last = lastPropertyIndex === i;
node = block.nodes[i];
switch (node.nodeName) {
case 'null':
case 'expression':
case 'function':
case 'group':
case 'block':
case 'unit':
case 'media':
case 'keyframes':
case 'atrule':
case 'supports':
continue;
// inline comments
case !this.compress && node.inline && 'comment':
this.buf = this.buf.slice(0, -1);
this.buf += this.out(' ' + this.visit(node) + '\n', node);
break;
case 'property':
var ret = this.visit(node) + separator;
this.buf += this.compress ? ret : this.out(ret, node);
break;
default:
this.buf += this.out(this.visit(node) + separator, node);
}
}
if (needBrackets) {
--this.indents;
this.buf += this.out(this.indent + '}' + separator);
}
}
// Nesting
for (var i = 0, len = block.nodes.length; i < len; ++i) {
node = block.nodes[i];
switch (node.nodeName) {
case 'group':
case 'block':
case 'keyframes':
if (this.linenos || this.firebug) this.debugInfo(node);
this.visit(node);
break;
case 'media':
case 'import':
case 'atrule':
case 'supports':
this.visit(node);
break;
case 'comment':
// only show unsuppressed comments
if (!node.suppress) {
this.buf += this.out(this.indent + this.visit(node) + '\n', node);
}
break;
case 'charset':
case 'literal':
case 'namespace':
this.buf += this.out(this.visit(node) + '\n', node);
break;
}
}
};
/**
* Visit Keyframes.
*/
Compiler.prototype.visitKeyframes = function(node){
if (!node.frames) return;
var prefix = 'official' == node.prefix
? ''
: '-' + node.prefix + '-';
this.buf += this.out('@' + prefix + 'keyframes '
+ this.visit(node.val)
+ (this.compress ? '{' : ' {\n'), node);
this.keyframe = true;
++this.indents;
this.visit(node.block);
--this.indents;
this.keyframe = false;
this.buf += this.out('}' + (this.compress ? '' : '\n'));
};
/**
* Visit Media.
*/
Compiler.prototype.visitMedia = function(media){
var val = media.val;
if (!media.hasOutput || !val.nodes.length) return;
this.buf += this.out('@media ', media);
this.visit(val);
this.buf += this.out(this.compress ? '{' : ' {\n');
++this.indents;
this.visit(media.block);
--this.indents;
this.buf += this.out('}' + (this.compress ? '' : '\n'));
};
/**
* Visit QueryList.
*/
Compiler.prototype.visitQueryList = function(queries){
for (var i = 0, len = queries.nodes.length; i < len; ++i) {
this.visit(queries.nodes[i]);
if (len - 1 != i) this.buf += this.out(',' + (this.compress ? '' : ' '));
}
};
/**
* Visit Query.
*/
Compiler.prototype.visitQuery = function(node){
var len = node.nodes.length;
if (node.predicate) this.buf += this.out(node.predicate + ' ');
if (node.type) this.buf += this.out(node.type + (len ? ' and ' : ''));
for (var i = 0; i < len; ++i) {
this.buf += this.out(this.visit(node.nodes[i]));
if (len - 1 != i) this.buf += this.out(' and ');
}
};
/**
* Visit Feature.
*/
Compiler.prototype.visitFeature = function(node){
if (!node.expr) {
return node.name;
} else if (node.expr.isEmpty) {
return '(' + node.name + ')';
} else {
return '(' + node.name + ':' + (this.compress ? '' : ' ') + this.visit(node.expr) + ')';
}
};
/**
* Visit Import.
*/
Compiler.prototype.visitImport = function(imported){
this.buf += this.out('@import ' + this.visit(imported.path) + ';\n', imported);
};
/**
* Visit Atrule.
*/
Compiler.prototype.visitAtrule = function(atrule){
var newline = this.compress ? '' : '\n';
this.buf += this.out(this.indent + '@' + atrule.type, atrule);
if (atrule.val) this.buf += this.out(' ' + atrule.val.trim());
if (atrule.block) {
if (atrule.block.isEmpty) {
this.buf += this.out((this.compress ? '' : ' ') + '{}' + newline);
} else if (atrule.hasOnlyProperties) {
this.visit(atrule.block);
} else {
this.buf += this.out(this.compress ? '{' : ' {\n');
++this.indents;
this.visit(atrule.block);
--this.indents;
this.buf += this.out(this.indent + '}' + newline);
}
} else {
this.buf += this.out(';' + newline);
}
};
/**
* Visit Supports.
*/
Compiler.prototype.visitSupports = function(node){
if (!node.hasOutput) return;
this.buf += this.out(this.indent + '@supports ', node);
this.isCondition = true;
this.buf += this.out(this.visit(node.condition));
this.isCondition = false;
this.buf += this.out(this.compress ? '{' : ' {\n');
++this.indents;
this.visit(node.block);
--this.indents;
this.buf += this.out(this.indent + '}' + (this.compress ? '' : '\n'));
},
/**
* Visit Comment.
*/
Compiler.prototype.visitComment = function(comment){
return this.compress
? comment.suppress
? ''
: comment.str
: comment.str;
};
/**
* Visit Function.
*/
Compiler.prototype.visitFunction = function(fn){
return fn.name;
};
/**
* Visit Charset.
*/
Compiler.prototype.visitCharset = function(charset){
return '@charset ' + this.visit(charset.val) + ';';
};
/**
* Visit Namespace.
*/
Compiler.prototype.visitNamespace = function(namespace){
return '@namespace '
+ (namespace.prefix ? this.visit(namespace.prefix) + ' ' : '')
+ this.visit(namespace.val) + ';';
};
/**
* Visit Literal.
*/
Compiler.prototype.visitLiteral = function(lit){
var val = lit.val;
if (lit.css) val = val.replace(/^ /gm, '');
return val;
};
/**
* Visit Boolean.
*/
Compiler.prototype.visitBoolean = function(bool){
return bool.toString();
};
/**
* Visit RGBA.
*/
Compiler.prototype.visitRGBA = function(rgba){
return rgba.toString();
};
/**
* Visit HSLA.
*/
Compiler.prototype.visitHSLA = function(hsla){
return hsla.rgba.toString();
};
/**
* Visit Unit.
*/
Compiler.prototype.visitUnit = function(unit){
var type = unit.type || ''
, n = unit.val
, float = n != (n | 0);
// Compress
if (this.compress) {
// Always return '0' unless the unit is a percentage, time, degree or fraction
if (!(['%', 's', 'ms', 'deg', 'fr'].includes(type)) && 0 == n) return '0';
// Omit leading '0' on floats
if (float && n < 1 && n > -1) {
return n.toString().replace('0.', '.') + type;
}
}
return (float ? parseFloat(n.toFixed(15)) : n).toString() + type;
};
/**
* Visit Group.
*/
Compiler.prototype.visitGroup = function(group){
var stack = this.keyframe ? [] : this.stack
, comma = this.compress ? ',' : ',\n';
stack.push(group.nodes);
// selectors
if (group.block.hasProperties) {
var selectors = utils.compileSelectors.call(this, stack)
, len = selectors.length;
if (len) {
if (this.keyframe) comma = this.compress ? ',' : ', ';
for (var i = 0; i < len; ++i) {
var selector = selectors[i]
, last = (i == len - 1);
// keyframe blocks (10%, 20% { ... })
if (this.keyframe) selector = i ? selector.trim() : selector;
this.buf += this.out(selector + (last ? '' : comma), group.nodes[i]);
}
} else {
group.block.lacksRenderedSelectors = true;
}
}
// output block
this.visit(group.block);
stack.pop();
};
/**
* Visit Ident.
*/
Compiler.prototype.visitIdent = function(ident){
return ident.name;
};
/**
* Visit String.
*/
Compiler.prototype.visitString = function(string){
return this.isURL
? string.val
: string.toString();
};
/**
* Visit Null.
*/
Compiler.prototype.visitNull = function(node){
return '';
};
/**
* Visit Call.
*/
Compiler.prototype.visitCall = function(call){
this.isURL = 'url' == call.name;
var args = call.args.nodes.map(function(arg){
return this.visit(arg);
}, this).join(this.compress ? ',' : ', ');
if (this.isURL) args = '"' + args + '"';
this.isURL = false;
return call.name + '(' + args + ')';
};
/**
* Visit Expression.
*/
Compiler.prototype.visitExpression = function(expr){
var buf = []
, self = this
, len = expr.nodes.length
, nodes = expr.nodes.map(function(node){ return self.visit(node); });
nodes.forEach(function(node, i){
var last = i == len - 1;
buf.push(node);
if ('/' == nodes[i + 1] || '/' == node) return;
if (last) return;
var space = self.isURL || (self.isCondition
&& (')' == nodes[i + 1] || '(' == node))
? '' : ' ';
buf.push(expr.isList
? (self.compress ? ',' : ', ')
: space);
});
return buf.join('');
};
/**
* Visit Arguments.
*/
Compiler.prototype.visitArguments = Compiler.prototype.visitExpression;
/**
* Visit Property.
*/
Compiler.prototype.visitProperty = function(prop){
var val = this.visit(prop.expr).trim()
, name = (prop.name || prop.segments.join(''))
, arr = [];
if (name === '@apply') {
arr.push(
this.out(this.indent),
this.out(name + ' ', prop),
this.out(val, prop.expr),
this.out(this.compress ? (this.last ? '' : ';') : ';')
);
return arr.join('');
}
arr.push(
this.out(this.indent),
this.out(name + (this.compress ? ':' : ': '), prop),
this.out(val, prop.expr),
this.out(this.compress ? (this.last ? '' : ';') : ';')
);
return arr.join('');
};
/**
* Debug info.
*/
Compiler.prototype.debugInfo = function(node){
var path = node.filename == 'stdin' ? 'stdin' : fs.realpathSync(node.filename)
, line = (node.nodes && node.nodes.length ? node.nodes[0].lineno : node.lineno) || 1;
if (this.linenos){
this.buf += '\n/* ' + 'line ' + line + ' : ' + path + ' */\n';
}
if (this.firebug){
// debug info for firebug, the crazy formatting is needed
path = 'file\\\:\\\/\\\/' + path.replace(/([.:/\\])/g, function(m) {
return '\\' + (m === '\\' ? '\/' : m)
});
line = '\\00003' + line;
this.buf += '\n@media -stylus-debug-info'
+ '{filename{font-family:' + path
+ '}line{font-family:' + line + '}}\n';
}
}