summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkpdecker <kpdecker@gmail.com>2014-11-06 09:56:00 -0600
committerkpdecker <kpdecker@gmail.com>2014-11-08 17:50:01 -0600
commit3ee0682247a1be883810d7251a75a95a5aa7e943 (patch)
treec90a6ca50f4d4f3fc2dbf1982ff450ec3221a256
parent249f559104cb7f85736e7e83e38ccc67b9b84bf6 (diff)
downloadhandlebars.js-3ee0682247a1be883810d7251a75a95a5aa7e943.zip
handlebars.js-3ee0682247a1be883810d7251a75a95a5aa7e943.tar.gz
handlebars.js-3ee0682247a1be883810d7251a75a95a5aa7e943.tar.bz2
Generate source maps
Allow the precompiler to generate source maps when the srcFile parameter is passed. This refactors large chunks of the code generation pipeline, allowing metadata to be associated with code chunks as well as breaking out much of the code generation logic into a separate helper.
-rw-r--r--lib/handlebars/compiler/code-gen.js151
-rw-r--r--lib/handlebars/compiler/javascript-compiler.js224
-rw-r--r--package.json3
-rw-r--r--spec/env/browser.js2
-rw-r--r--spec/expected/empty.amd.js2
-rw-r--r--spec/helpers.js4
-rw-r--r--spec/javascript-compiler.js2
-rw-r--r--spec/source-map.js45
8 files changed, 330 insertions, 103 deletions
diff --git a/lib/handlebars/compiler/code-gen.js b/lib/handlebars/compiler/code-gen.js
new file mode 100644
index 0000000..7d1b4ca
--- /dev/null
+++ b/lib/handlebars/compiler/code-gen.js
@@ -0,0 +1,151 @@
+import {isArray} from "../utils";
+
+try {
+ var SourceMap = require('source-map'),
+ SourceNode = SourceMap.SourceNode;
+} catch (err) {
+ /* istanbul ignore next: tested but not covered in istanbul due to dist build */
+ SourceNode = function(line, column, srcFile, chunks) {
+ this.src = '';
+ if (chunks) {
+ this.add(chunks);
+ }
+ };
+ /* istanbul ignore next */
+ SourceNode.prototype = {
+ add: function(chunks) {
+ if (isArray(chunks)) {
+ chunks = chunks.join('');
+ }
+ this.src += chunks;
+ },
+ prepend: function(chunks) {
+ if (isArray(chunks)) {
+ chunks = chunks.join('');
+ }
+ this.src = chunks + this.src;
+ },
+ toStringWithSourceMap: function() {
+ return {code: this.toString()};
+ },
+ toString: function() {
+ return this.src;
+ }
+ };
+}
+
+
+function castChunk(chunk, codeGen, loc) {
+ if (isArray(chunk)) {
+ var ret = [];
+
+ for (var i = 0, len = chunk.length; i < len; i++) {
+ ret.push(codeGen.wrap(chunk[i], loc));
+ }
+ return ret;
+ } else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
+ // Handle primitives that the SourceNode will throw up on
+ return chunk+'';
+ }
+ return chunk;
+}
+
+
+function CodeGen(srcFile) {
+ this.srcFile = srcFile;
+ this.source = [];
+}
+
+CodeGen.prototype = {
+ prepend: function(source, loc) {
+ this.source.unshift(this.wrap(source, loc));
+ },
+ push: function(source, loc) {
+ this.source.push(this.wrap(source, loc));
+ },
+
+ merge: function() {
+ var source = this.empty();
+ this.each(function(line) {
+ source.add([' ', line, '\n']);
+ });
+ return source;
+ },
+
+ each: function(iter) {
+ for (var i = 0, len = this.source.length; i < len; i++) {
+ iter(this.source[i]);
+ }
+ },
+
+ empty: function(loc) {
+ loc = loc || this.currentLocation || {};
+ return new SourceNode(loc.firstLine, loc.firstColumn, this.srcFile);
+ },
+ wrap: function(chunk, loc) {
+ if (chunk instanceof SourceNode) {
+ return chunk;
+ }
+
+ loc = loc || this.currentLocation || {};
+ chunk = castChunk(chunk, this, loc);
+
+ return new SourceNode(loc.firstLine, loc.firstColumn, this.srcFile, chunk);
+ },
+
+ functionCall: function(fn, type, params) {
+ params = this.generateList(params);
+ return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
+ },
+
+ 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') + '"';
+ },
+
+ objectLiteral: function(obj) {
+ var pairs = [];
+
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ pairs.push([this.quotedString(key), ':', castChunk(obj[key], this)]);
+ }
+ }
+
+ var ret = this.generateList(pairs);
+ ret.prepend('{');
+ ret.add('}');
+ return ret;
+ },
+
+
+ generateList: function(entries, loc) {
+ var ret = this.empty(loc);
+
+ for (var i = 0, len = entries.length; i < len; i++) {
+ if (i) {
+ ret.add(',');
+ }
+
+ ret.add(castChunk(entries[i], this, loc));
+ }
+
+ return ret;
+ },
+
+ generateArray: function(entries, loc) {
+ var ret = this.generateList(entries, loc);
+ ret.prepend('[');
+ ret.add(']');
+
+ return ret;
+ }
+};
+
+export default CodeGen;
+
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" +
diff --git a/package.json b/package.json
index 6ed9a55..5de52c0 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,8 @@
"node": ">=0.4.7"
},
"dependencies": {
- "optimist": "~0.3"
+ "optimist": "~0.3",
+ "source-map": "^0.1.40"
},
"optionalDependencies": {
"uglify-js": "~2.3"
diff --git a/spec/env/browser.js b/spec/env/browser.js
index bcf4259..a0ce243 100644
--- a/spec/env/browser.js
+++ b/spec/env/browser.js
@@ -9,6 +9,8 @@ global.Handlebars = undefined;
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
global.CompilerContext = {
+ browser: true,
+
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
diff --git a/spec/expected/empty.amd.js b/spec/expected/empty.amd.js
index 1cf3298..852733b 100644
--- a/spec/expected/empty.amd.js
+++ b/spec/expected/empty.amd.js
@@ -1,6 +1,6 @@
define(['handlebars.runtime'], function(Handlebars) {
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
return templates['empty'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
- return "";
+ return "";
},"useData":true});
});
diff --git a/spec/helpers.js b/spec/helpers.js
index e604e91..f23ee14 100644
--- a/spec/helpers.js
+++ b/spec/helpers.js
@@ -60,6 +60,10 @@ describe('helpers', function() {
}};
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
});
+ it('helper returning undefined value', function() {
+ shouldCompileTo(' {{nothere}}', [{}, {nothere: function() {}}], ' ');
+ shouldCompileTo(' {{#nothere}}{{/nothere}}', [{}, {nothere: function() {}}], ' ');
+ });
it("block helper", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
diff --git a/spec/javascript-compiler.js b/spec/javascript-compiler.js
index 1605868..4260567 100644
--- a/spec/javascript-compiler.js
+++ b/spec/javascript-compiler.js
@@ -63,7 +63,7 @@ describe('javascript-compiler api', function() {
});
it('should allow append buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
- return $superAppend.call(this, string + ' + "_foo"');
+ return $superAppend.call(this, [string, ' + "_foo"']);
};
shouldCompileTo("{{foo}}", { foo: "food" }, "food_foo");
});
diff --git a/spec/source-map.js b/spec/source-map.js
new file mode 100644
index 0000000..4d3fa3e
--- /dev/null
+++ b/spec/source-map.js
@@ -0,0 +1,45 @@
+/*global CompilerContext, Handlebars */
+var SourceMap = require('source-map'),
+ SourceMapConsumer = SourceMap.SourceMapConsumer;
+
+describe('source-map', function() {
+ if (!Handlebars.precompile) {
+ return;
+ }
+
+ it('should safely include source map info', function() {
+ var template = Handlebars.precompile('{{hello}}', {destName: 'dest.js', srcName: 'src.hbs'});
+
+ equal(!!template.code, true);
+ equal(!!template.map, !CompilerContext.browser);
+ });
+ it('should map source properly', function() {
+ var source = ' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
+ template = Handlebars.precompile(source, {destName: 'dest.js', srcName: 'src.hbs'});
+
+ if (template.map) {
+ var consumer = new SourceMapConsumer(template.map),
+ lines = template.code.split('\n'),
+ srcLines = source.split('\n'),
+
+ generated = grepLine('" b"', lines),
+ source = grepLine(' b', srcLines);
+
+ var mapped = consumer.originalPositionFor(generated);
+ equal(mapped.line, source.line);
+ equal(mapped.column, source.column);
+ }
+ });
+});
+
+function grepLine(token, lines) {
+ for (var i = 0; i < lines.length; i++) {
+ var column = lines[i].indexOf(token);
+ if (column >= 0) {
+ return {
+ line: i+1,
+ column: column
+ };
+ }
+ }
+}