Skip to content

Commit

Permalink
Merge pull request #692 from fivetanley/line-numbers
Browse files Browse the repository at this point in the history
add line numbers to nodes when parsing
  • Loading branch information
wycats committed Dec 29, 2013
2 parents 1c0614b + e187805 commit 6c2137a
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 42 deletions.
70 changes: 55 additions & 15 deletions lib/handlebars/compiler/ast.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
import Exception from "../exception";

function LocationInfo(locInfo){
locInfo = locInfo || {};
this.firstLine = locInfo.first_line;
this.firstColumn = locInfo.first_column;
this.lastColumn = locInfo.last_column;
this.lastLine = locInfo.last_line;
}

var AST = {
ProgramNode: function(statements, inverseStrip, inverse) {
ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
var inverseLocationInfo, firstInverseNode;
if (arguments.length === 3) {
inverse = null;
locInfo = arguments[arguments.length - 1];
} else if (arguments.length === 2 ) {
locInfo = arguments[1];
}
LocationInfo.call(this, locInfo);
this.type = "program";
this.statements = statements;
this.strip = {};

if(inverse) {
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
firstInverseNode = inverse[0];
if (firstInverseNode) {
inverseLocationInfo = {
first_line: firstInverseNode.firstLine,
last_line: firstInverseNode.lastLine,
last_column: firstInverseNode.lastColumn,
first_column: firstInverseNode.firstColumn
};
this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
} else {
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
}
this.strip.right = inverseStrip.left;
} else if (inverseStrip) {
this.strip.left = inverseStrip.right;
}
},

MustacheNode: function(rawParams, hash, open, strip) {
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "mustache";
this.hash = hash;
this.strip = strip;
Expand Down Expand Up @@ -45,19 +73,22 @@ var AST = {
// pass or at runtime.
},

PartialNode: function(partialName, context, strip) {
PartialNode: function(partialName, context, strip, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "partial";
this.partialName = partialName;
this.context = context;
this.strip = strip;
},

BlockNode: function(mustache, program, inverse, close) {
BlockNode: function(mustache, program, inverse, close, locInfo) {
if(mustache.id.original !== close.path.original) {
throw new Exception(mustache.id.original + " doesn't match " + close.path.original);
}

this.type = "block";
LocationInfo.call(this, locInfo);

this.type = 'block';
this.mustache = mustache;
this.program = program;
this.inverse = inverse;
Expand All @@ -75,17 +106,20 @@ var AST = {
}
},

ContentNode: function(string) {
ContentNode: function(string, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "content";
this.string = string;
},

HashNode: function(pairs) {
HashNode: function(pairs, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "hash";
this.pairs = pairs;
},

IdNode: function(parts) {
IdNode: function(parts, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "ID";

var original = "",
Expand Down Expand Up @@ -116,37 +150,43 @@ var AST = {
this.stringModeValue = this.string;
},

PartialNameNode: function(name) {
PartialNameNode: function(name, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "PARTIAL_NAME";
this.name = name.original;
},

DataNode: function(id) {
DataNode: function(id, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "DATA";
this.id = id;
},

StringNode: function(string) {
StringNode: function(string, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "STRING";
this.original =
this.string =
this.stringModeValue = string;
},

IntegerNode: function(integer) {
IntegerNode: function(integer, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "INTEGER";
this.original =
this.integer = integer;
this.stringModeValue = Number(integer);
},

BooleanNode: function(bool) {
BooleanNode: function(bool, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "BOOLEAN";
this.bool = bool;
this.stringModeValue = bool === "true";
},

CommentNode: function(comment) {
CommentNode: function(comment, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "comment";
this.comment = comment;
}
Expand Down
186 changes: 185 additions & 1 deletion spec/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ describe('ast', function() {
return;
}

var LOCATION_INFO = {
last_line: 0,
first_line: 0,
first_column: 0,
last_column: 0
};

function testLocationInfoStorage(node){
var properties = [ 'firstLine', 'lastLine', 'firstColumn', 'lastColumn' ],
property,
propertiesLen = properties.length,
i;

for (i = 0; i < propertiesLen; i++){
property = properties[0];
equals(node[property], 0);
}
}

describe('MustacheNode', function() {
function testEscape(open, expected) {
var mustache = new handlebarsEnv.AST.MustacheNode([{}], {}, open, false);
Expand All @@ -13,14 +32,15 @@ describe('ast', function() {
it('should store args', function() {
var id = {isSimple: true},
hash = {},
mustache = new handlebarsEnv.AST.MustacheNode([id, 'param1'], hash, '', false);
mustache = new handlebarsEnv.AST.MustacheNode([id, 'param1'], hash, '', false, LOCATION_INFO);
equals(mustache.type, 'mustache');
equals(mustache.hash, hash);
equals(mustache.escaped, true);
equals(mustache.id, id);
equals(mustache.params.length, 1);
equals(mustache.params[0], 'param1');
equals(!!mustache.isHelper, true);
testLocationInfoStorage(mustache);
});
it('should accept token for escape', function() {
testEscape('{{', true);
Expand Down Expand Up @@ -53,6 +73,17 @@ describe('ast', function() {
new handlebarsEnv.AST.BlockNode({id: {original: 'foo'}}, {}, {}, {path: {original: 'bar'}});
}, Handlebars.Exception, "foo doesn't match bar");
});

it('stores location info', function(){
var block = new handlebarsEnv.AST.BlockNode({strip: {}, id: {original: 'foo'}},
{strip: {}}, {strip: {}},
{
strip: {},
path: {original: 'foo'}
},
LOCATION_INFO);
testLocationInfoStorage(block);
});
});
describe('IdNode', function() {
it('should throw on invalid path', function() {
Expand All @@ -78,5 +109,158 @@ describe('ast', function() {
]);
}, Handlebars.Exception, "Invalid path: foothis");
});

it('stores location info', function(){
var idNode = new handlebarsEnv.AST.IdNode([], LOCATION_INFO);
testLocationInfoStorage(idNode);
});
});

describe("HashNode", function(){

it('stores location info', function(){
var hash = new handlebarsEnv.AST.HashNode([], LOCATION_INFO);
testLocationInfoStorage(hash);
});
});

describe("ContentNode", function(){

it('stores location info', function(){
var content = new handlebarsEnv.AST.ContentNode("HI", LOCATION_INFO);
testLocationInfoStorage(content);
});
});

describe("CommentNode", function(){

it('stores location info', function(){
var comment = new handlebarsEnv.AST.CommentNode("HI", LOCATION_INFO);
testLocationInfoStorage(comment);
});
});

describe("IntegerNode", function(){

it('stores location info', function(){
var integer = new handlebarsEnv.AST.IntegerNode("6", LOCATION_INFO);
testLocationInfoStorage(integer);
});
});

describe("StringNode", function(){

it('stores location info', function(){
var string = new handlebarsEnv.AST.StringNode("6", LOCATION_INFO);
testLocationInfoStorage(string);
});
});

describe("BooleanNode", function(){

it('stores location info', function(){
var bool = new handlebarsEnv.AST.BooleanNode("true", LOCATION_INFO);
testLocationInfoStorage(bool);
});
});

describe("DataNode", function(){

it('stores location info', function(){
var data = new handlebarsEnv.AST.DataNode("YES", LOCATION_INFO);
testLocationInfoStorage(data);
});
});

describe("PartialNameNode", function(){

it('stores location info', function(){
var pnn = new handlebarsEnv.AST.PartialNameNode({original: "YES"}, LOCATION_INFO);
testLocationInfoStorage(pnn);
});
});

describe("PartialNode", function(){

it('stores location info', function(){
var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, LOCATION_INFO);
testLocationInfoStorage(pn);
});
});
describe("ProgramNode", function(){

describe("storing location info", function(){
it("stores when `inverse` argument isn't passed", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
testLocationInfoStorage(pn);
});

it("stores when `inverse` or `stripInverse` arguments passed", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
testLocationInfoStorage(pn);

var clone = {
strip: {},
firstLine: 0,
lastLine: 0,
firstColumn: 0,
lastColumn: 0
};
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
testLocationInfoStorage(pn);

// Assert that the newly created ProgramNode has the same location
// information as the inverse
testLocationInfoStorage(pn.inverse);
});
});
});

describe("Line Numbers", function(){
var ast, statements;

function testColumns(node, firstLine, lastLine, firstColumn, lastColumn){
equals(node.firstLine, firstLine);
equals(node.lastLine, lastLine);
equals(node.firstColumn, firstColumn);
equals(node.lastColumn, lastColumn);
}

ast = Handlebars.parse("line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n" +
"line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}");
statements = ast.statements;

it('gets ContentNode line numbers', function(){
var contentNode = statements[0];
testColumns(contentNode, 1, 1, 0, 7);
});

it('gets MustacheNode line numbers', function(){
var mustacheNode = statements[1];
testColumns(mustacheNode, 1, 1, 7, 21);
});

it('gets line numbers correct when newlines appear', function(){
var secondContentNode = statements[2];
testColumns(secondContentNode, 1, 2, 21, 8);
});

it('gets MustacheNode line numbers correct across newlines', function(){
var secondMustacheNode = statements[3];
testColumns(secondMustacheNode, 2, 2, 8, 22);
});

it('gets the block helper information correct', function(){
var blockHelperNode = statements[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});

it('correctly records the line numbers of an inverse of a block helper', function(){
var blockHelperNode = statements[5],
inverse = blockHelperNode.inverse;

testColumns(inverse, 5, 6, 13, 0);
});
});
});

Loading

0 comments on commit 6c2137a

Please sign in to comment.