diff options
Diffstat (limited to 'node_modules/stylus/lib/visitor')
| -rw-r--r-- | node_modules/stylus/lib/visitor/compiler.js | 567 | ||||
| -rw-r--r-- | node_modules/stylus/lib/visitor/deps-resolver.js | 169 | ||||
| -rw-r--r-- | node_modules/stylus/lib/visitor/evaluator.js | 1610 | ||||
| -rw-r--r-- | node_modules/stylus/lib/visitor/index.js | 31 | ||||
| -rw-r--r-- | node_modules/stylus/lib/visitor/normalizer.js | 424 | ||||
| -rw-r--r-- | node_modules/stylus/lib/visitor/sourcemapper.js | 203 |
6 files changed, 3004 insertions, 0 deletions
diff --git a/node_modules/stylus/lib/visitor/compiler.js b/node_modules/stylus/lib/visitor/compiler.js new file mode 100644 index 00000000..47bb9ae6 --- /dev/null +++ b/node_modules/stylus/lib/visitor/compiler.js @@ -0,0 +1,567 @@ +/*! + * 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; + + if (block.hasProperties && !block.lacksRenderedSelectors) { + needBrackets = this.needBrackets(block.node); + + if (needBrackets) { + this.buf += this.out(this.compress ? '{' : ' {\n'); + ++this.indents; + } + for (var i = 0, len = block.nodes.length; i < len; ++i) { + this.last = len - 1 == 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.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 or time + if ('%' != type && 's' != type && 'ms' != 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 = []; + 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'; + } +} diff --git a/node_modules/stylus/lib/visitor/deps-resolver.js b/node_modules/stylus/lib/visitor/deps-resolver.js new file mode 100644 index 00000000..b5597cad --- /dev/null +++ b/node_modules/stylus/lib/visitor/deps-resolver.js @@ -0,0 +1,169 @@ + +/** + * Module dependencies. + */ + +var Visitor = require('./') + , Parser = require('../parser') + , nodes = require('../nodes') + , utils = require('../utils') + , dirname = require('path').dirname + , fs = require('fs'); + +/** + * Initialize a new `DepsResolver` with the given `root` Node + * and the `options`. + * + * @param {Node} root + * @param {Object} options + * @api private + */ + +var DepsResolver = module.exports = function DepsResolver(root, options) { + this.root = root; + this.filename = options.filename; + this.paths = options.paths || []; + this.paths.push(dirname(options.filename || '.')); + this.options = options; + this.functions = {}; + this.deps = []; +}; + +/** + * Inherit from `Visitor.prototype`. + */ + +DepsResolver.prototype.__proto__ = Visitor.prototype; + +var visit = DepsResolver.prototype.visit; + +DepsResolver.prototype.visit = function(node) { + switch (node.nodeName) { + case 'root': + case 'block': + case 'expression': + this.visitRoot(node); + break; + case 'group': + case 'media': + case 'atblock': + case 'atrule': + case 'keyframes': + case 'each': + case 'supports': + this.visit(node.block); + break; + default: + visit.call(this, node); + } +}; + +/** + * Visit Root. + */ + +DepsResolver.prototype.visitRoot = function(block) { + for (var i = 0, len = block.nodes.length; i < len; ++i) { + this.visit(block.nodes[i]); + } +}; + +/** + * Visit Ident. + */ + +DepsResolver.prototype.visitIdent = function(ident) { + this.visit(ident.val); +}; + +/** + * Visit If. + */ + +DepsResolver.prototype.visitIf = function(node) { + this.visit(node.block); + this.visit(node.cond); + for (var i = 0, len = node.elses.length; i < len; ++i) { + this.visit(node.elses[i]); + } +}; + +/** + * Visit Function. + */ + +DepsResolver.prototype.visitFunction = function(fn) { + this.functions[fn.name] = fn.block; +}; + +/** + * Visit Call. + */ + +DepsResolver.prototype.visitCall = function(call) { + if (call.name in this.functions) this.visit(this.functions[call.name]); + if (call.block) this.visit(call.block); +}; + +/** + * Visit Import. + */ + +DepsResolver.prototype.visitImport = function(node) { + var path = node.path.first.val + , literal, found, oldPath; + + if (!path) return; + + literal = /\.css(?:"|$)/.test(path); + + // support optional .styl + if (!literal && !/\.styl$/i.test(path)) { + oldPath = path; + path += '.styl'; + } + + // Lookup + found = utils.find(path, this.paths, this.filename); + + // support optional index + if (!found && oldPath) found = utils.lookupIndex(oldPath, this.paths, this.filename); + + if (!found) return; + + this.deps = this.deps.concat(found); + + if (literal) return; + + // nested imports + for (var i = 0, len = found.length; i < len; ++i) { + var file = found[i] + , dir = dirname(file) + , str = fs.readFileSync(file, 'utf-8') + , block = new nodes.Block + , parser = new Parser(str, utils.merge({ root: block }, this.options)); + + if (!~this.paths.indexOf(dir)) this.paths.push(dir); + + try { + block = parser.parse(); + } catch (err) { + err.filename = file; + err.lineno = parser.lexer.lineno; + err.column = parser.lexer.column; + err.input = str; + throw err; + } + + this.visit(block); + } +}; + +/** + * Get dependencies. + */ + +DepsResolver.prototype.resolve = function() { + this.visit(this.root); + return utils.uniq(this.deps); +}; diff --git a/node_modules/stylus/lib/visitor/evaluator.js b/node_modules/stylus/lib/visitor/evaluator.js new file mode 100644 index 00000000..b5ddc636 --- /dev/null +++ b/node_modules/stylus/lib/visitor/evaluator.js @@ -0,0 +1,1610 @@ + +/*! + * Stylus - Evaluator + * Copyright (c) Automattic <developer.wordpress.com> + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Visitor = require('./') + , units = require('../units') + , nodes = require('../nodes') + , Stack = require('../stack') + , Frame = require('../stack/frame') + , utils = require('../utils') + , bifs = require('../functions') + , dirname = require('path').dirname + , colors = require('../colors') + , debug = require('debug')('stylus:evaluator') + , fs = require('fs'); + +/** + * Import `file` and return Block node. + * + * @api private + */ +function importFile(node, file, literal) { + var importStack = this.importStack + , Parser = require('../parser') + , stat; + + // Handling the `require` + if (node.once) { + if (this.requireHistory[file]) return nodes.null; + this.requireHistory[file] = true; + + if (literal && !this.includeCSS) { + return node; + } + } + + // Avoid overflows from importing the same file over again + if (~importStack.indexOf(file)) + throw new Error('import loop has been found'); + + var str = fs.readFileSync(file, 'utf8'); + + // shortcut for empty files + if (!str.trim()) return nodes.null; + + // Expose imports + node.path = file; + node.dirname = dirname(file); + // Store the modified time + stat = fs.statSync(file); + node.mtime = stat.mtime; + this.paths.push(node.dirname); + + if (this.options._imports) this.options._imports.push(node.clone()); + + // Parse the file + importStack.push(file); + nodes.filename = file; + + if (literal) { + literal = new nodes.Literal(str.replace(/\r\n?/g, '\n')); + literal.lineno = literal.column = 1; + if (!this.resolveURL) return literal; + } + + // parse + var block = new nodes.Block + , parser = new Parser(str, utils.merge({ root: block }, this.options)); + + try { + block = parser.parse(); + } catch (err) { + var line = parser.lexer.lineno + , column = parser.lexer.column; + + if (literal && this.includeCSS && this.resolveURL) { + this.warn('ParseError: ' + file + ':' + line + ':' + column + '. This file included as-is'); + return literal; + } else { + err.filename = file; + err.lineno = line; + err.column = column; + err.input = str; + throw err; + } + } + + // Evaluate imported "root" + block = block.clone(this.currentBlock); + block.parent = this.currentBlock; + block.scope = false; + var ret = this.visit(block); + importStack.pop(); + if (!this.resolveURL || this.resolveURL.nocheck) this.paths.pop(); + + return ret; +} + +/** + * Initialize a new `Evaluator` with the given `root` Node + * and the following `options`. + * + * Options: + * + * - `compress` Compress the css output, defaults to false + * - `warn` Warn the user of duplicate function definitions etc + * + * @param {Node} root + * @api private + */ + +var Evaluator = module.exports = function Evaluator(root, options) { + options = options || {}; + Visitor.call(this, root); + var functions = this.functions = options.functions || {}; + this.stack = new Stack; + this.imports = options.imports || []; + this.globals = options.globals || {}; + this.paths = options.paths || []; + this.prefix = options.prefix || ''; + this.filename = options.filename; + this.includeCSS = options['include css']; + this.resolveURL = functions.url + && 'resolver' == functions.url.name + && functions.url.options; + this.paths.push(dirname(options.filename || '.')); + this.stack.push(this.global = new Frame(root)); + this.warnings = options.warn; + this.options = options; + this.calling = []; // TODO: remove, use stack + this.importStack = []; + this.requireHistory = {}; + this.return = 0; +}; + +/** + * Inherit from `Visitor.prototype`. + */ + +Evaluator.prototype.__proto__ = Visitor.prototype; + +/** + * Proxy visit to expose node line numbers. + * + * @param {Node} node + * @return {Node} + * @api private + */ + +var visit = Visitor.prototype.visit; +Evaluator.prototype.visit = function(node){ + try { + return visit.call(this, node); + } catch (err) { + if (err.filename) throw err; + err.lineno = node.lineno; + err.column = node.column; + err.filename = node.filename; + err.stylusStack = this.stack.toString(); + try { + err.input = fs.readFileSync(err.filename, 'utf8'); + } catch (err) { + // ignore + } + throw err; + } +}; + +/** + * Perform evaluation setup: + * + * - populate global scope + * - iterate imports + * + * @api private + */ + +Evaluator.prototype.setup = function(){ + var root = this.root; + var imports = []; + + this.populateGlobalScope(); + this.imports.forEach(function(file){ + var expr = new nodes.Expression; + expr.push(new nodes.String(file)); + imports.push(new nodes.Import(expr)); + }, this); + + root.nodes = imports.concat(root.nodes); +}; + +/** + * Populate the global scope with: + * + * - css colors + * - user-defined globals + * + * @api private + */ + +Evaluator.prototype.populateGlobalScope = function(){ + var scope = this.global.scope; + + // colors + Object.keys(colors).forEach(function(name){ + var color = colors[name] + , rgba = new nodes.RGBA(color[0], color[1], color[2], color[3]) + , node = new nodes.Ident(name, rgba); + rgba.name = name; + scope.add(node); + }); + + // expose url function + scope.add(new nodes.Ident( + 'embedurl', + new nodes.Function('embedurl', require('../functions/url')({ + limit: false + })) + )); + + // user-defined globals + var globals = this.globals; + Object.keys(globals).forEach(function(name){ + var val = globals[name]; + if (!val.nodeName) val = new nodes.Literal(val); + scope.add(new nodes.Ident(name, val)); + }); +}; + +/** + * Evaluate the tree. + * + * @return {Node} + * @api private + */ + +Evaluator.prototype.evaluate = function(){ + debug('eval %s', this.filename); + this.setup(); + return this.visit(this.root); +}; + +/** + * Visit Group. + */ + +Evaluator.prototype.visitGroup = function(group){ + group.nodes = group.nodes.map(function(selector){ + selector.val = this.interpolate(selector); + debug('ruleset %s', selector.val); + return selector; + }, this); + + group.block = this.visit(group.block); + return group; +}; + +/** + * Visit Return. + */ + +Evaluator.prototype.visitReturn = function(ret){ + ret.expr = this.visit(ret.expr); + throw ret; +}; + +/** + * Visit Media. + */ + +Evaluator.prototype.visitMedia = function(media){ + media.block = this.visit(media.block); + media.val = this.visit(media.val); + return media; +}; + +/** + * Visit QueryList. + */ + +Evaluator.prototype.visitQueryList = function(queries){ + var val, query; + queries.nodes.forEach(this.visit, this); + + if (1 == queries.nodes.length) { + query = queries.nodes[0]; + if (val = this.lookup(query.type)) { + val = val.first.string; + if (!val) return queries; + var Parser = require('../parser') + , parser = new Parser(val, this.options); + queries = this.visit(parser.queries()); + } + } + return queries; +}; + +/** + * Visit Query. + */ + +Evaluator.prototype.visitQuery = function(node){ + node.predicate = this.visit(node.predicate); + node.type = this.visit(node.type); + node.nodes.forEach(this.visit, this); + return node; +}; + +/** + * Visit Feature. + */ + +Evaluator.prototype.visitFeature = function(node){ + node.name = this.interpolate(node); + if (node.expr) { + this.return++; + node.expr = this.visit(node.expr); + this.return--; + } + return node; +}; + +/** + * Visit Object. + */ + +Evaluator.prototype.visitObject = function(obj){ + for (var key in obj.vals) { + obj.vals[key] = this.visit(obj.vals[key]); + } + return obj; +}; + +/** + * Visit Member. + */ + +Evaluator.prototype.visitMember = function(node){ + var left = node.left + , right = node.right + , obj = this.visit(left).first; + + if ('object' != obj.nodeName) { + throw new Error(left.toString() + ' has no property .' + right); + } + if (node.val) { + this.return++; + obj.set(right.name, this.visit(node.val)); + this.return--; + } + return obj.get(right.name); +}; + +/** + * Visit Keyframes. + */ + +Evaluator.prototype.visitKeyframes = function(keyframes){ + var val; + if (keyframes.fabricated) return keyframes; + keyframes.val = this.interpolate(keyframes).trim(); + if (val = this.lookup(keyframes.val)) { + keyframes.val = val.first.string || val.first.name; + } + keyframes.block = this.visit(keyframes.block); + + if ('official' != keyframes.prefix) return keyframes; + + this.vendors.forEach(function(prefix){ + // IE never had prefixes for keyframes + if ('ms' == prefix) return; + var node = keyframes.clone(); + node.val = keyframes.val; + node.prefix = prefix; + node.block = keyframes.block; + node.fabricated = true; + this.currentBlock.push(node); + }, this); + + return nodes.null; +}; + +/** + * Visit Function. + */ + +Evaluator.prototype.visitFunction = function(fn){ + // check local + var local = this.stack.currentFrame.scope.lookup(fn.name); + if (local) this.warn('local ' + local.nodeName + ' "' + fn.name + '" previously defined in this scope'); + + // user-defined + var user = this.functions[fn.name]; + if (user) this.warn('user-defined function "' + fn.name + '" is already defined'); + + // BIF + var bif = bifs[fn.name]; + if (bif) this.warn('built-in function "' + fn.name + '" is already defined'); + + return fn; +}; + +/** + * Visit Each. + */ + +Evaluator.prototype.visitEach = function(each){ + this.return++; + var expr = utils.unwrap(this.visit(each.expr)) + , len = expr.nodes.length + , val = new nodes.Ident(each.val) + , key = new nodes.Ident(each.key || '__index__') + , scope = this.currentScope + , block = this.currentBlock + , vals = [] + , self = this + , body + , obj; + this.return--; + + each.block.scope = false; + + function visitBody(key, val) { + scope.add(val); + scope.add(key); + body = self.visit(each.block.clone()); + vals = vals.concat(body.nodes); + } + + // for prop in obj + if (1 == len && 'object' == expr.nodes[0].nodeName) { + obj = expr.nodes[0]; + for (var prop in obj.vals) { + val.val = new nodes.String(prop); + key.val = obj.get(prop); + visitBody(key, val); + } + } else { + for (var i = 0; i < len; ++i) { + val.val = expr.nodes[i]; + key.val = new nodes.Unit(i); + visitBody(key, val); + } + } + + this.mixin(vals, block); + return vals[vals.length - 1] || nodes.null; +}; + +/** + * Visit Call. + */ + +Evaluator.prototype.visitCall = function(call){ + debug('call %s', call); + var fn = this.lookup(call.name) + , literal + , ret; + + // url() + this.ignoreColors = 'url' == call.name; + + // Variable function + if (fn && 'expression' == fn.nodeName) { + fn = fn.nodes[0]; + } + + // Not a function? try user-defined or built-ins + if (fn && 'function' != fn.nodeName) { + fn = this.lookupFunction(call.name); + } + + // Undefined function? render literal CSS + if (!fn || fn.nodeName != 'function') { + debug('%s is undefined', call); + // Special case for `calc` + if ('calc' == this.unvendorize(call.name)) { + literal = call.args.nodes && call.args.nodes[0]; + if (literal) ret = new nodes.Literal(call.name + literal); + } else { + ret = this.literalCall(call); + } + this.ignoreColors = false; + return ret; + } + + this.calling.push(call.name); + + // Massive stack + if (this.calling.length > 200) { + throw new RangeError('Maximum stylus call stack size exceeded'); + } + + // First node in expression + if ('expression' == fn.nodeName) fn = fn.first; + + // Evaluate arguments + this.return++; + var args = this.visit(call.args); + + for (var key in args.map) { + args.map[key] = this.visit(args.map[key].clone()); + } + this.return--; + + // Built-in + if (fn.fn) { + debug('%s is built-in', call); + ret = this.invokeBuiltin(fn.fn, args); + // User-defined + } else if ('function' == fn.nodeName) { + debug('%s is user-defined', call); + // Evaluate mixin block + if (call.block) call.block = this.visit(call.block); + ret = this.invokeFunction(fn, args, call.block); + } + + this.calling.pop(); + this.ignoreColors = false; + return ret; +}; + +/** + * Visit Ident. + */ + +Evaluator.prototype.visitIdent = function(ident){ + var prop; + // Property lookup + if (ident.property) { + if (prop = this.lookupProperty(ident.name)) { + return this.visit(prop.expr.clone()); + } + return nodes.null; + // Lookup + } else if (ident.val.isNull) { + var val = this.lookup(ident.name); + // Object or Block mixin + if (val && ident.mixin) this.mixinNode(val); + return val ? this.visit(val) : ident; + // Assign + } else { + this.return++; + ident.val = this.visit(ident.val); + this.return--; + this.currentScope.add(ident); + return ident.val; + } +}; + +/** + * Visit BinOp. + */ + +Evaluator.prototype.visitBinOp = function(binop){ + // Special-case "is defined" pseudo binop + if ('is defined' == binop.op) return this.isDefined(binop.left); + + this.return++; + // Visit operands + var op = binop.op + , left = this.visit(binop.left) + , right = ('||' == op || '&&' == op) + ? binop.right : this.visit(binop.right); + + // HACK: ternary + var val = binop.val + ? this.visit(binop.val) + : null; + this.return--; + + // Operate + try { + return this.visit(left.operate(op, right, val)); + } catch (err) { + // disregard coercion issues in equality + // checks, and simply return false + if ('CoercionError' == err.name) { + switch (op) { + case '==': + return nodes.false; + case '!=': + return nodes.true; + } + } + throw err; + } +}; + +/** + * Visit UnaryOp. + */ + +Evaluator.prototype.visitUnaryOp = function(unary){ + var op = unary.op + , node = this.visit(unary.expr); + + if ('!' != op) { + node = node.first.clone(); + utils.assertType(node, 'unit'); + } + + switch (op) { + case '-': + node.val = -node.val; + break; + case '+': + node.val = +node.val; + break; + case '~': + node.val = ~node.val; + break; + case '!': + return node.toBoolean().negate(); + } + + return node; +}; + +/** + * Visit TernaryOp. + */ + +Evaluator.prototype.visitTernary = function(ternary){ + var ok = this.visit(ternary.cond).toBoolean(); + return ok.isTrue + ? this.visit(ternary.trueExpr) + : this.visit(ternary.falseExpr); +}; + +/** + * Visit Expression. + */ + +Evaluator.prototype.visitExpression = function(expr){ + for (var i = 0, len = expr.nodes.length; i < len; ++i) { + expr.nodes[i] = this.visit(expr.nodes[i]); + } + + // support (n * 5)px etc + if (this.castable(expr)) expr = this.cast(expr); + + return expr; +}; + +/** + * Visit Arguments. + */ + +Evaluator.prototype.visitArguments = Evaluator.prototype.visitExpression; + +/** + * Visit Property. + */ + +Evaluator.prototype.visitProperty = function(prop){ + var name = this.interpolate(prop) + , fn = this.lookup(name) + , call = fn && 'function' == fn.first.nodeName + , literal = ~this.calling.indexOf(name) + , _prop = this.property; + + // Function of the same name + if (call && !literal && !prop.literal) { + var args = nodes.Arguments.fromExpression(utils.unwrap(prop.expr.clone())); + prop.name = name; + this.property = prop; + this.return++; + this.property.expr = this.visit(prop.expr); + this.return--; + var ret = this.visit(new nodes.Call(name, args)); + this.property = _prop; + return ret; + // Regular property + } else { + this.return++; + prop.name = name; + prop.literal = true; + this.property = prop; + prop.expr = this.visit(prop.expr); + this.property = _prop; + this.return--; + return prop; + } +}; + +/** + * Visit Root. + */ + +Evaluator.prototype.visitRoot = function(block){ + // normalize cached imports + if (block != this.root) { + block.constructor = nodes.Block; + return this.visit(block); + } + + for (var i = 0; i < block.nodes.length; ++i) { + block.index = i; + block.nodes[i] = this.visit(block.nodes[i]); + } + return block; +}; + +/** + * Visit Block. + */ + +Evaluator.prototype.visitBlock = function(block){ + this.stack.push(new Frame(block)); + for (block.index = 0; block.index < block.nodes.length; ++block.index) { + try { + block.nodes[block.index] = this.visit(block.nodes[block.index]); + } catch (err) { + if ('return' == err.nodeName) { + if (this.return) { + this.stack.pop(); + throw err; + } else { + block.nodes[block.index] = err; + break; + } + } else { + throw err; + } + } + } + this.stack.pop(); + return block; +}; + +/** + * Visit Atblock. + */ + +Evaluator.prototype.visitAtblock = function(atblock){ + atblock.block = this.visit(atblock.block); + return atblock; +}; + +/** + * Visit Atrule. + */ + +Evaluator.prototype.visitAtrule = function(atrule){ + atrule.val = this.interpolate(atrule); + if (atrule.block) atrule.block = this.visit(atrule.block); + return atrule; +}; + +/** + * Visit Supports. + */ + +Evaluator.prototype.visitSupports = function(node){ + var condition = node.condition + , val; + + this.return++; + node.condition = this.visit(condition); + this.return--; + + val = condition.first; + if (1 == condition.nodes.length + && 'string' == val.nodeName) { + node.condition = val.string; + } + node.block = this.visit(node.block); + return node; +}; + +/** + * Visit If. + */ + +Evaluator.prototype.visitIf = function(node){ + var ret + , block = this.currentBlock + , negate = node.negate; + + this.return++; + var ok = this.visit(node.cond).first.toBoolean(); + this.return--; + + node.block.scope = node.block.hasMedia; + + // Evaluate body + if (negate) { + // unless + if (ok.isFalse) { + ret = this.visit(node.block); + } + } else { + // if + if (ok.isTrue) { + ret = this.visit(node.block); + // else + } else if (node.elses.length) { + var elses = node.elses + , len = elses.length + , cond; + for (var i = 0; i < len; ++i) { + // else if + if (elses[i].cond) { + elses[i].block.scope = elses[i].block.hasMedia; + this.return++; + cond = this.visit(elses[i].cond).first.toBoolean(); + this.return--; + if (cond.isTrue) { + ret = this.visit(elses[i].block); + break; + } + // else + } else { + elses[i].scope = elses[i].hasMedia; + ret = this.visit(elses[i]); + } + } + } + } + + // mixin conditional statements within + // a selector group or at-rule + if (ret && !node.postfix && block.node + && ~['group' + , 'atrule' + , 'media' + , 'supports' + , 'keyframes'].indexOf(block.node.nodeName)) { + this.mixin(ret.nodes, block); + return nodes.null; + } + + return ret || nodes.null; +}; + +/** + * Visit Extend. + */ + +Evaluator.prototype.visitExtend = function(extend){ + var block = this.currentBlock; + if ('group' != block.node.nodeName) block = this.closestGroup; + extend.selectors.forEach(function(selector){ + block.node.extends.push({ + // Cloning the selector for when we are in a loop and don't want it to affect + // the selector nodes and cause the values to be different to expected + selector: this.interpolate(selector.clone()).trim(), + optional: selector.optional, + lineno: selector.lineno, + column: selector.column + }); + }, this); + return nodes.null; +}; + +/** + * Visit Import. + */ + +Evaluator.prototype.visitImport = function(imported){ + this.return++; + + var path = this.visit(imported.path).first + , nodeName = imported.once ? 'require' : 'import' + , found + , literal; + + this.return--; + debug('import %s', path); + + // url() passed + if ('url' == path.name) { + if (imported.once) throw new Error('You cannot @require a url'); + + return imported; + } + + // Ensure string + if (!path.string) throw new Error('@' + nodeName + ' string expected'); + + var name = path = path.string; + + // Absolute URL or hash + if (/(?:url\s*\(\s*)?['"]?(?:#|(?:https?:)?\/\/)/i.test(path)) { + if (imported.once) throw new Error('You cannot @require a url'); + return imported; + } + + // Literal + if (/\.css(?:"|$)/.test(path)) { + literal = true; + if (!imported.once && !this.includeCSS) { + return imported; + } + } + + // support optional .styl + if (!literal && !/\.styl$/i.test(path)) path += '.styl'; + + // Lookup + found = utils.find(path, this.paths, this.filename); + if (!found) { + found = utils.lookupIndex(name, this.paths, this.filename); + } + + // Throw if import failed + if (!found) throw new Error('failed to locate @' + nodeName + ' file ' + path); + + var block = new nodes.Block; + + for (var i = 0, len = found.length; i < len; ++i) { + block.push(importFile.call(this, imported, found[i], literal)); + } + + return block; +}; + +/** + * Invoke `fn` with `args`. + * + * @param {Function} fn + * @param {Array} args + * @return {Node} + * @api private + */ + +Evaluator.prototype.invokeFunction = function(fn, args, content){ + var block = new nodes.Block(fn.block.parent); + + // Clone the function body + // to prevent mutation of subsequent calls + var body = fn.block.clone(block); + + // mixin block + var mixinBlock = this.stack.currentFrame.block; + + // new block scope + this.stack.push(new Frame(block)); + var scope = this.currentScope; + + // normalize arguments + if ('arguments' != args.nodeName) { + var expr = new nodes.Expression; + expr.push(args); + args = nodes.Arguments.fromExpression(expr); + } + + // arguments local + scope.add(new nodes.Ident('arguments', args)); + + // mixin scope introspection + scope.add(new nodes.Ident('mixin', this.return + ? nodes.false + : new nodes.String(mixinBlock.nodeName))); + + // current property + if (this.property) { + var prop = this.propertyExpression(this.property, fn.name); + scope.add(new nodes.Ident('current-property', prop)); + } else { + scope.add(new nodes.Ident('current-property', nodes.null)); + } + + // current call stack + var expr = new nodes.Expression; + for (var i = this.calling.length - 1; i-- ; ) { + expr.push(new nodes.Literal(this.calling[i])); + }; + scope.add(new nodes.Ident('called-from', expr)); + + // inject arguments as locals + var i = 0 + , len = args.nodes.length; + fn.params.nodes.forEach(function(node){ + // rest param support + if (node.rest) { + node.val = new nodes.Expression; + for (; i < len; ++i) node.val.push(args.nodes[i]); + node.val.preserve = true; + node.val.isList = args.isList; + // argument default support + } else { + var arg = args.map[node.name] || args.nodes[i++]; + node = node.clone(); + if (arg) { + arg.isEmpty ? args.nodes[i - 1] = this.visit(node) : node.val = arg; + } else { + args.push(node.val); + } + + // required argument not satisfied + if (node.val.isNull) { + throw new Error('argument "' + node + '" required for ' + fn); + } + } + + scope.add(node); + }, this); + + // mixin block + if (content) scope.add(new nodes.Ident('block', content, true)); + + // invoke + return this.invoke(body, true, fn.filename); +}; + +/** + * Invoke built-in `fn` with `args`. + * + * @param {Function} fn + * @param {Array} args + * @return {Node} + * @api private + */ + +Evaluator.prototype.invokeBuiltin = function(fn, args){ + // Map arguments to first node + // providing a nicer js api for + // BIFs. Functions may specify that + // they wish to accept full expressions + // via .raw + if (fn.raw) { + args = args.nodes; + } else { + args = utils.params(fn).reduce(function(ret, param){ + var arg = args.map[param] || args.nodes.shift() + if (arg) { + arg = utils.unwrap(arg); + var len = arg.nodes.length; + if (len > 1) { + for (var i = 0; i < len; ++i) { + ret.push(utils.unwrap(arg.nodes[i].first)); + } + } else { + ret.push(arg.first); + } + } + return ret; + }, []); + } + + // Invoke the BIF + var body = utils.coerce(fn.apply(this, args)); + + // Always wrapping allows js functions + // to return several values with a single + // Expression node + var expr = new nodes.Expression; + expr.push(body); + body = expr; + + // Invoke + return this.invoke(body); +}; + +/** + * Invoke the given function `body`. + * + * @param {Block} body + * @return {Node} + * @api private + */ + +Evaluator.prototype.invoke = function(body, stack, filename){ + var self = this + , ret; + + if (filename) this.paths.push(dirname(filename)); + + // Return + if (this.return) { + ret = this.eval(body.nodes); + if (stack) this.stack.pop(); + // Mixin + } else { + body = this.visit(body); + if (stack) this.stack.pop(); + this.mixin(body.nodes, this.currentBlock); + ret = nodes.null; + } + + if (filename) this.paths.pop(); + + return ret; +}; + +/** + * Mixin the given `nodes` to the given `block`. + * + * @param {Array} nodes + * @param {Block} block + * @api private + */ + +Evaluator.prototype.mixin = function(nodes, block){ + if (!nodes.length) return; + var len = block.nodes.length + , head = block.nodes.slice(0, block.index) + , tail = block.nodes.slice(block.index + 1, len); + this._mixin(nodes, head, block); + block.index = 0; + block.nodes = head.concat(tail); +}; + +/** + * Mixin the given `items` to the `dest` array. + * + * @param {Array} items + * @param {Array} dest + * @param {Block} block + * @api private + */ + +Evaluator.prototype._mixin = function(items, dest, block){ + var node + , len = items.length; + for (var i = 0; i < len; ++i) { + switch ((node = items[i]).nodeName) { + case 'return': + return; + case 'block': + this._mixin(node.nodes, dest, block); + break; + case 'media': + // fix link to the parent block + var parentNode = node.block.parent.node; + if (parentNode && 'call' != parentNode.nodeName) { + node.block.parent = block; + } + case 'property': + var val = node.expr; + // prevent `block` mixin recursion + if (node.literal && 'block' == val.first.name) { + val = utils.unwrap(val); + val.nodes[0] = new nodes.Literal('block'); + } + default: + dest.push(node); + } + } +}; + +/** + * Mixin the given `node` to the current block. + * + * @param {Node} node + * @api private + */ + +Evaluator.prototype.mixinNode = function(node){ + node = this.visit(node.first); + switch (node.nodeName) { + case 'object': + this.mixinObject(node); + return nodes.null; + case 'block': + case 'atblock': + this.mixin(node.nodes, this.currentBlock); + return nodes.null; + } +}; + +/** + * Mixin the given `object` to the current block. + * + * @param {Object} object + * @api private + */ + +Evaluator.prototype.mixinObject = function(object){ + var Parser = require('../parser') + , root = this.root + , str = '$block ' + object.toBlock() + , parser = new Parser(str, utils.merge({ root: block }, this.options)) + , block; + + try { + block = parser.parse(); + } catch (err) { + err.filename = this.filename; + err.lineno = parser.lexer.lineno; + err.column = parser.lexer.column; + err.input = str; + throw err; + } + + block.parent = root; + block.scope = false; + var ret = this.visit(block) + , vals = ret.first.nodes; + for (var i = 0, len = vals.length; i < len; ++i) { + if (vals[i].block) { + this.mixin(vals[i].block.nodes, this.currentBlock); + break; + } + } +}; + +/** + * Evaluate the given `vals`. + * + * @param {Array} vals + * @return {Node} + * @api private + */ + +Evaluator.prototype.eval = function(vals){ + if (!vals) return nodes.null; + var len = vals.length + , node = nodes.null; + + try { + for (var i = 0; i < len; ++i) { + node = vals[i]; + switch (node.nodeName) { + case 'if': + if ('block' != node.block.nodeName) { + node = this.visit(node); + break; + } + case 'each': + case 'block': + node = this.visit(node); + if (node.nodes) node = this.eval(node.nodes); + break; + default: + node = this.visit(node); + } + } + } catch (err) { + if ('return' == err.nodeName) { + return err.expr; + } else { + throw err; + } + } + + return node; +}; + +/** + * Literal function `call`. + * + * @param {Call} call + * @return {call} + * @api private + */ + +Evaluator.prototype.literalCall = function(call){ + call.args = this.visit(call.args); + return call; +}; + +/** + * Lookup property `name`. + * + * @param {String} name + * @return {Property} + * @api private + */ + +Evaluator.prototype.lookupProperty = function(name){ + var i = this.stack.length + , index = this.currentBlock.index + , top = i + , nodes + , block + , len + , other; + + while (i--) { + block = this.stack[i].block; + if (!block.node) continue; + switch (block.node.nodeName) { + case 'group': + case 'function': + case 'if': + case 'each': + case 'atrule': + case 'media': + case 'atblock': + case 'call': + nodes = block.nodes; + // scan siblings from the property index up + if (i + 1 == top) { + while (index--) { + // ignore current property + if (this.property == nodes[index]) continue; + other = this.interpolate(nodes[index]); + if (name == other) return nodes[index].clone(); + } + // sequential lookup for non-siblings (for now) + } else { + len = nodes.length; + while (len--) { + if ('property' != nodes[len].nodeName + || this.property == nodes[len]) continue; + other = this.interpolate(nodes[len]); + if (name == other) return nodes[len].clone(); + } + } + break; + } + } + + return nodes.null; +}; + +/** + * Return the closest mixin-able `Block`. + * + * @return {Block} + * @api private + */ + +Evaluator.prototype.__defineGetter__('closestBlock', function(){ + var i = this.stack.length + , block; + while (i--) { + block = this.stack[i].block; + if (block.node) { + switch (block.node.nodeName) { + case 'group': + case 'keyframes': + case 'atrule': + case 'atblock': + case 'media': + case 'call': + return block; + } + } + } +}); + +/** + * Return the closest group block. + * + * @return {Block} + * @api private + */ + +Evaluator.prototype.__defineGetter__('closestGroup', function(){ + var i = this.stack.length + , block; + while (i--) { + block = this.stack[i].block; + if (block.node && 'group' == block.node.nodeName) { + return block; + } + } +}); + +/** + * Return the current selectors stack. + * + * @return {Array} + * @api private + */ + +Evaluator.prototype.__defineGetter__('selectorStack', function(){ + var block + , stack = []; + for (var i = 0, len = this.stack.length; i < len; ++i) { + block = this.stack[i].block; + if (block.node && 'group' == block.node.nodeName) { + block.node.nodes.forEach(function(selector) { + if (!selector.val) selector.val = this.interpolate(selector); + }, this); + stack.push(block.node.nodes); + } + } + return stack; +}); + +/** + * Lookup `name`, with support for JavaScript + * functions, and BIFs. + * + * @param {String} name + * @return {Node} + * @api private + */ + +Evaluator.prototype.lookup = function(name){ + var val; + if (this.ignoreColors && name in colors) return; + if (val = this.stack.lookup(name)) { + return utils.unwrap(val); + } else { + return this.lookupFunction(name); + } +}; + +/** + * Map segments in `node` returning a string. + * + * @param {Node} node + * @return {String} + * @api private + */ + +Evaluator.prototype.interpolate = function(node){ + var self = this + , isSelector = ('selector' == node.nodeName); + function toString(node) { + switch (node.nodeName) { + case 'function': + case 'ident': + return node.name; + case 'literal': + case 'string': + if (self.prefix && !node.prefixed && !node.val.nodeName) { + node.val = node.val.replace(/\./g, '.' + self.prefix); + node.prefixed = true; + } + return node.val; + case 'unit': + // Interpolation inside keyframes + return '%' == node.type ? node.val + '%' : node.val; + case 'member': + return toString(self.visit(node)); + case 'expression': + // Prevent cyclic `selector()` calls. + if (self.calling && ~self.calling.indexOf('selector') && self._selector) return self._selector; + self.return++; + var ret = toString(self.visit(node).first); + self.return--; + if (isSelector) self._selector = ret; + return ret; + } + } + + if (node.segments) { + return node.segments.map(toString).join(''); + } else { + return toString(node); + } +}; + +/** + * Lookup JavaScript user-defined or built-in function. + * + * @param {String} name + * @return {Function} + * @api private + */ + +Evaluator.prototype.lookupFunction = function(name){ + var fn = this.functions[name] || bifs[name]; + if (fn) return new nodes.Function(name, fn); +}; + +/** + * Check if the given `node` is an ident, and if it is defined. + * + * @param {Node} node + * @return {Boolean} + * @api private + */ + +Evaluator.prototype.isDefined = function(node){ + if ('ident' == node.nodeName) { + return nodes.Boolean(this.lookup(node.name)); + } else { + throw new Error('invalid "is defined" check on non-variable ' + node); + } +}; + +/** + * Return `Expression` based on the given `prop`, + * replacing cyclic calls to the given function `name` + * with "__CALL__". + * + * @param {Property} prop + * @param {String} name + * @return {Expression} + * @api private + */ + +Evaluator.prototype.propertyExpression = function(prop, name){ + var expr = new nodes.Expression + , val = prop.expr.clone(); + + // name + expr.push(new nodes.String(prop.name)); + + // replace cyclic call with __CALL__ + function replace(node) { + if ('call' == node.nodeName && name == node.name) { + return new nodes.Literal('__CALL__'); + } + + if (node.nodes) node.nodes = node.nodes.map(replace); + return node; + } + + replace(val); + expr.push(val); + return expr; +}; + +/** + * Cast `expr` to the trailing ident. + * + * @param {Expression} expr + * @return {Unit} + * @api private + */ + +Evaluator.prototype.cast = function(expr){ + return new nodes.Unit(expr.first.val, expr.nodes[1].name); +}; + +/** + * Check if `expr` is castable. + * + * @param {Expression} expr + * @return {Boolean} + * @api private + */ + +Evaluator.prototype.castable = function(expr){ + return 2 == expr.nodes.length + && 'unit' == expr.first.nodeName + && ~units.indexOf(expr.nodes[1].name); +}; + +/** + * Warn with the given `msg`. + * + * @param {String} msg + * @api private + */ + +Evaluator.prototype.warn = function(msg){ + if (!this.warnings) return; + console.warn('\u001b[33mWarning:\u001b[0m ' + msg); +}; + +/** + * Return the current `Block`. + * + * @return {Block} + * @api private + */ + +Evaluator.prototype.__defineGetter__('currentBlock', function(){ + return this.stack.currentFrame.block; +}); + +/** + * Return an array of vendor names. + * + * @return {Array} + * @api private + */ + +Evaluator.prototype.__defineGetter__('vendors', function(){ + return this.lookup('vendors').nodes.map(function(node){ + return node.string; + }); +}); + +/** + * Return the property name without vendor prefix. + * + * @param {String} prop + * @return {String} + * @api public + */ + +Evaluator.prototype.unvendorize = function(prop){ + for (var i = 0, len = this.vendors.length; i < len; i++) { + if ('official' != this.vendors[i]) { + var vendor = '-' + this.vendors[i] + '-'; + if (~prop.indexOf(vendor)) return prop.replace(vendor, ''); + } + } + return prop; +}; + +/** + * Return the current frame `Scope`. + * + * @return {Scope} + * @api private + */ + +Evaluator.prototype.__defineGetter__('currentScope', function(){ + return this.stack.currentFrame.scope; +}); + +/** + * Return the current `Frame`. + * + * @return {Frame} + * @api private + */ + +Evaluator.prototype.__defineGetter__('currentFrame', function(){ + return this.stack.currentFrame; +}); diff --git a/node_modules/stylus/lib/visitor/index.js b/node_modules/stylus/lib/visitor/index.js new file mode 100644 index 00000000..b7f3ce3f --- /dev/null +++ b/node_modules/stylus/lib/visitor/index.js @@ -0,0 +1,31 @@ + +/*! + * Stylus - Visitor + * Copyright (c) Automattic <developer.wordpress.com> + * MIT Licensed + */ + +/** + * Initialize a new `Visitor` with the given `root` Node. + * + * @param {Node} root + * @api private + */ + +var Visitor = module.exports = function Visitor(root) { + this.root = root; +}; + +/** + * Visit the given `node`. + * + * @param {Node|Array} node + * @api public + */ + +Visitor.prototype.visit = function(node, fn){ + var method = 'visit' + node.constructor.name; + if (this[method]) return this[method](node); + return node; +}; + diff --git a/node_modules/stylus/lib/visitor/normalizer.js b/node_modules/stylus/lib/visitor/normalizer.js new file mode 100644 index 00000000..2d67ddfb --- /dev/null +++ b/node_modules/stylus/lib/visitor/normalizer.js @@ -0,0 +1,424 @@ + +/*! + * 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; + 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); +}; diff --git a/node_modules/stylus/lib/visitor/sourcemapper.js b/node_modules/stylus/lib/visitor/sourcemapper.js new file mode 100644 index 00000000..dcb85bba --- /dev/null +++ b/node_modules/stylus/lib/visitor/sourcemapper.js @@ -0,0 +1,203 @@ +/*! + * Stylus - SourceMapper + * Copyright (c) Automattic <developer.wordpress.com> + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Compiler = require('./compiler') + , SourceMapGenerator = require('source-map').SourceMapGenerator + , basename = require('path').basename + , extname = require('path').extname + , dirname = require('path').dirname + , join = require('path').join + , relative = require('path').relative + , sep = require('path').sep + , fs = require('fs'); + +/** + * Initialize a new `SourceMapper` generator with the given `root` Node + * and the following `options`. + * + * @param {Node} root + * @api public + */ + +var SourceMapper = module.exports = function SourceMapper(root, options){ + options = options || {}; + this.column = 1; + this.lineno = 1; + this.contents = {}; + this.filename = options.filename; + this.dest = options.dest; + + var sourcemap = options.sourcemap; + this.basePath = sourcemap.basePath || '.'; + this.inline = sourcemap.inline; + this.comment = sourcemap.comment; + if (this.dest && extname(this.dest) === '.css') { + this.basename = basename(this.dest); + this.dest = dirname(this.dest); + } else { + this.basename = basename(this.filename, extname(this.filename)) + '.css'; + } + this.utf8 = false; + + this.map = new SourceMapGenerator({ + file: this.basename, + sourceRoot: sourcemap.sourceRoot || null + }); + Compiler.call(this, root, options); +}; + +/** + * Inherit from `Compiler.prototype`. + */ + +SourceMapper.prototype.__proto__ = Compiler.prototype; + +/** + * Generate and write source map. + * + * @return {String} + * @api private + */ + +var compile = Compiler.prototype.compile; +SourceMapper.prototype.compile = function(){ + var css = compile.call(this) + , out = this.basename + '.map' + , url = this.normalizePath(this.dest + ? join(this.dest, out) + : join(dirname(this.filename), out)) + , map; + + if (this.inline) { + map = this.map.toString(); + url = 'data:application/json;' + + (this.utf8 ? 'charset=utf-8;' : '') + 'base64,' + + new Buffer(map).toString('base64'); + } + if (this.inline || false !== this.comment) + css += '/*# sourceMappingURL=' + url + ' */'; + return css; +}; + +/** + * Add mapping information. + * + * @param {String} str + * @param {Node} node + * @return {String} + * @api private + */ + +SourceMapper.prototype.out = function(str, node){ + if (node && node.lineno) { + var filename = this.normalizePath(node.filename); + + this.map.addMapping({ + original: { + line: node.lineno, + column: node.column - 1 + }, + generated: { + line: this.lineno, + column: this.column - 1 + }, + source: filename + }); + + if (this.inline && !this.contents[filename]) { + this.map.setSourceContent(filename, fs.readFileSync(node.filename, 'utf-8')); + this.contents[filename] = true; + } + } + + this.move(str); + return str; +}; + +/** + * Move current line and column position. + * + * @param {String} str + * @api private + */ + +SourceMapper.prototype.move = function(str){ + var lines = str.match(/\n/g) + , idx = str.lastIndexOf('\n'); + + if (lines) this.lineno += lines.length; + this.column = ~idx + ? str.length - idx + : this.column + str.length; +}; + +/** + * Normalize the given `path`. + * + * @param {String} path + * @return {String} + * @api private + */ + +SourceMapper.prototype.normalizePath = function(path){ + path = relative(this.dest || this.basePath, path); + if ('\\' == sep) { + path = path.replace(/^[a-z]:\\/i, '/') + .replace(/\\/g, '/'); + } + return path; +}; + +/** + * Visit Literal. + */ + +var literal = Compiler.prototype.visitLiteral; +SourceMapper.prototype.visitLiteral = function(lit){ + var val = literal.call(this, lit) + , filename = this.normalizePath(lit.filename) + , indentsRe = /^\s+/ + , lines = val.split('\n'); + + // add mappings for multiline literals + if (lines.length > 1) { + lines.forEach(function(line, i) { + var indents = line.match(indentsRe) + , column = indents && indents[0] + ? indents[0].length + : 0; + + if (lit.css) column += 2; + + this.map.addMapping({ + original: { + line: lit.lineno + i, + column: column + }, + generated: { + line: this.lineno + i, + column: 0 + }, + source: filename + }); + }, this); + } + return val; +}; + +/** + * Visit Charset. + */ + +var charset = Compiler.prototype.visitCharset; +SourceMapper.prototype.visitCharset = function(node){ + this.utf8 = ('utf-8' == node.val.string.toLowerCase()); + return charset.call(this, node); +}; |
