Skip to content

Commit 6e4e1f8

Browse files
committed
Include line info in compiler thrown exceptions
Fixes #636
1 parent 3c85720 commit 6e4e1f8

File tree

6 files changed

+41
-25
lines changed

6 files changed

+41
-25
lines changed

lib/handlebars/base.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function registerDefaultHelpers(instance) {
5353
if(arguments.length === 2) {
5454
return undefined;
5555
} else {
56-
throw new Error("Missing helper: '" + arg + "'");
56+
throw new Exception("Missing helper: '" + arg + "'");
5757
}
5858
});
5959

lib/handlebars/compiler/ast.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ var AST = {
106106
},
107107

108108
BlockNode: function(mustache, program, inverse, close, locInfo) {
109+
LocationInfo.call(this, locInfo);
110+
109111
if(mustache.sexpr.id.original !== close.path.original) {
110-
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original);
112+
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
111113
}
112114

113-
LocationInfo.call(this, locInfo);
114-
115115
this.type = 'block';
116116
this.mustache = mustache;
117117
this.program = program;
@@ -155,11 +155,16 @@ var AST = {
155155
original += (parts[i].separator || '') + part;
156156

157157
if (part === ".." || part === "." || part === "this") {
158-
if (dig.length > 0) { throw new Exception("Invalid path: " + original); }
159-
else if (part === "..") { depth++; }
160-
else { this.isScoped = true; }
158+
if (dig.length > 0) {
159+
throw new Exception("Invalid path: " + original, this);
160+
} else if (part === "..") {
161+
depth++;
162+
} else {
163+
this.isScoped = true;
164+
}
165+
} else {
166+
dig.push(part);
161167
}
162-
else { dig.push(part); }
163168
}
164169

165170
this.original = original;

lib/handlebars/compiler/compiler.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ Compiler.prototype = {
279279
if (this.options.knownHelpers[name]) {
280280
this.opcode('invokeKnownHelper', params.length, name);
281281
} else if (this.options.knownHelpersOnly) {
282-
throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
282+
throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
283283
} else {
284284
this.opcode('invokeHelper', params.length, name, sexpr.isRoot);
285285
}
@@ -316,7 +316,7 @@ Compiler.prototype = {
316316
DATA: function(data) {
317317
this.options.data = true;
318318
if (data.id.isScoped || data.id.depth) {
319-
throw new Exception('Scoped data references are not supported: ' + data.original);
319+
throw new Exception('Scoped data references are not supported: ' + data.original, data);
320320
}
321321

322322
this.opcode('lookupData');
@@ -350,7 +350,6 @@ Compiler.prototype = {
350350
},
351351

352352
addDepth: function(depth) {
353-
if(isNaN(depth)) { throw new Error("EWOT"); }
354353
if(depth === 0) { return; }
355354

356355
if(!this.depths[depth]) {

lib/handlebars/exception.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11

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

4-
function Exception(/* message */) {
5-
var tmp = Error.prototype.constructor.apply(this, arguments);
4+
function Exception(message, node) {
5+
var line;
6+
if (node && node.firstLine) {
7+
line = node.firstLine;
8+
9+
message += ' - ' + line + ':' + node.firstColumn;
10+
}
11+
12+
var tmp = Error.prototype.constructor.call(this, message);
613

714
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
815
for (var idx = 0; idx < errorProps.length; idx++) {
916
this[errorProps[idx]] = tmp[errorProps[idx]];
1017
}
18+
19+
if (line) {
20+
this.lineNumber = line;
21+
this.column = node.firstColumn;
22+
}
1123
}
1224

1325
Exception.prototype = new Error();

lib/handlebars/runtime.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ export function checkRevision(compilerInfo) {
1010
if (compilerRevision < currentRevision) {
1111
var runtimeVersions = REVISION_CHANGES[currentRevision],
1212
compilerVersions = REVISION_CHANGES[compilerRevision];
13-
throw new Error("Template was precompiled with an older version of Handlebars than the current runtime. "+
13+
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
1414
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
1515
} else {
1616
// Use the embedded version info since the runtime doesn't know about this revision yet
17-
throw new Error("Template was precompiled with a newer version of Handlebars than the current runtime. "+
17+
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
1818
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
1919
}
2020
}
@@ -24,7 +24,7 @@ export function checkRevision(compilerInfo) {
2424

2525
export function template(templateSpec, env) {
2626
if (!env) {
27-
throw new Error("No environment passed to template");
27+
throw new Exception("No environment passed to template");
2828
}
2929

3030
// Note: Using env.VM references rather than local var references throughout this section to allow

spec/ast.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ describe('ast', function() {
7878
shouldThrow(function() {
7979
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
8080
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
81-
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
82-
}, Handlebars.Exception, "foo doesn't match bar");
81+
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}}, {first_line: 2, first_column: 2});
82+
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
8383
});
8484

8585
it('stores location info', function(){
@@ -102,22 +102,22 @@ describe('ast', function() {
102102
{part: 'foo'},
103103
{part: '..'},
104104
{part: 'bar'}
105-
]);
106-
}, Handlebars.Exception, "Invalid path: foo..");
105+
], {first_line: 1, first_column: 1});
106+
}, Handlebars.Exception, "Invalid path: foo.. - 1:1");
107107
shouldThrow(function() {
108108
new handlebarsEnv.AST.IdNode([
109109
{part: 'foo'},
110110
{part: '.'},
111111
{part: 'bar'}
112-
]);
113-
}, Handlebars.Exception, "Invalid path: foo.");
112+
], {first_line: 1, first_column: 1});
113+
}, Handlebars.Exception, "Invalid path: foo. - 1:1");
114114
shouldThrow(function() {
115115
new handlebarsEnv.AST.IdNode([
116116
{part: 'foo'},
117117
{part: 'this'},
118118
{part: 'bar'}
119-
]);
120-
}, Handlebars.Exception, "Invalid path: foothis");
119+
], {first_line: 1, first_column: 1});
120+
}, Handlebars.Exception, "Invalid path: foothis - 1:1");
121121
});
122122

123123
it('stores location info', function(){
@@ -216,7 +216,7 @@ describe('ast', function() {
216216
firstColumn: 0,
217217
lastColumn: 0
218218
};
219-
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
219+
pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
220220
testLocationInfoStorage(pn);
221221

222222
// Assert that the newly created ProgramNode has the same location

0 commit comments

Comments
 (0)