Skip to content

Commit

Permalink
Include line info in compiler thrown exceptions
Browse files Browse the repository at this point in the history
Fixes #636
  • Loading branch information
kpdecker committed Jan 2, 2014
1 parent 3c85720 commit 6e4e1f8
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 25 deletions.
2 changes: 1 addition & 1 deletion lib/handlebars/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function registerDefaultHelpers(instance) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Missing helper: '" + arg + "'");
throw new Exception("Missing helper: '" + arg + "'");
}
});

Expand Down
19 changes: 12 additions & 7 deletions lib/handlebars/compiler/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ var AST = {
},

BlockNode: function(mustache, program, inverse, close, locInfo) {
LocationInfo.call(this, locInfo);

if(mustache.sexpr.id.original !== close.path.original) {
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original);
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
}

LocationInfo.call(this, locInfo);

this.type = 'block';
this.mustache = mustache;
this.program = program;
Expand Down Expand Up @@ -155,11 +155,16 @@ var AST = {
original += (parts[i].separator || '') + part;

if (part === ".." || part === "." || part === "this") {
if (dig.length > 0) { throw new Exception("Invalid path: " + original); }
else if (part === "..") { depth++; }
else { this.isScoped = true; }
if (dig.length > 0) {
throw new Exception("Invalid path: " + original, this);
} else if (part === "..") {
depth++;
} else {
this.isScoped = true;
}
} else {
dig.push(part);
}
else { dig.push(part); }
}

this.original = original;
Expand Down
5 changes: 2 additions & 3 deletions lib/handlebars/compiler/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ Compiler.prototype = {
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) {
throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
} else {
this.opcode('invokeHelper', params.length, name, sexpr.isRoot);
}
Expand Down Expand Up @@ -316,7 +316,7 @@ Compiler.prototype = {
DATA: function(data) {
this.options.data = true;
if (data.id.isScoped || data.id.depth) {
throw new Exception('Scoped data references are not supported: ' + data.original);
throw new Exception('Scoped data references are not supported: ' + data.original, data);
}

this.opcode('lookupData');
Expand Down Expand Up @@ -350,7 +350,6 @@ Compiler.prototype = {
},

addDepth: function(depth) {
if(isNaN(depth)) { throw new Error("EWOT"); }
if(depth === 0) { return; }

if(!this.depths[depth]) {
Expand Down
16 changes: 14 additions & 2 deletions lib/handlebars/exception.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@

var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];

function Exception(/* message */) {
var tmp = Error.prototype.constructor.apply(this, arguments);
function Exception(message, node) {
var line;
if (node && node.firstLine) {
line = node.firstLine;

message += ' - ' + line + ':' + node.firstColumn;
}

var tmp = Error.prototype.constructor.call(this, message);

// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (var idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}

if (line) {
this.lineNumber = line;
this.column = node.firstColumn;
}
}

Exception.prototype = new Error();
Expand Down
6 changes: 3 additions & 3 deletions lib/handlebars/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ export function checkRevision(compilerInfo) {
if (compilerRevision < currentRevision) {
var runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Error("Template was precompiled with an older version of Handlebars than the current runtime. "+
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Error("Template was precompiled with a newer version of Handlebars than the current runtime. "+
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
}
}
Expand All @@ -24,7 +24,7 @@ export function checkRevision(compilerInfo) {

export function template(templateSpec, env) {
if (!env) {
throw new Error("No environment passed to template");
throw new Exception("No environment passed to template");
}

// Note: Using env.VM references rather than local var references throughout this section to allow
Expand Down
18 changes: 9 additions & 9 deletions spec/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ describe('ast', function() {
shouldThrow(function() {
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
}, Handlebars.Exception, "foo doesn't match bar");
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}}, {first_line: 2, first_column: 2});
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
});

it('stores location info', function(){
Expand All @@ -102,22 +102,22 @@ describe('ast', function() {
{part: 'foo'},
{part: '..'},
{part: 'bar'}
]);
}, Handlebars.Exception, "Invalid path: foo..");
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foo.. - 1:1");
shouldThrow(function() {
new handlebarsEnv.AST.IdNode([
{part: 'foo'},
{part: '.'},
{part: 'bar'}
]);
}, Handlebars.Exception, "Invalid path: foo.");
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foo. - 1:1");
shouldThrow(function() {
new handlebarsEnv.AST.IdNode([
{part: 'foo'},
{part: 'this'},
{part: 'bar'}
]);
}, Handlebars.Exception, "Invalid path: foothis");
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foothis - 1:1");
});

it('stores location info', function(){
Expand Down Expand Up @@ -216,7 +216,7 @@ describe('ast', function() {
firstColumn: 0,
lastColumn: 0
};
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
testLocationInfoStorage(pn);

// Assert that the newly created ProgramNode has the same location
Expand Down

0 comments on commit 6e4e1f8

Please sign in to comment.