12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010 |
- '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(' +
- stringify(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(' +
- stringify(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');
- },
- visitEachOf: function(each) {
- this.buf.push(
- '' +
- '// iterate ' +
- each.obj +
- '\n' +
- 'for (const ' +
- each.val +
- ' of ' +
- each.obj +
- ') {\n'
- );
- this.visit(each.block, each);
- this.buf.push('}\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);
- }
|