123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837 |
- 'use strict';
- var doctypes = require('doctypes');
- var makeError = require('pug-error');
- var buildRuntime = require('pug-runtime/build');
- var runtime = require('pug-runtime');
- var compileAttrs = require('pug-attrs');
- var selfClosing = require('void-elements');
- var constantinople = require('constantinople');
- var stringify = require('js-stringify');
- var addWith = require('with');
- // This is used to prevent pretty printing inside certain tags
- var WHITE_SPACE_SENSITIVE_TAGS = {
- pre: true,
- textarea: true
- };
- var INTERNAL_VARIABLES = [
- 'pug',
- 'pug_mixins',
- 'pug_interp',
- 'pug_debug_filename',
- 'pug_debug_line',
- 'pug_debug_sources',
- 'pug_html'
- ];
- module.exports = generateCode;
- module.exports.CodeGenerator = Compiler;
- function generateCode(ast, options) {
- return (new Compiler(ast, options)).compile();
- }
- function isConstant(src) {
- return constantinople(src, {pug: runtime, 'pug_interp': undefined});
- }
- function toConstant(src) {
- return constantinople.toConstant(src, {pug: runtime, 'pug_interp': undefined});
- }
- /**
- * Initialize `Compiler` with the given `node`.
- *
- * @param {Node} node
- * @param {Object} options
- * @api public
- */
- function Compiler(node, options) {
- this.options = options = options || {};
- this.node = node;
- this.bufferedConcatenationCount = 0;
- this.hasCompiledDoctype = false;
- this.hasCompiledTag = false;
- this.pp = options.pretty || false;
- if (this.pp && typeof this.pp !== 'string') {
- this.pp = ' ';
- }
- if (this.pp && !/^\s+$/.test(this.pp)) {
- throw new Error(
- 'The pretty parameter should either be a boolean or whitespace only string'
- );
- }
- this.debug = false !== options.compileDebug;
- this.indents = 0;
- this.parentIndents = 0;
- this.terse = false;
- this.mixins = {};
- this.dynamicMixins = false;
- this.eachCount = 0;
- if (options.doctype) this.setDoctype(options.doctype);
- this.runtimeFunctionsUsed = [];
- this.inlineRuntimeFunctions = options.inlineRuntimeFunctions || false;
- if (this.debug && this.inlineRuntimeFunctions) {
- this.runtimeFunctionsUsed.push('rethrow');
- }
- };
- /**
- * Compiler prototype.
- */
- Compiler.prototype = {
- runtime: function (name) {
- if (this.inlineRuntimeFunctions) {
- this.runtimeFunctionsUsed.push(name);
- return 'pug_' + name;
- } else {
- return 'pug.' + name;
- }
- },
- error: function (message, code, node) {
- var err = makeError(code, message, {
- line: node.line,
- column: node.column,
- filename: node.filename,
- });
- throw err;
- },
- /**
- * Compile parse tree to JavaScript.
- *
- * @api public
- */
- compile: function(){
- this.buf = [];
- if (this.pp) this.buf.push("var pug_indent = [];");
- this.lastBufferedIdx = -1;
- this.visit(this.node);
- if (!this.dynamicMixins) {
- // if there are no dynamic mixins we can remove any un-used mixins
- var mixinNames = Object.keys(this.mixins);
- for (var i = 0; i < mixinNames.length; i++) {
- var mixin = this.mixins[mixinNames[i]];
- if (!mixin.used) {
- for (var x = 0; x < mixin.instances.length; x++) {
- for (var y = mixin.instances[x].start; y < mixin.instances[x].end; y++) {
- this.buf[y] = '';
- }
- }
- }
- }
- }
- var js = this.buf.join('\n');
- var globals = this.options.globals ? this.options.globals.concat(INTERNAL_VARIABLES) : INTERNAL_VARIABLES;
- if (this.options.self) {
- js = 'var self = locals || {};' + js;
- } else {
- js = addWith('locals || {}', js, globals.concat(this.runtimeFunctionsUsed.map(function (name) { return 'pug_' + name; })));
- }
- if (this.debug) {
- if (this.options.includeSources) {
- js = 'var pug_debug_sources = ' + stringify(this.options.includeSources) + ';\n' + js;
- }
- js = 'var pug_debug_filename, pug_debug_line;' +
- 'try {' +
- js +
- '} catch (err) {' +
- (this.inlineRuntimeFunctions ? 'pug_rethrow' : 'pug.rethrow') +
- '(err, pug_debug_filename, pug_debug_line' +
- (
- this.options.includeSources
- ? ', pug_debug_sources[pug_debug_filename]'
- : ''
- ) +
- ');' +
- '}';
- }
- return buildRuntime(this.runtimeFunctionsUsed) + 'function ' + (this.options.templateName || 'template') + '(locals) {var pug_html = "", pug_mixins = {}, pug_interp;' + js + ';return pug_html;}';
- },
- /**
- * Sets the default doctype `name`. Sets terse mode to `true` when
- * html 5 is used, causing self-closing tags to end with ">" vs "/>",
- * and boolean attributes are not mirrored.
- *
- * @param {string} name
- * @api public
- */
- setDoctype: function(name){
- this.doctype = doctypes[name.toLowerCase()] || '<!DOCTYPE ' + name + '>';
- this.terse = this.doctype.toLowerCase() == '<!doctype html>';
- this.xml = 0 == this.doctype.indexOf('<?xml');
- },
- /**
- * Buffer the given `str` exactly as is or with interpolation
- *
- * @param {String} str
- * @param {Boolean} interpolate
- * @api public
- */
- buffer: function (str) {
- var self = this;
- str = stringify(str);
- str = str.substr(1, str.length - 2);
- if (this.lastBufferedIdx == this.buf.length && this.bufferedConcatenationCount < 100) {
- if (this.lastBufferedType === 'code') {
- this.lastBuffered += ' + "';
- this.bufferedConcatenationCount++;
- }
- this.lastBufferedType = 'text';
- this.lastBuffered += str;
- this.buf[this.lastBufferedIdx - 1] = 'pug_html = pug_html + ' + this.bufferStartChar + this.lastBuffered + '";';
- } else {
- this.bufferedConcatenationCount = 0;
- this.buf.push('pug_html = pug_html + "' + str + '";');
- this.lastBufferedType = 'text';
- this.bufferStartChar = '"';
- this.lastBuffered = str;
- this.lastBufferedIdx = this.buf.length;
- }
- },
- /**
- * Buffer the given `src` so it is evaluated at run time
- *
- * @param {String} src
- * @api public
- */
- bufferExpression: function (src) {
- if (isConstant(src)) {
- return this.buffer(toConstant(src) + '')
- }
- if (this.lastBufferedIdx == this.buf.length && this.bufferedConcatenationCount < 100) {
- this.bufferedConcatenationCount++;
- if (this.lastBufferedType === 'text') this.lastBuffered += '"';
- this.lastBufferedType = 'code';
- this.lastBuffered += ' + (' + src + ')';
- this.buf[this.lastBufferedIdx - 1] = 'pug_html = pug_html + (' + this.bufferStartChar + this.lastBuffered + ');';
- } else {
- this.bufferedConcatenationCount = 0;
- this.buf.push('pug_html = pug_html + (' + src + ');');
- this.lastBufferedType = 'code';
- this.bufferStartChar = '';
- this.lastBuffered = '(' + src + ')';
- this.lastBufferedIdx = this.buf.length;
- }
- },
- /**
- * Buffer an indent based on the current `indent`
- * property and an additional `offset`.
- *
- * @param {Number} offset
- * @param {Boolean} newline
- * @api public
- */
- prettyIndent: function(offset, newline){
- offset = offset || 0;
- newline = newline ? '\n' : '';
- this.buffer(newline + Array(this.indents + offset).join(this.pp));
- if (this.parentIndents)
- this.buf.push('pug_html = pug_html + pug_indent.join("");');
- },
- /**
- * Visit `node`.
- *
- * @param {Node} node
- * @api public
- */
- visit: function(node, parent){
- var debug = this.debug;
- if (!node) {
- var msg;
- if (parent) {
- msg = 'A child of ' + parent.type + ' (' + (parent.filename || 'Pug') + ':' + parent.line + ')';
- } else {
- msg = 'A top-level node';
- }
- msg += ' is ' + node + ', expected a Pug AST Node.';
- throw new TypeError(msg);
- }
- if (debug && node.debug !== false && node.type !== 'Block') {
- if (node.line) {
- var js = ';pug_debug_line = ' + node.line;
- if (node.filename) js += ';pug_debug_filename = ' + stringify(node.filename);
- this.buf.push(js + ';');
- }
- }
- if (!this['visit' + node.type]) {
- var msg;
- if (parent) {
- msg = 'A child of ' + parent.type
- } else {
- msg = 'A top-level node';
- }
- msg += ' (' + (node.filename || 'Pug') + ':' + node.line + ')'
- + ' is of type ' + node.type + ','
- + ' which is not supported by pug-code-gen.'
- switch (node.type) {
- case 'Filter':
- msg += ' Please use pug-filters to preprocess this AST.'
- break;
- case 'Extends':
- case 'Include':
- case 'NamedBlock':
- case 'FileReference': // unlikely but for the sake of completeness
- msg += ' Please use pug-linker to preprocess this AST.'
- break;
- }
- throw new TypeError(msg);
- }
- this.visitNode(node);
- },
- /**
- * Visit `node`.
- *
- * @param {Node} node
- * @api public
- */
- visitNode: function(node){
- return this['visit' + node.type](node);
- },
- /**
- * Visit case `node`.
- *
- * @param {Literal} node
- * @api public
- */
- visitCase: function(node){
- this.buf.push('switch (' + node.expr + '){');
- this.visit(node.block, node);
- this.buf.push('}');
- },
- /**
- * Visit when `node`.
- *
- * @param {Literal} node
- * @api public
- */
- visitWhen: function(node){
- if ('default' == node.expr) {
- this.buf.push('default:');
- } else {
- this.buf.push('case ' + node.expr + ':');
- }
- if (node.block) {
- this.visit(node.block, node);
- this.buf.push(' break;');
- }
- },
- /**
- * Visit literal `node`.
- *
- * @param {Literal} node
- * @api public
- */
- visitLiteral: function(node){
- this.buffer(node.str);
- },
- visitNamedBlock: function(block){
- return this.visitBlock(block);
- },
- /**
- * Visit all nodes in `block`.
- *
- * @param {Block} block
- * @api public
- */
- visitBlock: function(block){
- var escapePrettyMode = this.escapePrettyMode;
- var pp = this.pp;
- // Pretty print multi-line text
- if (pp && block.nodes.length > 1 && !escapePrettyMode &&
- block.nodes[0].type === 'Text' && block.nodes[1].type === 'Text' ) {
- this.prettyIndent(1, true);
- }
- for (var i = 0; i < block.nodes.length; ++i) {
- // Pretty print text
- if (pp && i > 0 && !escapePrettyMode &&
- block.nodes[i].type === 'Text' && block.nodes[i-1].type === 'Text' &&
- /\n$/.test(block.nodes[i - 1].val)) {
- this.prettyIndent(1, false);
- }
- this.visit(block.nodes[i], block);
- }
- },
- /**
- * Visit a mixin's `block` keyword.
- *
- * @param {MixinBlock} block
- * @api public
- */
- visitMixinBlock: function(block){
- if (this.pp) this.buf.push("pug_indent.push('" + Array(this.indents + 1).join(this.pp) + "');");
- this.buf.push('block && block();');
- if (this.pp) this.buf.push("pug_indent.pop();");
- },
- /**
- * Visit `doctype`. Sets terse mode to `true` when html 5
- * is used, causing self-closing tags to end with ">" vs "/>",
- * and boolean attributes are not mirrored.
- *
- * @param {Doctype} doctype
- * @api public
- */
- visitDoctype: function(doctype){
- if (doctype && (doctype.val || !this.doctype)) {
- this.setDoctype(doctype.val || 'html');
- }
- if (this.doctype) this.buffer(this.doctype);
- this.hasCompiledDoctype = true;
- },
- /**
- * Visit `mixin`, generating a function that
- * may be called within the template.
- *
- * @param {Mixin} mixin
- * @api public
- */
- visitMixin: function(mixin){
- var name = 'pug_mixins[';
- var args = mixin.args || '';
- var block = mixin.block;
- var attrs = mixin.attrs;
- var attrsBlocks = this.attributeBlocks(mixin.attributeBlocks);
- var pp = this.pp;
- var dynamic = mixin.name[0]==='#';
- var key = mixin.name;
- if (dynamic) this.dynamicMixins = true;
- name += (dynamic ? mixin.name.substr(2,mixin.name.length-3):'"'+mixin.name+'"')+']';
- this.mixins[key] = this.mixins[key] || {used: false, instances: []};
- if (mixin.call) {
- this.mixins[key].used = true;
- if (pp) this.buf.push("pug_indent.push('" + Array(this.indents + 1).join(pp) + "');")
- if (block || attrs.length || attrsBlocks.length) {
- this.buf.push(name + '.call({');
- if (block) {
- this.buf.push('block: function(){');
- // Render block with no indents, dynamically added when rendered
- this.parentIndents++;
- var _indents = this.indents;
- this.indents = 0;
- this.visit(mixin.block, mixin);
- this.indents = _indents;
- this.parentIndents--;
- if (attrs.length || attrsBlocks.length) {
- this.buf.push('},');
- } else {
- this.buf.push('}');
- }
- }
- if (attrsBlocks.length) {
- if (attrs.length) {
- var val = this.attrs(attrs);
- attrsBlocks.unshift(val);
- }
- if (attrsBlocks.length > 1) {
- this.buf.push('attributes: ' + this.runtime('merge') + '([' + attrsBlocks.join(',') + '])');
- } else {
- this.buf.push('attributes: ' + attrsBlocks[0]);
- }
- } else if (attrs.length) {
- var val = this.attrs(attrs);
- this.buf.push('attributes: ' + val);
- }
- if (args) {
- this.buf.push('}, ' + args + ');');
- } else {
- this.buf.push('});');
- }
- } else {
- this.buf.push(name + '(' + args + ');');
- }
- if (pp) this.buf.push("pug_indent.pop();")
- } else {
- var mixin_start = this.buf.length;
- args = args ? args.split(',') : [];
- var rest;
- if (args.length && /^\.\.\./.test(args[args.length - 1].trim())) {
- rest = args.pop().trim().replace(/^\.\.\./, '');
- }
- // we need use pug_interp here for v8: https://code.google.com/p/v8/issues/detail?id=4165
- // once fixed, use this: this.buf.push(name + ' = function(' + args.join(',') + '){');
- this.buf.push(name + ' = pug_interp = function(' + args.join(',') + '){');
- this.buf.push('var block = (this && this.block), attributes = (this && this.attributes) || {};');
- if (rest) {
- this.buf.push('var ' + rest + ' = [];');
- this.buf.push('for (pug_interp = ' + args.length + '; pug_interp < arguments.length; pug_interp++) {');
- this.buf.push(' ' + rest + '.push(arguments[pug_interp]);');
- this.buf.push('}');
- }
- this.parentIndents++;
- this.visit(block, mixin);
- this.parentIndents--;
- this.buf.push('};');
- var mixin_end = this.buf.length;
- this.mixins[key].instances.push({start: mixin_start, end: mixin_end});
- }
- },
- /**
- * Visit `tag` buffering tag markup, generating
- * attributes, visiting the `tag`'s code and block.
- *
- * @param {Tag} tag
- * @param {boolean} interpolated
- * @api public
- */
- visitTag: function(tag, interpolated){
- this.indents++;
- var name = tag.name
- , pp = this.pp
- , self = this;
- function bufferName() {
- if (interpolated) self.bufferExpression(tag.expr);
- else self.buffer(name);
- }
- if (WHITE_SPACE_SENSITIVE_TAGS[tag.name] === true) this.escapePrettyMode = true;
- if (!this.hasCompiledTag) {
- if (!this.hasCompiledDoctype && 'html' == name) {
- this.visitDoctype();
- }
- this.hasCompiledTag = true;
- }
- // pretty print
- if (pp && !tag.isInline)
- this.prettyIndent(0, true);
- if (tag.selfClosing || (!this.xml && selfClosing[tag.name])) {
- this.buffer('<');
- bufferName();
- this.visitAttributes(tag.attrs, this.attributeBlocks(tag.attributeBlocks));
- if (this.terse && !tag.selfClosing) {
- this.buffer('>');
- } else {
- this.buffer('/>');
- }
- // if it is non-empty throw an error
- if (tag.code ||
- tag.block &&
- !(tag.block.type === 'Block' && tag.block.nodes.length === 0) &&
- tag.block.nodes.some(function (tag) {
- return tag.type !== 'Text' || !/^\s*$/.test(tag.val)
- })) {
- this.error(name + ' is a self closing element: <'+name+'/> but contains nested content.', 'SELF_CLOSING_CONTENT', tag);
- }
- } else {
- // Optimize attributes buffering
- this.buffer('<');
- bufferName();
- this.visitAttributes(tag.attrs, this.attributeBlocks(tag.attributeBlocks));
- this.buffer('>');
- if (tag.code) this.visitCode(tag.code);
- this.visit(tag.block, tag);
- // pretty print
- if (pp && !tag.isInline && WHITE_SPACE_SENSITIVE_TAGS[tag.name] !== true && !tagCanInline(tag))
- this.prettyIndent(0, true);
- this.buffer('</');
- bufferName();
- this.buffer('>');
- }
- if (WHITE_SPACE_SENSITIVE_TAGS[tag.name] === true) this.escapePrettyMode = false;
- this.indents--;
- },
- /**
- * Visit InterpolatedTag.
- *
- * @param {InterpolatedTag} tag
- * @api public
- */
- visitInterpolatedTag: function(tag) {
- return this.visitTag(tag, true);
- },
- /**
- * Visit `text` node.
- *
- * @param {Text} text
- * @api public
- */
- visitText: function(text){
- this.buffer(text.val);
- },
- /**
- * Visit a `comment`, only buffering when the buffer flag is set.
- *
- * @param {Comment} comment
- * @api public
- */
- visitComment: function(comment){
- if (!comment.buffer) return;
- if (this.pp) this.prettyIndent(1, true);
- this.buffer('<!--' + comment.val + '-->');
- },
- /**
- * Visit a `YieldBlock`.
- *
- * This is necessary since we allow compiling a file with `yield`.
- *
- * @param {YieldBlock} block
- * @api public
- */
- visitYieldBlock: function(block) {},
- /**
- * Visit a `BlockComment`.
- *
- * @param {Comment} comment
- * @api public
- */
- visitBlockComment: function(comment){
- if (!comment.buffer) return;
- if (this.pp) this.prettyIndent(1, true);
- this.buffer('<!--' + (comment.val || ''));
- this.visit(comment.block, comment);
- if (this.pp) this.prettyIndent(1, true);
- this.buffer('-->');
- },
- /**
- * Visit `code`, respecting buffer / escape flags.
- * If the code is followed by a block, wrap it in
- * a self-calling function.
- *
- * @param {Code} code
- * @api public
- */
- visitCode: function(code){
- // Wrap code blocks with {}.
- // we only wrap unbuffered code blocks ATM
- // since they are usually flow control
- // Buffer code
- if (code.buffer) {
- var val = code.val.trim();
- val = 'null == (pug_interp = '+val+') ? "" : pug_interp';
- if (code.mustEscape !== false) val = this.runtime('escape') + '(' + val + ')';
- this.bufferExpression(val);
- } else {
- this.buf.push(code.val);
- }
- // Block support
- if (code.block) {
- if (!code.buffer) this.buf.push('{');
- this.visit(code.block, code);
- if (!code.buffer) this.buf.push('}');
- }
- },
- /**
- * Visit `Conditional`.
- *
- * @param {Conditional} cond
- * @api public
- */
- visitConditional: function(cond){
- var test = cond.test;
- this.buf.push('if (' + test + ') {');
- this.visit(cond.consequent, cond);
- this.buf.push('}')
- if (cond.alternate) {
- if (cond.alternate.type === 'Conditional') {
- this.buf.push('else')
- this.visitConditional(cond.alternate);
- } else {
- this.buf.push('else {');
- this.visit(cond.alternate, cond);
- this.buf.push('}');
- }
- }
- },
- /**
- * Visit `While`.
- *
- * @param {While} loop
- * @api public
- */
- visitWhile: function(loop){
- var test = loop.test;
- this.buf.push('while (' + test + ') {');
- this.visit(loop.block, loop);
- this.buf.push('}');
- },
- /**
- * Visit `each` block.
- *
- * @param {Each} each
- * @api public
- */
- visitEach: function(each){
- var indexVarName = each.key || 'pug_index' + this.eachCount;
- this.eachCount++;
- this.buf.push(''
- + '// iterate ' + each.obj + '\n'
- + ';(function(){\n'
- + ' var $$obj = ' + each.obj + ';\n'
- + ' if (\'number\' == typeof $$obj.length) {');
- if (each.alternate) {
- this.buf.push(' if ($$obj.length) {');
- }
- this.buf.push(''
- + ' for (var ' + indexVarName + ' = 0, $$l = $$obj.length; ' + indexVarName + ' < $$l; ' + indexVarName + '++) {\n'
- + ' var ' + each.val + ' = $$obj[' + indexVarName + '];');
- this.visit(each.block, each);
- this.buf.push(' }');
- if (each.alternate) {
- this.buf.push(' } else {');
- this.visit(each.alternate, each);
- this.buf.push(' }');
- }
- this.buf.push(''
- + ' } else {\n'
- + ' var $$l = 0;\n'
- + ' for (var ' + indexVarName + ' in $$obj) {\n'
- + ' $$l++;\n'
- + ' var ' + each.val + ' = $$obj[' + indexVarName + '];');
- this.visit(each.block, each);
- this.buf.push(' }');
- if (each.alternate) {
- this.buf.push(' if ($$l === 0) {');
- this.visit(each.alternate, each);
- this.buf.push(' }');
- }
- this.buf.push(' }\n}).call(this);\n');
- },
- /**
- * Visit `attrs`.
- *
- * @param {Array} attrs
- * @api public
- */
- visitAttributes: function(attrs, attributeBlocks){
- if (attributeBlocks.length) {
- if (attrs.length) {
- var val = this.attrs(attrs);
- attributeBlocks.unshift(val);
- }
- if (attributeBlocks.length > 1) {
- this.bufferExpression(this.runtime('attrs') + '(' + this.runtime('merge') + '([' + attributeBlocks.join(',') + ']), ' + stringify(this.terse) + ')');
- } else {
- this.bufferExpression(this.runtime('attrs') + '(' + attributeBlocks[0] + ', ' + stringify(this.terse) + ')');
- }
- } else if (attrs.length) {
- this.attrs(attrs, true);
- }
- },
- /**
- * Compile attributes.
- */
- attrs: function(attrs, buffer){
- var res = compileAttrs(attrs, {
- terse: this.terse,
- format: buffer ? 'html' : 'object',
- runtime: this.runtime.bind(this)
- });
- if (buffer) {
- this.bufferExpression(res);
- }
- return res;
- },
- /**
- * Compile attribute blocks.
- */
- attributeBlocks: function (attributeBlocks) {
- return attributeBlocks && attributeBlocks.slice().map(function(attrBlock){
- return attrBlock.val;
- });
- }
- };
- function tagCanInline(tag) {
- function isInline(node){
- // Recurse if the node is a block
- if (node.type === 'Block') return node.nodes.every(isInline);
- // When there is a YieldBlock here, it is an indication that the file is
- // expected to be included but is not. If this is the case, the block
- // must be empty.
- if (node.type === 'YieldBlock') return true;
- return (node.type === 'Text' && !/\n/.test(node.val)) || node.isInline;
- }
- return tag.block.nodes.every(isInline);
- }
|