summaryrefslogtreecommitdiffstats
path: root/lib/handlebars/compiler/javascript-compiler.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/handlebars/compiler/javascript-compiler.js')
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js224
1 files changed, 124 insertions, 100 deletions
diff --git a/lib/handlebars/compiler/javascript-compiler.js b/lib/handlebars/compiler/javascript-compiler.js
index 4256b1c..cb9b1ca 100644
--- a/lib/handlebars/compiler/javascript-compiler.js
+++ b/lib/handlebars/compiler/javascript-compiler.js
@@ -1,5 +1,7 @@
import { COMPILER_REVISION, REVISION_CHANGES } from "../base";
import Exception from "../exception";
+import {isArray} from "../utils";
+import CodeGen from "./code-gen";
function Literal(value) {
this.value = value;
@@ -12,15 +14,15 @@ JavaScriptCompiler.prototype = {
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name /* , type*/) {
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
- return parent + "." + name;
+ return [parent, ".", name];
} else {
- return parent + "['" + name + "']";
+ return [parent, "['", name, "']"];
}
},
depthedLookup: function(name) {
this.aliases.lookup = 'this.lookup';
- return 'lookup(depths, "' + name + '")';
+ return ['lookup(depths, "', name, '")'];
},
compilerInfo: function() {
@@ -29,15 +31,23 @@ JavaScriptCompiler.prototype = {
return [revision, versions];
},
- appendToBuffer: function(string) {
+ appendToBuffer: function(string, location, explicit) {
+ // Force a string as this simplifies the merge logic.
+ if (!isArray(string)) {
+ string = [string];
+ }
+ string = this.source.wrap(string, location);
+
if (this.environment.isSimple) {
- return "return " + string + ";";
+ return ['return ', string, ';'];
+ } else if (explicit) {
+ // This is a case where the buffer operation occurs as a child of another
+ // construct, generally braces. We have to explicitly output these buffer
+ // operations to ensure that the emitted code goes in the correct location.
+ return ['buffer += ', string, ';'];
} else {
- return {
- appendToBuffer: true,
- content: string,
- toString: function() { return "buffer += " + string + ";"; }
- };
+ string.appendToBuffer = true;
+ return string;
}
},
@@ -84,11 +94,12 @@ JavaScriptCompiler.prototype = {
for (i = 0, l = opcodes.length; i < l; i++) {
opcode = opcodes[i];
- this.currentLoc = opcode.loc;
+ this.source.currentLocation = opcode.loc;
this[opcode.opcode].apply(this, opcode.args);
}
// Flush any trailing content that might be pending.
+ this.source.currentLocation = undefined;
this.pushSource('');
/* istanbul ignore next */
@@ -125,6 +136,13 @@ JavaScriptCompiler.prototype = {
if (!asObject) {
ret.compiler = JSON.stringify(ret.compiler);
ret = this.objectLiteral(ret);
+
+ if (options.srcName) {
+ ret = ret.toStringWithSourceMap({file: options.destName});
+ ret.map = ret.map && ret.map.toString();
+ } else {
+ ret = ret.toString();
+ }
}
return ret;
@@ -137,7 +155,7 @@ JavaScriptCompiler.prototype = {
// track the last context pushed into place to allow skipping the
// getContext opcode when it would be a noop
this.lastContext = 0;
- this.source = [];
+ this.source = new CodeGen(this.options.srcName);
},
createFunctionContext: function(asObject) {
@@ -169,59 +187,67 @@ JavaScriptCompiler.prototype = {
return Function.apply(this, params);
} else {
- return 'function(' + params.join(',') + ') {\n ' + source + '}';
+ return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
}
},
mergeSource: function(varDeclarations) {
- var source = '',
- buffer,
+ var isSimple = this.environment.isSimple,
appendOnly = !this.forceBuffer,
- appendFirst;
+ appendFirst,
- for (var i = 0, len = this.source.length; i < len; i++) {
- var line = this.source[i];
+ sourceSeen,
+ bufferStart,
+ bufferEnd;
+ this.source.each(function(line) {
if (line.appendToBuffer) {
- if (buffer) {
- buffer = buffer + '\n + ' + line.content;
+ if (bufferStart) {
+ line.prepend(' + ');
} else {
- buffer = line.content;
+ bufferStart = line;
}
+ bufferEnd = line;
} else {
- if (buffer) {
- if (!source) {
+ if (bufferStart) {
+ if (!sourceSeen) {
appendFirst = true;
- source = buffer + ';\n ';
} else {
- source += 'buffer += ' + buffer + ';\n ';
+ bufferStart.prepend('buffer += ');
}
- buffer = undefined;
+ bufferEnd.add(';');
+ bufferStart = bufferEnd = undefined;
}
- source += line + '\n ';
- if (!this.environment.isSimple) {
+ sourceSeen = true;
+ if (!isSimple) {
appendOnly = false;
}
}
- }
+ });
+
if (appendOnly) {
- if (buffer || !source) {
- source += 'return ' + (buffer || '""') + ';\n';
+ if (bufferStart) {
+ bufferStart.prepend('return ');
+ bufferEnd.add(';');
+ } else {
+ this.source.push('return "";');
}
} else {
varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer());
- if (buffer) {
- source += 'return buffer + ' + buffer + ';\n';
+
+ if (bufferStart) {
+ bufferStart.prepend('return buffer + ');
+ bufferEnd.add(';');
} else {
- source += 'return buffer;\n';
+ this.source.push('return buffer;');
}
}
if (varDeclarations) {
- source = 'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n ') + source;
+ this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n '));
}
- return source;
+ return this.source.merge();
},
// [blockValue]
@@ -242,7 +268,7 @@ JavaScriptCompiler.prototype = {
var blockName = this.popStack();
params.splice(1, 0, blockName);
- this.push('blockHelperMissing.call(' + params.join(', ') + ')');
+ this.push(this.source.functionCall('blockHelperMissing', 'call', params));
},
// [ambiguousBlockValue]
@@ -263,7 +289,10 @@ JavaScriptCompiler.prototype = {
var current = this.topStack();
params.splice(1, 0, current);
- this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
+ this.pushSource([
+ 'if (!', this.lastHelper, ') { ',
+ current, ' = ', this.source.functionCall('blockHelperMissing', 'call', params),
+ '}']);
},
// [appendContent]
@@ -275,6 +304,8 @@ JavaScriptCompiler.prototype = {
appendContent: function(content) {
if (this.pendingContent) {
content = this.pendingContent + content;
+ } else {
+ this.pendingLocation = this.source.currentLocation;
}
this.pendingContent = content;
@@ -292,15 +323,15 @@ JavaScriptCompiler.prototype = {
append: function() {
if (this.isInline()) {
this.replaceStack(function(current) {
- return ' != null ? ' + current + ' : ""';
+ return [' != null ? ', current, ' : ""'];
});
this.pushSource(this.appendToBuffer(this.popStack()));
} else {
var local = this.popStack();
- this.pushSource('if (' + local + ' != null) { ' + this.appendToBuffer(local) + ' }');
+ this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
if (this.environment.isSimple) {
- this.pushSource("else { " + this.appendToBuffer("''") + " }");
+ this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
}
}
},
@@ -314,7 +345,7 @@ JavaScriptCompiler.prototype = {
appendEscaped: function() {
this.aliases.escapeExpression = 'this.escapeExpression';
- this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
+ this.pushSource(this.appendToBuffer(['escapeExpression(', this.popStack(), ')']));
},
// [getContext]
@@ -364,10 +395,10 @@ JavaScriptCompiler.prototype = {
// We want to ensure that zero and false are handled properly if the context (falsy flag)
// needs to have the special handling for these values.
if (!falsy) {
- return ' != null ? ' + lookup + ' : ' + current;
+ return [' != null ? ', lookup, ' : ', current];
} else {
// Otherwise we can use generic falsy handling
- return ' && ' + lookup;
+ return [' && ', lookup];
}
});
}
@@ -390,7 +421,7 @@ JavaScriptCompiler.prototype = {
var len = parts.length;
for (var i = 0; i < len; i++) {
this.replaceStack(function(current) {
- return ' && ' + this.nameLookup(current, parts[i], 'data');
+ return [' && ', this.nameLookup(current, parts[i], 'data')];
});
}
},
@@ -405,7 +436,7 @@ JavaScriptCompiler.prototype = {
resolvePossibleLambda: function() {
this.aliases.lambda = 'this.lambda';
- this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')');
+ this.push(['lambda(', this.popStack(), ', ', this.contextName(0), ')']);
},
// [pushStringParam]
@@ -453,14 +484,14 @@ JavaScriptCompiler.prototype = {
this.hash = this.hashes.pop();
if (this.trackIds) {
- this.push('{' + hash.ids.join(',') + '}');
+ this.push(this.objectLiteral(hash.ids));
}
if (this.stringParams) {
- this.push('{' + hash.contexts.join(',') + '}');
- this.push('{' + hash.types.join(',') + '}');
+ this.push(this.objectLiteral(hash.contexts));
+ this.push(this.objectLiteral(hash.types));
}
- this.push('{\n ' + hash.values.join(',\n ') + '\n }');
+ this.push(this.objectLiteral(hash.values));
},
// [pushString]
@@ -473,17 +504,6 @@ JavaScriptCompiler.prototype = {
this.pushStackLiteral(this.quotedString(string));
},
- // [push]
- //
- // On stack, before: ...
- // On stack, after: expr, ...
- //
- // Push an expression onto the stack
- push: function(expr) {
- this.inlineStack.push(expr);
- return expr;
- },
-
// [pushLiteral]
//
// On stack, before: ...
@@ -526,9 +546,13 @@ JavaScriptCompiler.prototype = {
var nonHelper = this.popStack();
var helper = this.setupHelper(paramSize, name);
+ var simple = isSimple ? [helper.name, ' || '] : '';
- var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing';
- this.push('((' + lookup + ').call(' + helper.callParams + '))');
+ this.push(
+ this.source.functionCall(
+ ['('].concat(simple, nonHelper, ' || ', 'helperMissing)'),
+ 'call',
+ helper.callParams));
},
// [invokeKnownHelper]
@@ -540,7 +564,7 @@ JavaScriptCompiler.prototype = {
// so a `helperMissing` fallback is not required.
invokeKnownHelper: function(paramSize, name) {
var helper = this.setupHelper(paramSize, name);
- this.push(helper.name + ".call(" + helper.callParams + ")");
+ this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
},
// [invokeAmbiguous]
@@ -567,10 +591,12 @@ JavaScriptCompiler.prototype = {
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
- this.push(
- '((helper = (helper = ' + helperName + ' || ' + nonHelper + ') != null ? helper : helperMissing'
- + (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),'
- + '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))');
+ this.push([
+ '((helper = (helper = ', helperName, ' || ', nonHelper, ') != null ? helper : helperMissing',
+ (helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
+ '(typeof helper === functionType ? ',
+ this.source.functionCall('helper','call', helper.callParams), ' : helper))'
+ ]);
},
// [invokePartial]
@@ -592,7 +618,7 @@ JavaScriptCompiler.prototype = {
params.push('depths');
}
- this.push("this.invokePartial(" + params.join(", ") + ")");
+ this.push(this.source.functionCall('this.invokePartial', '', params));
},
// [assignToHash]
@@ -617,15 +643,15 @@ JavaScriptCompiler.prototype = {
var hash = this.hash;
if (context) {
- hash.contexts.push("'" + key + "': " + context);
+ hash.contexts[key] = context;
}
if (type) {
- hash.types.push("'" + key + "': " + type);
+ hash.types[key] = type;
}
if (id) {
- hash.ids.push("'" + key + "': " + id);
+ hash.ids[key] = id;
}
- hash.values.push("'" + key + "': (" + value + ")");
+ hash.values[key] = value;
},
pushId: function(type, name) {
@@ -697,13 +723,23 @@ JavaScriptCompiler.prototype = {
}
},
+ push: function(expr) {
+ if (!(expr instanceof Literal)) {
+ expr = this.source.wrap(expr);
+ }
+
+ this.inlineStack.push(expr);
+ return expr;
+ },
+
pushStackLiteral: function(item) {
this.push(new Literal(item));
},
pushSource: function(source) {
if (this.pendingContent) {
- this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
+ this.source.push(
+ this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
this.pendingContent = undefined;
}
@@ -713,7 +749,7 @@ JavaScriptCompiler.prototype = {
},
replaceStack: function(callback) {
- var prefix = '',
+ var prefix = ['('],
inline = this.isInline(),
stack,
createdStack,
@@ -729,14 +765,15 @@ JavaScriptCompiler.prototype = {
if (top instanceof Literal) {
// Literals do not need to be inlined
- prefix = stack = top.value;
+ stack = [top.value];
+ prefix = ['(', stack];
usedLiteral = true;
} else {
// Get or create the current stack name for use by the inline
createdStack = true;
var name = this.incrStack();
- prefix = '(' + this.push(name) + ' = ' + top + ')';
+ prefix = ['((', this.push(name), ' = ', top, ')'];
stack = this.topStack();
}
@@ -748,7 +785,7 @@ JavaScriptCompiler.prototype = {
if (createdStack) {
this.stackSlot--;
}
- this.push('(' + prefix + item + ')');
+ this.push(prefix.concat(item, ')'));
},
incrStack: function() {
@@ -769,7 +806,7 @@ JavaScriptCompiler.prototype = {
this.compileStack.push(entry);
} else {
var stack = this.incrStack();
- this.pushSource(stack + " = " + entry + ";");
+ this.pushSource([stack, ' = ', entry, ';']);
this.compileStack.push(stack);
}
}
@@ -817,25 +854,11 @@ JavaScriptCompiler.prototype = {
},
quotedString: function(str) {
- return '"' + str
- .replace(/\\/g, '\\\\')
- .replace(/"/g, '\\"')
- .replace(/\n/g, '\\n')
- .replace(/\r/g, '\\r')
- .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
- .replace(/\u2029/g, '\\u2029') + '"';
+ return this.source.quotedString(str);
},
objectLiteral: function(obj) {
- var pairs = [];
-
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- pairs.push(this.quotedString(key) + ':' + obj[key]);
- }
- }
-
- return '{' + pairs.join(',') + '}';
+ return this.source.objectLiteral(obj);
},
setupHelper: function(paramSize, name, blockHelper) {
@@ -847,7 +870,7 @@ JavaScriptCompiler.prototype = {
params: params,
paramsInit: paramsInit,
name: foundHelper,
- callParams: [this.contextName(0)].concat(params).join(", ")
+ callParams: [this.contextName(0)].concat(params)
};
},
@@ -892,11 +915,11 @@ JavaScriptCompiler.prototype = {
}
if (this.trackIds) {
- options.ids = "[" + ids.join(",") + "]";
+ options.ids = this.source.generateArray(ids);
}
if (this.stringParams) {
- options.types = "[" + types.join(",") + "]";
- options.contexts = "[" + contexts.join(",") + "]";
+ options.types = this.source.generateArray(types);
+ options.contexts = this.source.generateArray(contexts);
}
if (this.options.data) {
@@ -907,7 +930,7 @@ JavaScriptCompiler.prototype = {
if (useRegister) {
this.useRegister('options');
params.push('options');
- return 'options=' + options;
+ return ['options=', options];
} else {
params.push(options);
return '';
@@ -915,6 +938,7 @@ JavaScriptCompiler.prototype = {
}
};
+
var reservedWords = (
"break else new var" +
" case finally return void" +