jade-code-gen
Advanced tools
Comparing version 0.0.3 to 0.0.4
350
index.js
@@ -6,4 +6,4 @@ 'use strict'; | ||
var runtime = require('jade-runtime'); | ||
var compileAttrs = require('jade-attrs'); | ||
var selfClosing = require('void-elements'); | ||
var parseJSExpression = require('character-parser').parseMax; | ||
var constantinople = require('constantinople'); | ||
@@ -13,2 +13,8 @@ var stringify = require('js-stringify'); | ||
// This is used to prevent pretty printing inside certain tags | ||
var WHITE_SPACE_SENSITIVE_TAGS = { | ||
pre: true, | ||
textarea: true | ||
}; | ||
var INTERNAL_VARIABLES = [ | ||
@@ -21,3 +27,3 @@ 'jade', | ||
'jade_debug_sources', | ||
'buf' | ||
'jade_html' | ||
]; | ||
@@ -50,2 +56,3 @@ | ||
this.node = node; | ||
this.bufferedConcatenationCount = 0; | ||
this.hasCompiledDoctype = false; | ||
@@ -63,5 +70,6 @@ this.hasCompiledTag = false; | ||
this.dynamicMixins = false; | ||
this.eachCount = 0; | ||
if (options.doctype) this.setDoctype(options.doctype); | ||
this.runtimeFunctionsUsed = []; | ||
this.inlineRuntimeFunctions = options.inlineRuntimeFunctions; | ||
this.inlineRuntimeFunctions = options.inlineRuntimeFunctions || false; | ||
if (this.debug && this.inlineRuntimeFunctions) { | ||
@@ -146,3 +154,3 @@ this.runtimeFunctionsUsed.push('rethrow'); | ||
} | ||
return buildRuntime(this.runtimeFunctionsUsed) + 'function ' + (this.options.templateName || 'template') + '(locals) {var buf = [], jade_mixins = {}, jade_interp;' + js + ';return buf.join("");}'; | ||
return buildRuntime(this.runtimeFunctionsUsed) + 'function ' + (this.options.templateName || 'template') + '(locals) {var jade_html = "", jade_mixins = {}, jade_interp;' + js + ';return jade_html;}'; | ||
}, | ||
@@ -173,22 +181,4 @@ | ||
buffer: function (str, interpolate) { | ||
buffer: function (str) { | ||
var self = this; | ||
if (interpolate) { | ||
var match = /(\\)?([#!]){((?:.|\n)*)$/.exec(str); | ||
if (match) { | ||
this.buffer(str.substr(0, match.index), false); | ||
if (match[1]) { // escape | ||
this.buffer(match[2] + '{', false); | ||
this.buffer(match[3], true); | ||
return; | ||
} else { | ||
var rest = match[3]; | ||
var range = parseJSExpression(rest); | ||
var code = ('!' == match[2] ? '' : this.runtime('escape')) + "((jade_interp = " + range.src + ") == null ? '' : jade_interp)"; | ||
this.bufferExpression(code); | ||
this.buffer(rest.substr(range.end + 1), true); | ||
return; | ||
} | ||
} | ||
} | ||
@@ -198,9 +188,13 @@ str = stringify(str); | ||
if (this.lastBufferedIdx == this.buf.length) { | ||
if (this.lastBufferedType === 'code') this.lastBuffered += ' + "'; | ||
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] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + '");' | ||
this.buf[this.lastBufferedIdx - 1] = 'jade_html = jade_html + ' + this.bufferStartChar + this.lastBuffered + '";'; | ||
} else { | ||
this.buf.push('buf.push("' + str + '");'); | ||
this.bufferedConcatenationCount = 0; | ||
this.buf.push('jade_html = jade_html + "' + str + '";'); | ||
this.lastBufferedType = 'text'; | ||
@@ -222,11 +216,13 @@ this.bufferStartChar = '"'; | ||
if (isConstant(src)) { | ||
return this.buffer(toConstant(src) + '', false) | ||
return this.buffer(toConstant(src) + '') | ||
} | ||
if (this.lastBufferedIdx == this.buf.length) { | ||
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] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + ');' | ||
this.buf[this.lastBufferedIdx - 1] = 'jade_html = jade_html + (' + this.bufferStartChar + this.lastBuffered + ');'; | ||
} else { | ||
this.buf.push('buf.push(' + src + ');'); | ||
this.bufferedConcatenationCount = 0; | ||
this.buf.push('jade_html = jade_html + (' + src + ');'); | ||
this.lastBufferedType = 'code'; | ||
@@ -253,3 +249,3 @@ this.bufferStartChar = ''; | ||
if (this.parentIndents) | ||
this.buf.push("buf.push.apply(buf, jade_indent);"); | ||
this.buf.push('jade_html = jade_html + jade_indent.join("");'); | ||
}, | ||
@@ -264,5 +260,16 @@ | ||
visit: function(node){ | ||
visit: function(node, parent){ | ||
var debug = this.debug; | ||
if (!node) { | ||
var msg; | ||
if (parent) { | ||
msg = 'A child of ' + parent.type + ' (' + (parent.filename || 'Jade') + ':' + parent.line + ')'; | ||
} else { | ||
msg = 'A top-level node'; | ||
} | ||
msg += ' is ' + node + ', expected a Jade AST Node.'; | ||
throw new TypeError(msg); | ||
} | ||
if (debug && node.debug !== false && node.type !== 'Block') { | ||
@@ -276,2 +283,26 @@ if (node.line) { | ||
if (!this['visit' + node.type]) { | ||
var msg; | ||
if (parent) { | ||
msg = 'A child of ' + parent.type | ||
} else { | ||
msg = 'A top-level node'; | ||
} | ||
msg += ' (' + (node.filename || 'Jade') + ':' + node.line + ')' | ||
+ ' is of type ' + node.type + ',' | ||
+ ' which is not supported by jade-code-gen.' | ||
switch (node.type) { | ||
case 'Filter': | ||
msg += ' Please use jade-filters to preprocess this AST.' | ||
break; | ||
case 'Extends': | ||
case 'Include': | ||
case 'NamedBlock': | ||
case 'FileReference': // unlikely but for the sake of completeness | ||
msg += ' Please use jade-linker to preprocess this AST.' | ||
break; | ||
} | ||
throw new TypeError(msg); | ||
} | ||
this.visitNode(node); | ||
@@ -303,3 +334,3 @@ //this.buf.push(''); | ||
this.buf.push('switch (' + node.expr + '){'); | ||
this.visit(node.block); | ||
this.visit(node.block, node); | ||
this.buf.push('}'); | ||
@@ -323,3 +354,3 @@ this.withinCase = _; | ||
if (node.block) { | ||
this.visit(node.block); | ||
this.visit(node.block, node); | ||
this.buf.push(' break;'); | ||
@@ -351,7 +382,7 @@ } | ||
visitBlock: function(block){ | ||
var escape = this.escape; | ||
var escapePrettyMode = this.escapePrettyMode; | ||
var pp = this.pp; | ||
// Pretty print multi-line text | ||
if (pp && block.nodes.length > 1 && !escape && | ||
if (pp && block.nodes.length > 1 && !escapePrettyMode && | ||
block.nodes[0].type === 'Text' && block.nodes[1].type === 'Text' ) { | ||
@@ -362,3 +393,3 @@ this.prettyIndent(1, true); | ||
// Pretty print text | ||
if (pp && i > 0 && !escape && | ||
if (pp && i > 0 && !escapePrettyMode && | ||
block.nodes[i].type === 'Text' && block.nodes[i-1].type === 'Text' && | ||
@@ -368,3 +399,3 @@ /\n$/.test(block.nodes[i - 1].val)) { | ||
} | ||
this.visit(block.nodes[i]); | ||
this.visit(block.nodes[i], block); | ||
} | ||
@@ -439,3 +470,3 @@ }, | ||
this.indents = 0; | ||
this.visit(mixin.block); | ||
this.visit(mixin.block, mixin); | ||
this.indents = _indents; | ||
@@ -456,3 +487,7 @@ this.parentIndents--; | ||
} | ||
this.buf.push('attributes: ' + this.runtime('merge') + '([' + attrsBlocks.join(',') + '])'); | ||
if (attrsBlocks.length > 1) { | ||
this.buf.push('attributes: ' + this.runtime('merge') + '([' + attrsBlocks.join(',') + '])'); | ||
} else { | ||
this.buf.push('attributes: ' + attrsBlocks[0]); | ||
} | ||
} else if (attrs.length) { | ||
@@ -491,3 +526,3 @@ var val = this.attrs(attrs); | ||
this.parentIndents++; | ||
this.visit(block); | ||
this.visit(block, mixin); | ||
this.parentIndents--; | ||
@@ -505,6 +540,7 @@ this.buf.push('};'); | ||
* @param {Tag} tag | ||
* @param {boolean} interpolated | ||
* @api public | ||
*/ | ||
visitTag: function(tag){ | ||
visitTag: function(tag, interpolated){ | ||
this.indents++; | ||
@@ -516,7 +552,7 @@ var name = tag.name | ||
function bufferName() { | ||
if (tag.buffer) self.bufferExpression(name); | ||
if (interpolated) self.bufferExpression(tag.expr); | ||
else self.buffer(name); | ||
} | ||
if ('pre' == tag.name) this.escape = true; | ||
if (WHITE_SPACE_SENSITIVE_TAGS[tag.name] === true) this.escapePrettyMode = true; | ||
@@ -537,7 +573,10 @@ if (!this.hasCompiledTag) { | ||
this.visitAttributes(tag.attrs, tag.attributeBlocks.slice()); | ||
this.terse | ||
? this.buffer('>') | ||
: this.buffer('/>'); | ||
if (this.terse && !tag.selfClosing) { | ||
this.buffer('>'); | ||
} else { | ||
this.buffer('/>'); | ||
} | ||
// if it is non-empty throw an error | ||
if (tag.block && | ||
if (tag.code || | ||
tag.block && | ||
!(tag.block.type === 'Block' && tag.block.nodes.length === 0) && | ||
@@ -556,6 +595,6 @@ tag.block.nodes.some(function (tag) { | ||
if (tag.code) this.visitCode(tag.code); | ||
this.visit(tag.block); | ||
this.visit(tag.block, tag); | ||
// pretty print | ||
if (pp && !tag.isInline && 'pre' != tag.name && !tagCanInline(tag)) | ||
if (pp && !tag.isInline && WHITE_SPACE_SENSITIVE_TAGS[tag.name] !== true && !tagCanInline(tag)) | ||
this.prettyIndent(0, true); | ||
@@ -568,3 +607,3 @@ | ||
if ('pre' == tag.name) this.escape = false; | ||
if (WHITE_SPACE_SENSITIVE_TAGS[tag.name] === true) this.escapePrettyMode = false; | ||
@@ -575,2 +614,13 @@ this.indents--; | ||
/** | ||
* Visit InterpolatedTag. | ||
* | ||
* @param {InterpolatedTag} tag | ||
* @api public | ||
*/ | ||
visitInterpolatedTag: function(tag) { | ||
return this.visitTag(tag, true); | ||
}, | ||
/** | ||
* Visit `text` node. | ||
@@ -583,3 +633,3 @@ * | ||
visitText: function(text){ | ||
this.buffer(text.val, true); | ||
this.buffer(text.val); | ||
}, | ||
@@ -601,2 +651,13 @@ | ||
/** | ||
* 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`. | ||
@@ -612,3 +673,3 @@ * | ||
this.buffer('<!--' + (comment.val || '')); | ||
this.visit(comment.block); | ||
this.visit(comment.block, comment); | ||
if (this.pp) this.prettyIndent(1, true); | ||
@@ -636,3 +697,3 @@ this.buffer('-->'); | ||
val = 'null == (jade_interp = '+val+') ? "" : jade_interp'; | ||
if (code.escape) val = this.runtime('escape') + '(' + val + ')'; | ||
if (code.mustEscape !== false) val = this.runtime('escape') + '(' + val + ')'; | ||
this.bufferExpression(val); | ||
@@ -646,3 +707,3 @@ } else { | ||
if (!code.buffer) this.buf.push('{'); | ||
this.visit(code.block); | ||
this.visit(code.block, code); | ||
if (!code.buffer) this.buf.push('}'); | ||
@@ -653,2 +714,40 @@ } | ||
/** | ||
* 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. | ||
@@ -661,42 +760,46 @@ * | ||
visitEach: function(each){ | ||
var indexVarName = each.key || 'jade_index' + this.eachCount | ||
, objVarName = 'jade_obj' + this.eachCount | ||
, lengthVarName = 'jade_length' + this.eachCount; | ||
this.eachCount++; | ||
this.buf.push('' | ||
+ '// iterate ' + each.obj + '\n' | ||
+ ';(function(){\n' | ||
+ ' var $$obj = ' + each.obj + ';\n' | ||
+ ' if (\'number\' == typeof $$obj.length) {\n'); | ||
+ 'var ' + objVarName + ' = ' + each.obj + ';\n' | ||
+ 'if (\'number\' == typeof ' + objVarName + '.length) {\n'); | ||
if (each.alternative) { | ||
this.buf.push(' if ($$obj.length) {'); | ||
if (each.alternate) { | ||
this.buf.push('if (' + objVarName + '.length) {'); | ||
} | ||
this.buf.push('' | ||
+ ' for (var ' + each.key + ' = 0, $$l = $$obj.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n' | ||
+ ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); | ||
+ ' for (var ' + indexVarName + ' = 0, ' + lengthVarName + ' = ' + objVarName + '.length; ' + indexVarName + ' < ' + lengthVarName + '; ' + indexVarName + '++) {\n' | ||
+ ' var ' + each.val + ' = ' + objVarName + '[' + indexVarName + '];\n'); | ||
this.visit(each.block); | ||
this.visit(each.block, each); | ||
this.buf.push(' }\n'); | ||
this.buf.push(' }\n'); | ||
if (each.alternative) { | ||
this.buf.push(' } else {'); | ||
this.visit(each.alternative); | ||
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 ' + each.key + ' in $$obj) {\n' | ||
+ ' $$l++;' | ||
+ ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); | ||
+ '} else {\n' | ||
+ ' var ' + lengthVarName + ' = 0;\n' | ||
+ ' for (var ' + indexVarName + ' in ' + objVarName + ') {\n' | ||
+ ' ' + lengthVarName + '++;\n' | ||
+ ' var ' + each.val + ' = ' + objVarName + '[' + indexVarName + '];\n'); | ||
this.visit(each.block); | ||
this.visit(each.block, each); | ||
this.buf.push(' }\n'); | ||
if (each.alternative) { | ||
this.buf.push(' if ($$l === 0) {'); | ||
this.visit(each.alternative); | ||
this.buf.push(' }'); | ||
this.buf.push(' }\n'); | ||
if (each.alternate) { | ||
this.buf.push(' if (' + lengthVarName + ' === 0) {'); | ||
this.visit(each.alternate, each); | ||
this.buf.push(' }'); | ||
} | ||
this.buf.push(' }\n}).call(this);\n'); | ||
this.buf.push('}\n'); | ||
}, | ||
@@ -732,76 +835,11 @@ | ||
attrs: function(attrs, buffer){ | ||
var buf = []; | ||
var classes = []; | ||
var classEscaping = []; | ||
var addAttribute = function (key, val, escaped) { | ||
if (isConstant(val)) { | ||
if (buffer) { | ||
this.buffer(runtime.attr(key, toConstant(val), escaped, this.terse)); | ||
} else { | ||
var val = toConstant(val); | ||
if (escaped && !(key.indexOf('data') === 0 && typeof val !== 'string')) { | ||
val = runtime.escape(val); | ||
} | ||
buf.push(stringify(key) + ': ' + stringify(val)); | ||
} | ||
} else { | ||
if (buffer) { | ||
this.bufferExpression(this.runtime('attr') + '("' + key + '", ' + val + ', ' + stringify(escaped) + ', ' + stringify(this.terse) + ')'); | ||
} else { | ||
var val = val; | ||
if (escaped && !(key.indexOf('data') === 0)) { | ||
val = this.runtime('escape') + '(' + val + ')'; | ||
} else if (escaped) { | ||
val = '(typeof (jade_interp = ' + val + ') == "string" ? ' + this.runtime('escape') + '(jade_interp) : jade_interp)'; | ||
} | ||
buf.push(stringify(key) + ': ' + val); | ||
} | ||
} | ||
}.bind(this); | ||
attrs.forEach(function(attr){ | ||
var key = attr.name; | ||
var val = attr.val; | ||
var escaped = attr.escaped; | ||
if (key === 'class') { | ||
classes.push(val); | ||
classEscaping.push(escaped); | ||
} else { | ||
if (key === 'style') { | ||
if (isConstant(val)) { | ||
val = stringify(runtime.style(toConstant(val))); | ||
} else { | ||
val = this.runtime('style') + '(' + val + ')'; | ||
} | ||
} | ||
addAttribute(key, val, escaped); | ||
} | ||
}.bind(this)); | ||
if (classes.length) { | ||
if (classes.every(isConstant)) { | ||
addAttribute( | ||
'class', | ||
stringify(runtime.classes(classes.map(toConstant), classEscaping)), | ||
false | ||
); | ||
} else { | ||
classes = classes.map(function (cls, i) { | ||
if (isConstant(cls)) { | ||
cls = stringify(runtime.classes( | ||
[toConstant(cls)], | ||
classEscaping[i] | ||
)); | ||
classEscaping[i] = false; | ||
} | ||
return cls; | ||
}); | ||
addAttribute( | ||
'class', | ||
this.runtime('classes') + '([' + classes.join(',') + '], ' + stringify(classEscaping) + ')', | ||
false | ||
); | ||
} | ||
var res = compileAttrs(attrs, { | ||
terse: this.terse, | ||
format: buffer ? 'html' : 'object', | ||
runtime: this.runtime.bind(this) | ||
}); | ||
if (buffer) { | ||
this.bufferExpression(res); | ||
} | ||
return '{' + buf.join(',') + '}'; | ||
return res; | ||
} | ||
@@ -814,2 +852,6 @@ }; | ||
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; | ||
@@ -816,0 +858,0 @@ } |
{ | ||
"name": "jade-code-gen", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"description": "Default code-generator for jade. It generates HTML via a JavaScript template function.", | ||
"keywords": [], | ||
"keywords": [ | ||
"jade" | ||
], | ||
"dependencies": { | ||
"character-parser": "^1.2.2", | ||
"constantinople": "^3.0.1", | ||
"doctypes": "^1.0.0", | ||
"jade-attrs": "^2.0.0", | ||
"jade-runtime": "^1.0.0", | ||
@@ -16,3 +18,2 @@ "js-stringify": "^1.0.1", | ||
"devDependencies": { | ||
"mocha": "*" | ||
}, | ||
@@ -28,2 +29,2 @@ "scripts": { | ||
"license": "MIT" | ||
} | ||
} |
@@ -15,15 +15,49 @@ # jade-code-gen | ||
Options: | ||
```js | ||
var generateCode = require('jade-code-gen'); | ||
``` | ||
- pretty (boolean) | ||
- compileDebug (boolean) - defaults to `true` | ||
- doctype (string) | ||
- includeRuntimeFunctions (boolean) | ||
- globals (array of strings) | ||
- self (boolean) | ||
- includeSources (map of filename to source string) | ||
- templateName (string) - defaults to `'template'` | ||
### `generateCode(ast, options)` | ||
Generate a JavaScript function string for the given AST. | ||
`ast` is a fully expanded AST for Jade, with all inclusion, extends, and filters resolved. | ||
`options` may contain the following properties that have the same meaning as the options with the same names in `jade`: | ||
- pretty (boolean): default is `false` | ||
- compileDebug (boolean): default is `false` | ||
- doctype (string): default is `undefined` | ||
- inlineRuntimeFunctions (boolean): default is `false` | ||
- globals (array of strings): default is `[]` | ||
- self (boolean): default is `false` | ||
In addition to above, `jade-code-gen` has the following unique options: | ||
- includeSources (object): map of filename to source string; used if `compileDebug` is `true`; default is `undefined` | ||
- templateName (string): the name of the generated function; default is `'template'` | ||
```js | ||
var lex = require('jade-lexer'); | ||
var parse = require('jade-parser'); | ||
var generateCode = require('jade-code-gen'); | ||
var funcStr = generateCode(parse(lex('p Hello world!')), { | ||
compileDebug: false, | ||
pretty: true, | ||
inlineRuntimeFunctions: false, | ||
templateName: 'helloWorld' | ||
}); | ||
var func = Function('locals', funcStr); | ||
func(); | ||
//=> '\n<p>Hello world!</p>' | ||
``` | ||
### `new generateCode.CodeGenerator(ast, options)` | ||
The constructor for the internal class of the code generator. You shouldn't need to use this for most purposes. | ||
## License | ||
MIT |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
26505
0
725
63
5
1
+ Addedjade-attrs@^2.0.0
+ Addedjade-attrs@2.0.0(transitive)
- Removedcharacter-parser@^1.2.2
- Removedcharacter-parser@1.2.2(transitive)