Merge pull request #692 from fivetanley/line-numbers
add line numbers to nodes when parsing
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user