add line numbers to nodes when parsing

closes #691
This commit is contained in:
Stanley Stuart
2013-12-28 18:55:36 -06:00
parent 1c0614bd88
commit e1878050b5
3 changed files with 266 additions and 42 deletions
+55 -15
View File
@@ -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;
@@ -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;
@@ -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 = "",
@@ -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;
}
+185 -1
View File
@@ -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);
@@ -13,7 +32,7 @@ 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);
@@ -21,6 +40,7 @@ describe('ast', function() {
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);
@@ -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() {
@@ -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);
});
});
});
+26 -26
View File
@@ -16,17 +16,17 @@ function stripFlags(open, close) {
%%
root
: statements EOF { return new yy.ProgramNode($1); }
| EOF { return new yy.ProgramNode([]); }
: statements EOF { return new yy.ProgramNode($1, @$); }
| EOF { return new yy.ProgramNode([], @$); }
;
program
: simpleInverse statements -> new yy.ProgramNode([], $1, $2)
| statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3)
| statements simpleInverse -> new yy.ProgramNode($1, $2, [])
| statements -> new yy.ProgramNode($1)
| simpleInverse -> new yy.ProgramNode([])
| "" -> new yy.ProgramNode([])
: simpleInverse statements -> new yy.ProgramNode([], $1, $2, @$)
| statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3, @$)
| statements simpleInverse -> new yy.ProgramNode($1, $2, [], @$)
| statements -> new yy.ProgramNode($1, @$)
| simpleInverse -> new yy.ProgramNode([], @$)
| "" -> new yy.ProgramNode([], @$)
;
statements
@@ -35,20 +35,20 @@ statements
;
statement
: openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3)
| openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3)
: openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3, @$)
| openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3, @$)
| mustache -> $1
| partial -> $1
| CONTENT -> new yy.ContentNode($1)
| COMMENT -> new yy.CommentNode($1)
| CONTENT -> new yy.ContentNode($1, @$)
| COMMENT -> new yy.CommentNode($1, @$)
;
openBlock
: OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3))
: OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3), @$)
;
openInverse
: OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3))
: OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3), @$)
;
closeBlock
@@ -58,13 +58,13 @@ closeBlock
mustache
// Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node.
// This also allows for handler unification as all mustache node instances can utilize the same handler
: OPEN inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3))
| OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3))
: OPEN inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3), @$)
| OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3), @$)
;
partial
: OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3, stripFlags($1, $4))
: OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3, stripFlags($1, $4), @$)
;
simpleInverse
@@ -78,14 +78,14 @@ inMustache
param
: path -> $1
| STRING -> new yy.StringNode($1)
| INTEGER -> new yy.IntegerNode($1)
| BOOLEAN -> new yy.BooleanNode($1)
| STRING -> new yy.StringNode($1, @$)
| INTEGER -> new yy.IntegerNode($1, @$)
| BOOLEAN -> new yy.BooleanNode($1, @$)
| dataName -> $1
;
hash
: hashSegment+ -> new yy.HashNode($1)
: hashSegment+ -> new yy.HashNode($1, @$)
;
hashSegment
@@ -93,17 +93,17 @@ hashSegment
;
partialName
: path -> new yy.PartialNameNode($1)
| STRING -> new yy.PartialNameNode(new yy.StringNode($1))
| INTEGER -> new yy.PartialNameNode(new yy.IntegerNode($1))
: path -> new yy.PartialNameNode($1, @$)
| STRING -> new yy.PartialNameNode(new yy.StringNode($1, @$), @$)
| INTEGER -> new yy.PartialNameNode(new yy.IntegerNode($1, @$))
;
dataName
: DATA path -> new yy.DataNode($2)
: DATA path -> new yy.DataNode($2, @$)
;
path
: pathSegments -> new yy.IdNode($1)
: pathSegments -> new yy.IdNode($1, @$)
;
pathSegments