From af88758381cdd289b71b682db68de674d42ac888 Mon Sep 17 00:00:00 2001 From: wolfgarnet Date: Thu, 3 Dec 2015 14:09:46 +0100 Subject: [PATCH] [#148] Storing comments to a commentmap --- ast/comments.go | 106 ++++ ast/comments_test.go | 61 ++ parser/comments_test.go | 1278 +++++++++++++++++++++++++++++++++++++++ parser/expression.go | 220 ++++++- parser/lexer.go | 47 +- parser/lexer_test.go | 14 +- parser/parser.go | 84 ++- parser/statement.go | 247 +++++++- 8 files changed, 2007 insertions(+), 50 deletions(-) create mode 100644 ast/comments.go create mode 100644 ast/comments_test.go create mode 100644 parser/comments_test.go diff --git a/ast/comments.go b/ast/comments.go new file mode 100644 index 0000000..5e40bd4 --- /dev/null +++ b/ast/comments.go @@ -0,0 +1,106 @@ +package ast + +import ( + "fmt" + "github.com/robertkrimen/otto/file" + "reflect" +) + +// CommentPosition determines where the comment is in a given context +type CommentPosition int + +const ( + _ CommentPosition = iota + LEADING // Before the pertinent expression + TRAILING // After the pertinent expression + KEY // After a key or keyword + COLON // After a colon in a field declaration + FINAL // Final comments in a block, not belonging to a specific expression + TBD +) + +// Comment contains the data of the comment +type Comment struct { + Begin file.Idx + Text string + Position CommentPosition +} + +// String returns a stringified version of the position +func (cp CommentPosition) String() string { + switch cp { + case LEADING: + return "Leading" + case TRAILING: + return "Trailing" + case KEY: + return "Key" + case COLON: + return "Colon" + case FINAL: + return "Final" + + default: + return "???" + } +} + +// String returns a stringified version of the comment +func (c Comment) String() string { + return fmt.Sprintf("Comment: %v", c.Text) +} + +// CommentMap is the data structure where all found comments are stored +type CommentMap map[Node][]*Comment + +// AddComment adds a single comment to the map +func (cm CommentMap) AddComment(node Node, comment *Comment) { + list := cm[node] + list = append(list, comment) + + cm[node] = list +} + +// AddComments adds a slice of comments, given a node and an updated position +func (cm CommentMap) AddComments(node Node, comments []*Comment, position CommentPosition) { + for _, comment := range comments { + comment.Position = position + cm.AddComment(node, comment) + } +} + +// Size returns the size of the map +func (cm CommentMap) Size() int { + size := 0 + for _, comments := range cm { + size += len(comments) + } + + return size +} + +// MoveComments moves comments with a given position from a node to another +func (cm CommentMap) MoveComments(from, to Node, position CommentPosition) { + for i, c := range cm[from] { + if c.Position == position { + cm.AddComment(to, c) + + // Remove the comment from the "from" slice + cm[from][i] = cm[from][len(cm[from])-1] + cm[from][len(cm[from])-1] = nil + cm[from] = cm[from][:len(cm[from])-1] + } + } +} + +// Display displays the map +func (cm CommentMap) Display() { + fmt.Printf("-- Displaying Comment Map --\n") + for k, v := range cm { + fmt.Printf("Node(%v==%v):\n", k, reflect.TypeOf(k)) + for i, c := range v { + fmt.Printf(" * [%v] \"%v\" (position:%v, begin:%v)\n", i, c.Text, c.Position, c.Begin) + } + } + fmt.Printf("----------------------------\n") +} diff --git a/ast/comments_test.go b/ast/comments_test.go new file mode 100644 index 0000000..dac1810 --- /dev/null +++ b/ast/comments_test.go @@ -0,0 +1,61 @@ +package ast + +import ( + "github.com/robertkrimen/otto/file" + "testing" +) + +func TestCommentMap(t *testing.T) { + statement := &EmptyStatement{file.Idx(1)} + comment := &Comment{1, "test", LEADING} + + cm := CommentMap{} + cm.AddComment(statement, comment) + + if cm.Size() != 1 { + t.Errorf("The number of comments is %v, not 1", cm.Size()) + } + + if len(cm[statement]) != 1 { + t.Errorf("The number of comments is %v, not 1", cm.Size()) + } + + if cm[statement][0].Text != "test" { + t.Errorf("The text is %v, not \"test\"", cm[statement][0].Text) + } +} + +func TestCommentMap_move(t *testing.T) { + statement1 := &EmptyStatement{file.Idx(1)} + statement2 := &EmptyStatement{file.Idx(2)} + comment := &Comment{1, "test", LEADING} + + cm := CommentMap{} + cm.AddComment(statement1, comment) + + if cm.Size() != 1 { + t.Errorf("The number of comments is %v, not 1", cm.Size()) + } + + if len(cm[statement1]) != 1 { + t.Errorf("The number of comments is %v, not 1", cm.Size()) + } + + if len(cm[statement2]) != 0 { + t.Errorf("The number of comments is %v, not 0", cm.Size()) + } + + cm.MoveComments(statement1, statement2, LEADING) + + if cm.Size() != 1 { + t.Errorf("The number of comments is %v, not 1", cm.Size()) + } + + if len(cm[statement2]) != 1 { + t.Errorf("The number of comments is %v, not 1", cm.Size()) + } + + if len(cm[statement1]) != 0 { + t.Errorf("The number of comments is %v, not 0", cm.Size()) + } +} diff --git a/parser/comments_test.go b/parser/comments_test.go new file mode 100644 index 0000000..1aee3bf --- /dev/null +++ b/parser/comments_test.go @@ -0,0 +1,1278 @@ +package parser + +import ( + "fmt" + "github.com/robertkrimen/otto/ast" + "reflect" + "testing" +) + +func checkComments(actual []*ast.Comment, expected []string, position ast.CommentPosition) error { + if len(actual) != len(expected) { + return fmt.Errorf("The number of comments is not correct. %v != %v", len(actual), len(expected)) + } + + for i, v := range actual { + if v.Text != expected[i] { + return fmt.Errorf("Comments do not match. \"%v\" != \"%v\"\n", v.Text, expected[i]) + } + if v.Position != position { + return fmt.Errorf("The comment is not %v, was %v\n", position, v.Position) + } + } + + return nil +} + +func displayStatements(statements []ast.Statement) { + fmt.Printf("Printing statements (%v)\n", len(statements)) + for k, v := range statements { + fmt.Printf("Type of line %v: %v\n", k, reflect.TypeOf(v)) + } +} + +func TestParser_comments(t *testing.T) { + tt(t, func() { + test := func(source string, chk interface{}) (*_parser, *ast.Program) { + parser, program, err := testParse(source) + is(firstErr(err), chk) + + // Check unresolved comments + is(len(parser.comments), 0) + return parser, program + } + + var err error + var parser *_parser + var program *ast.Program + + parser, program = test("q=2;// Hej\nv = 0", nil) + is(len(program.Body), 2) + err = checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING) + is(err, nil) + err = checkComments((*parser.commentMap)[program.Body[1]], []string{" Hej"}, ast.LEADING) + is(err, nil) + + // Assignment + parser, program = test("i = /*test=*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right], []string{"test="}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Conditional, before consequent + parser, program = test("i ? /*test?*/ 2 : 3", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ConditionalExpression).Consequent], []string{"test?"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Conditional, after consequent + parser, program = test("i ? 2 /*test?*/ : 3", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ConditionalExpression).Consequent], []string{"test?"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Conditional, before alternate + parser, program = test("i ? 2 : /*test:*/ 3", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ConditionalExpression).Alternate], []string{"test:"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Logical OR + parser, program = test("i || /*test||*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test||"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Logical AND + parser, program = test("i && /*test&&*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test&&"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Bitwise OR + parser, program = test("i | /*test|*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test|"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Exclusive OR + parser, program = test("i ^ /*test^*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test^"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Bitwise AND + parser, program = test("i & /*test&*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test&"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Equality + parser, program = test("i == /*test==*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test=="}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Relational, < + parser, program = test("i < /*test<*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test<"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Relational, instanceof + parser, program = test("i instanceof /*testinstanceof*/ thing", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"testinstanceof"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Shift left + parser, program = test("i << /*test<<*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test<<"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // + + parser, program = test("i + /*test+*/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test+"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // * + parser, program = test("i * /*test**/ 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right], []string{"test*"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Unary prefix, ++ + parser, program = test("++/*test++*/i", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.UnaryExpression).Operand], []string{"test++"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Unary prefix, delete + parser, program = test("delete /*testdelete*/ i", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.UnaryExpression).Operand], []string{"testdelete"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Unary postfix, ++ + parser, program = test("i/*test++*/++", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.UnaryExpression).Operand], []string{"test++"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // + pt 2 + parser, program = test("i /*test+*/ + 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Left], []string{"test+"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Multiple comments for a single node + parser, program = test("i /*test+*/ /*test+2*/ + 2", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Left], []string{"test+", "test+2"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 2) + + // Multiple comments for multiple nodes + parser, program = test("i /*test1*/ + 2 /*test2*/ + a /*test3*/ * x /*test4*/", nil) + is(len(program.Body), 1) + is(parser.commentMap.Size(), 4) + + // Leading comment + parser, program = test("/*leadingtest*/i + 2", nil) + is(len(program.Body), 1) + + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement)], []string{"leadingtest"}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Left], []string{}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Leading comment, with semicolon + parser, program = test("/*leadingtest;*/;i + 2", nil) + is(len(program.Body), 2) + is(checkComments((*parser.commentMap)[program.Body[0]], []string{"leadingtest;"}, ast.LEADING), nil) + is(checkComments((*parser.commentMap)[program.Body[1].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Left], []string{}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Arrays + parser, program = test("[1, 2 /*test2*/, 3]", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ArrayLiteral).Value[1]], []string{"test2"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Function calls + parser, program = test("fun(a,b) //test", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.CallExpression)], []string{"test"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Function calls, pt 2 + parser, program = test("fun(a/*test1*/,b)", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.CallExpression).ArgumentList[0]], []string{"test1"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Function calls, pt 3 + parser, program = test("fun(/*test1*/a,b)", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.CallExpression).ArgumentList[0]], []string{"test1"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Arrays pt 2 + parser, program = test(`["abc".substr(0,1)/*testa*/, + "abc.substr(0,2)"];`, nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ArrayLiteral).Value[0]], []string{"testa"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Arrays pt 3 + parser, program = test(`[a, //test + b];`, nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ArrayLiteral).Value[1]], []string{"test"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Arrays pt 4 + parser, program = test(`[a, //test + b, c];`, nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ArrayLiteral).Value[1]], []string{"test"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Arrays pt 5 + parser, program = test(` +[ + "a1", // "a" + "a2", // "ab" +]; + `, nil) + is(parser.commentMap.Size(), 1) // Should have been 2, but we need an empty expression node + + // Arrays pt 6 + parser, program = test(`[a, /*test*/ b, c];`, nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ArrayLiteral).Value[1]], []string{"test"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Arrays pt 7 - Not working correctly yet. + parser, program = test(`[a,,/*test2*/];`, nil) + is(len(program.Body), 1) + //is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ArrayLiteral).Value[0]], []string{"test"}, false), nil) + //is(parser.commentMap.Size(), 1) + + // Object literal + parser, program = test("obj = {a: 1, b: 2 /*test2*/, c: 3}", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.ObjectLiteral).Value[1].Value], []string{"test2"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Object literal, pt 2 + parser, program = test("obj = {/*test2*/a: 1, b: 2, c: 3}", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.ObjectLiteral).Value[0].Value], []string{"test2"}, ast.LEADING), nil) + is(parser.commentMap.Size(), 1) + + // Object literal, pt 3 + parser, program = test("obj = {x/*test2*/: 1, y: 2, z: 3}", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.ObjectLiteral).Value[0].Value], []string{"test2"}, ast.KEY), nil) + is(parser.commentMap.Size(), 1) + + // Object literal, pt 4 + parser, program = test("obj = {x: /*test2*/1, y: 2, z: 3}", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.ObjectLiteral).Value[0].Value], []string{"test2"}, ast.COLON), nil) + is(parser.commentMap.Size(), 1) + + // Object literal, pt 5 + parser, program = test("obj = {x: 1/*test2*/, y: 2, z: 3}", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.ObjectLiteral).Value[0].Value], []string{"test2"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Object literal, pt 6 + parser, program = test("obj = {x: 1, y: 2, z: 3/*test2*/}", nil) + is(len(program.Body), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.ObjectLiteral).Value[2].Value], []string{"test2"}, ast.TRAILING), nil) + is(parser.commentMap.Size(), 1) + + // Line breaks + parser, program = test(` +t1 = "BLA DE VLA" +/*Test*/ +t2 = "Nothing happens." + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.StringLiteral)], []string{}, ast.TRAILING), nil) + is(checkComments((*parser.commentMap)[program.Body[1].(*ast.ExpressionStatement)], []string{"Test"}, ast.LEADING), nil) + + // Line breaks pt 2 + parser, program = test(` +t1 = "BLA DE VLA" /*Test*/ +t2 = "Nothing happens." + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.StringLiteral)], []string{"Test"}, ast.TRAILING), nil) + is(checkComments((*parser.commentMap)[program.Body[1].(*ast.ExpressionStatement)], []string{}, ast.LEADING), nil) + + // Line breaks pt 3 + parser, program = test(` +t1 = "BLA DE VLA" /*Test*/ /*Test2*/ +t2 = "Nothing happens." + `, nil) + is(parser.commentMap.Size(), 2) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.StringLiteral)], []string{"Test", "Test2"}, ast.TRAILING), nil) + is(checkComments((*parser.commentMap)[program.Body[1].(*ast.ExpressionStatement)], []string{}, ast.LEADING), nil) + + // Line breaks pt 4 + parser, program = test(` +t1 = "BLA DE VLA" /*Test*/ +/*Test2*/ +t2 = "Nothing happens." + `, nil) + is(parser.commentMap.Size(), 2) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.AssignExpression).Right.(*ast.StringLiteral)], []string{"Test"}, ast.TRAILING), nil) + is(checkComments((*parser.commentMap)[program.Body[1].(*ast.ExpressionStatement)], []string{"Test2"}, ast.LEADING), nil) + + // Misc + parser, program = test(` +var x = Object.create({y: { +}, +// a +}); + `, nil) + is(parser.commentMap.Size(), 1) + + // Misc 2 + parser, program = test(` +var x = Object.create({y: { +}, +// a +// b +a: 2}); + `, nil) + is(parser.commentMap.Size(), 2) + + // Statement blocks + parser, program = test(` +(function() { + // Baseline setup +}) + `, nil) + is(parser.commentMap.Size(), 1) + + // Switches + parser, program = test(` +switch (switcha) { + // switch comment + case "switchb": + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0]], []string{" switch comment"}, ast.LEADING), nil) + + // Switches pt 2 + parser, program = test(` +switch (switcha) { + case /*switch comment*/ "switchb": + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Test], []string{"switch comment"}, ast.LEADING), nil) + + // Switches pt 3 + parser, program = test(` +switch (switcha) { + case "switchb" /*switch comment*/: + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Test], []string{"switch comment"}, ast.TRAILING), nil) + + // Switches pt 4 + parser, program = test(` +switch (switcha) { + case "switchb": /*switch comment*/ + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Test], []string{"switch comment"}, ast.TRAILING), nil) + + // Switches pt 5 - default + parser, program = test(` +switch (switcha) { + default: /*switch comment*/ + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Test], []string{"switch comment"}, ast.TRAILING), nil) + + // Switches pt 6 + parser, program = test(` +switch (switcha) { + case "switchb": + /*switch comment*/a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Consequent[0].(*ast.ExpressionStatement).Expression], []string{"switch comment"}, ast.LEADING), nil) + + // Switches pt 7 + parser, program = test(` +switch (switcha) { + case "switchb": /*switch comment*/ { + a + } +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Test], []string{"switch comment"}, ast.TRAILING), nil) + + // Switches pt 8 + parser, program = test(` +switch (switcha) { + case "switchb": { + a + }/*switch comment*/ +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Consequent[0]], []string{"switch comment"}, ast.TRAILING), nil) + + // Switches pt 9 + parser, program = test(` +switch (switcha) { + case "switchb": /*switch comment*/ { + a + } +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Test], []string{"switch comment"}, ast.TRAILING), nil) + + // Switches pt 10 + parser, program = test(` +switch (switcha) { + case "switchb": { + /*switch comment*/a + } +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.SwitchStatement).Body[0].Consequent[0].(*ast.BlockStatement).List[0].(*ast.ExpressionStatement).Expression], []string{"switch comment"}, ast.LEADING), nil) + + // For loops + parser, program = test(` +for(/*comment*/i = 0 ; i < 1 ; i++) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Initializer], []string{"comment"}, ast.LEADING), nil) + + // For loops pt 2 + parser, program = test(` +for(i/*comment*/ = 0 ; i < 1 ; i++) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Initializer.(*ast.SequenceExpression).Sequence[0].(*ast.AssignExpression).Left], []string{"comment"}, ast.TRAILING), nil) + + // For loops pt 3 + parser, program = test(` +for(i = 0 ; /*comment*/i < 1 ; i++) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Test.(*ast.BinaryExpression)], []string{"comment"}, ast.LEADING), nil) + + // For loops pt 4 + parser, program = test(` +for(i = 0 ;i /*comment*/ < 1 ; i++) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Test.(*ast.BinaryExpression).Left], []string{"comment"}, ast.TRAILING), nil) + + // For loops pt 5 + parser, program = test(` +for(i = 0 ;i < 1 /*comment*/ ; i++) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Test.(*ast.BinaryExpression).Right], []string{"comment"}, ast.TRAILING), nil) + + // For loops pt 6 + parser, program = test(` +for(i = 0 ;i < 1 ; /*comment*/ i++) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Update], []string{"comment"}, ast.LEADING), nil) + + // For loops pt 7 + parser, program = test(` +for(i = 0 ;i < 1 ; i++) /*comment*/ { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Body], []string{"comment"}, ast.LEADING), nil) + + // For loops pt 8 + parser, program = test(` +for(i = 0 ;i < 1 ; i++) { + a +}/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement)], []string{"comment"}, ast.TRAILING), nil) + + // For loops pt 9 + parser, program = test(` +for(i = 0 ;i < 1 ; /*comment*/i++) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Update.(*ast.UnaryExpression)], []string{"comment"}, ast.LEADING), nil) + + // For loops pt 10 + parser, program = test(` +for(i = 0 ;i < 1 ; i/*comment*/++) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Update.(*ast.UnaryExpression).Operand.(*ast.Identifier)], []string{"comment"}, ast.TRAILING), nil) + + // For loops pt 11 + parser, program = test(` +for(i = 0 ;i < 1 ; i++/*comment*/) { + a +} + `, nil) + parser.commentMap.Display() + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForStatement).Update.(*ast.UnaryExpression)], []string{"comment"}, ast.TRAILING), nil) + + // ForIn + parser, program = test(` +for(/*comment*/var i = 0 in obj) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForInStatement).Into], []string{"comment"}, ast.LEADING), nil) + + // ForIn pt 2 + parser, program = test(` +for(var i = 0 /*comment*/in obj) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForInStatement).Into.(*ast.VariableExpression).Initializer], []string{"comment"}, ast.TRAILING), nil) + + // ForIn pt 3 + parser, program = test(` +for(var i = 0 in /*comment*/ obj) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForInStatement).Source], []string{"comment"}, ast.LEADING), nil) + + // ForIn pt 4 + parser, program = test(` +for(var i = 0 in obj/*comment*/) { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForInStatement).Source], []string{"comment"}, ast.TRAILING), nil) + + // ForIn pt 5 + parser, program = test(` +for(var i = 0 in obj) /*comment*/ { + a +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForInStatement).Body], []string{"comment"}, ast.LEADING), nil) + + // ForIn pt 6 + parser, program = test(` +for(var i = 0 in obj) { + a +}/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ForInStatement)], []string{"comment"}, ast.TRAILING), nil) + + // ForIn pt 7 + parser, program = test(` +for(var i = 0 in obj) { + a +} +// comment + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program], []string{" comment"}, ast.FINAL), nil) + + // // Block + // parser, program = test(` + ///*comment*/{ + // a + //} + // `, nil) + // is(parser.commentMap.Size(), 1) + // is(checkComments((*parser.commentMap)[program.Body[0].(*ast.BlockStatement)], []string{"comment"}, ast.LEADING), nil) + // + // // Block pt 2 + // parser, program = test(` + //{ + // a + //}/*comment*/ + // `, nil) + // is(parser.commentMap.Size(), 1) + // is(checkComments((*parser.commentMap)[program.Body[0].(*ast.BlockStatement)], []string{"comment"}, ast.TRAILING), nil) + + // If then else + parser, program = test(` +/*comment*/ +if(a) { + b +} else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.IfStatement)], []string{"comment"}, ast.LEADING), nil) + + // If then else pt 2 + parser, program = test(` +if/*comment*/(a) { + b +} else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.IfStatement)], []string{"comment"}, ast.KEY), nil) + + // If then else pt 3 + parser, program = test(` +if(/*comment*/a) { + b +} else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.IfStatement).Test], []string{"comment"}, ast.LEADING), nil) + + // If then else pt 4 + parser, program = test(` +if(a/*comment*/) { + b +} else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.IfStatement).Test], []string{"comment"}, ast.TRAILING), nil) + + // If then else pt 4 + parser, program = test(` +if(a)/*comment*/ { + b +} else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.IfStatement).Consequent], []string{"comment"}, ast.LEADING), nil) + + // If then else pt 5 + parser, program = test(` +if(a) { + b +} /*comment*/else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.IfStatement).Consequent], []string{"comment"}, ast.TRAILING), nil) + + // If then else pt 6 + parser, program = test(` +if(a) { + b +} else/*comment*/ { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.IfStatement).Alternate], []string{"comment"}, ast.LEADING), nil) + + // If then else pt 7 + parser, program = test(` +if(a) { + b +} else { + c +}/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.IfStatement).Alternate], []string{"comment"}, ast.TRAILING), nil) + + // If then else pt 8 + parser, program = test(` +if +/*comment*/ +(a) { + b +} else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + + // If then else pt 9 + parser, program = test(` +if +(a) + /*comment*/{ + b +} else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + + // If then else pt 10 + parser, program = test(` +if(a){ + b +} +/*comment*/ +else { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + + // Do while + parser, program = test(` +/*comment*/do { + a +} while(b) + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.DoWhileStatement)], []string{"comment"}, ast.LEADING), nil) + + // Do while pt 2 + parser, program = test(` +do /*comment*/ { + a +} while(b) + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.DoWhileStatement)], []string{"comment"}, ast.KEY), nil) + + // Do while pt 3 + parser, program = test(` +do { + a +} /*comment*/ while(b) + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.DoWhileStatement).Body], []string{"comment"}, ast.TRAILING), nil) + + // Do while pt 4 + parser, program = test(` +do { + a +} while/*comment*/(b) + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.DoWhileStatement).Test], []string{"comment"}, ast.LEADING), nil) + + // Do while pt 5 + parser, program = test(` +do { + a +} while(b)/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.DoWhileStatement).Test], []string{"comment"}, ast.TRAILING), nil) + + // While + parser, program = test(` +/*comment*/while(a) { + b +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement)], []string{"comment"}, ast.LEADING), nil) + + // While pt 2 + parser, program = test(` +while/*comment*/(a) { + b +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement)], []string{"comment"}, ast.KEY), nil) + + // While pt 3 + parser, program = test(` +while(/*comment*/a) { + b +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement).Test], []string{"comment"}, ast.LEADING), nil) + + // While pt 4 + parser, program = test(` +while(a/*comment*/) { + b +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement).Test], []string{"comment"}, ast.TRAILING), nil) + + // While pt 5 + parser, program = test(` +while(a) /*comment*/ { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement).Body], []string{"comment"}, ast.LEADING), nil) + + // While pt 6 + parser, program = test(` +while(a) { + c +}/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement)], []string{"comment"}, ast.TRAILING), nil) + + // While pt 7 + parser, program = test(` +while(a) { + c/*comment*/ +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement).Body.(*ast.BlockStatement).List[0].(*ast.ExpressionStatement).Expression.(*ast.Identifier)], []string{"comment"}, ast.TRAILING), nil) + + // While pt 7 + parser, program = test(` +while(a) { + /*comment*/ +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement).Body.(*ast.BlockStatement)], []string{"comment"}, ast.FINAL), nil) + + // While pt 8 + parser, program = test(` +while +/*comment*/(a) { + +} + `, nil) + is(parser.commentMap.Size(), 1) + + // While pt 9 + parser, program = test(` +while +(a) + /*comment*/{ + +} + `, nil) + is(parser.commentMap.Size(), 1) + + // Break + parser, program = test(` +while(a) { + break/*comment*/; +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement).Body.(*ast.BlockStatement).List[0].(*ast.BranchStatement)], []string{"comment"}, ast.TRAILING), nil) + + // Break pt 2 + parser, program = test(` +while(a) { + next/*comment*/: + break next; +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement).Body.(*ast.BlockStatement).List[0].(*ast.LabelledStatement).Label], []string{"comment"}, ast.TRAILING), nil) + + // Break pt 3 + parser, program = test(` +while(a) { + next:/*comment*/ + break next; +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WhileStatement).Body.(*ast.BlockStatement).List[0].(*ast.LabelledStatement)], []string{"comment"}, ast.TRAILING), nil) + + // Debugger + parser, program = test(` +debugger // comment + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.DebuggerStatement)], []string{" comment"}, ast.TRAILING), nil) + + // Debugger pt 2 + parser, program = test(` +debugger; // comment + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.DebuggerStatement)], []string{" comment"}, ast.TRAILING), nil) + + // Debugger pt 3 + parser, program = test(` +debugger; +// comment + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program], []string{" comment"}, ast.FINAL), nil) + + // With + parser, program = test(` +/*comment*/with(a) { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WithStatement)], []string{"comment"}, ast.LEADING), nil) + + // With pt 2 + parser, program = test(` +with/*comment*/(a) { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WithStatement)], []string{"comment"}, ast.KEY), nil) + + // With pt 3 + parser, program = test(` +with(/*comment*/a) { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WithStatement).Object], []string{"comment"}, ast.LEADING), nil) + + // With pt 4 + parser, program = test(` +with(a/*comment*/) { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WithStatement).Object], []string{"comment"}, ast.TRAILING), nil) + + // With pt 5 + parser, program = test(` +with(a) /*comment*/ { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WithStatement).Body], []string{"comment"}, ast.LEADING), nil) + + // With pt 6 + parser, program = test(` +with(a) { +}/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.WithStatement)], []string{"comment"}, ast.TRAILING), nil) + + // With pt 7 + parser, program = test(` +with +/*comment*/(a) { +} + `, nil) + is(parser.commentMap.Size(), 1) + + // With pt 8 + parser, program = test(` +with +(a) + /*comment*/{ +} + `, nil) + is(parser.commentMap.Size(), 1) + + // Var + parser, program = test(` +/*comment*/var a + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.VariableStatement)], []string{"comment"}, ast.LEADING), nil) + + // Var pt 2 + parser, program = test(` +var/*comment*/ a + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.VariableStatement).List[0]], []string{"comment"}, ast.LEADING), nil) + + // Var pt 3 + parser, program = test(` +var a/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.VariableStatement)], []string{"comment"}, ast.TRAILING), nil) + + // Var pt 4 + parser, program = test(` +var a/*comment*/, b + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.VariableStatement).List[0].(*ast.VariableExpression)], []string{"comment"}, ast.TRAILING), nil) + + // Var pt 5 + parser, program = test(` +var a, /*comment*/b + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.VariableStatement).List[1].(*ast.VariableExpression)], []string{"comment"}, ast.LEADING), nil) + + // Var pt 6 + parser, program = test(` +var a, b/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.VariableStatement)], []string{"comment"}, ast.TRAILING), nil) + + // Var pt 7 + parser, program = test(` +var a, b; +/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program], []string{"comment"}, ast.FINAL), nil) + + // Return + parser, program = test(` + function f() { +/*comment*/return o +} + `, nil) + is(parser.commentMap.Size(), 1) + + // Try catch + parser, program = test(` +/*comment*/try { + a +} catch(b) { + c +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement)], []string{"comment"}, ast.LEADING), nil) + + // Try catch pt 2 + parser, program = test(` +try/*comment*/ { + a +} catch(b) { + c +} finally { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Body], []string{"comment"}, ast.LEADING), nil) + + // Try catch pt 3 + parser, program = test(` +try { + a +}/*comment*/ catch(b) { + c +} finally { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Body], []string{"comment"}, ast.TRAILING), nil) + + // Try catch pt 4 + parser, program = test(` +try { + a +} catch(/*comment*/b) { + c +} finally { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Catch.Parameter], []string{"comment"}, ast.LEADING), nil) + + // Try catch pt 5 + parser, program = test(` +try { + a +} catch(b/*comment*/) { + c +} finally { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Catch.Parameter], []string{"comment"}, ast.TRAILING), nil) + + // Try catch pt 6 + parser, program = test(` +try { + a +} catch(b) /*comment*/{ + c +} finally { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Catch.Body], []string{"comment"}, ast.LEADING), nil) + + // Try catch pt 7 + parser, program = test(` +try { + a +} catch(b){ + c +} /*comment*/ finally { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Catch.Body], []string{"comment"}, ast.TRAILING), nil) + + // Try catch pt 8 + parser, program = test(` +try { + a +} catch(b){ + c +} finally /*comment*/ { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Finally], []string{"comment"}, ast.LEADING), nil) + + // Try catch pt 9 + parser, program = test(` +try { + a +} catch(b){ + c +} finally { +}/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Finally], []string{"comment"}, ast.TRAILING), nil) + + // Try catch pt 11 + parser, program = test(` +try { + a +} +/*comment*/ + catch(b){ + c +} finally { + d +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Body], []string{"comment"}, ast.TRAILING), nil) + + // Throw + parser, program = test(` +throw a/*comment*/ + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ThrowStatement).Argument], []string{"comment"}, ast.TRAILING), nil) + + // Throw pt 2 + parser, program = test(` +/*comment*/throw a + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ThrowStatement)], []string{"comment"}, ast.LEADING), nil) + + // Throw pt 3 + parser, program = test(` +throw /*comment*/a + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.ThrowStatement).Argument], []string{"comment"}, ast.LEADING), nil) + + // Try catch pt 10 + parser, program = test(` +try { + a +} catch(b){ + c +} + /*comment*/finally { +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Catch], []string{"comment"}, ast.TRAILING), nil) + + // Try catch pt 11 + parser, program = test(` +try { + a +} catch(b){ + c +} + finally + /*comment*/ + { + d +} + `, nil) + is(parser.commentMap.Size(), 1) + is(checkComments((*parser.commentMap)[program.Body[0].(*ast.TryStatement).Finally], []string{"comment"}, ast.LEADING), nil) + + }) +} diff --git a/parser/expression.go b/parser/expression.go index 8baf22f..8c65e6c 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -12,10 +12,14 @@ func (self *_parser) parseIdentifier() *ast.Identifier { literal := self.literal idx := self.idx self.next() - return &ast.Identifier{ + comments := self.findComments(false) + exp := &ast.Identifier{ Name: literal, Idx: idx, } + + self.commentMap.AddComments(exp, comments, ast.TRAILING) + return exp } func (self *_parser) parsePrimaryExpression() ast.Expression { @@ -194,13 +198,24 @@ func (self *_parser) parseVariableDeclarationList(var_ file.Idx) []ast.Expressio var declarationList []*ast.VariableExpression // Avoid bad expressions var list []ast.Expression + var comments2 []*ast.Comment for { - list = append(list, self.parseVariableDeclaration(&declarationList)) + + comments := self.findComments(false) + + decl := self.parseVariableDeclaration(&declarationList) + self.commentMap.AddComments(decl, comments, ast.LEADING) + + comments2 = self.findComments(false) + self.commentMap.AddComments(decl, comments2, ast.TRAILING) + + list = append(list, decl) if self.token != token.COMMA { break } self.next() + } self.scope.declare(&ast.VariableDeclaration{ @@ -211,10 +226,13 @@ func (self *_parser) parseVariableDeclarationList(var_ file.Idx) []ast.Expressio return list } -func (self *_parser) parseObjectPropertyKey() (string, string) { +func (self *_parser) parseObjectPropertyKey() (string, string, []*ast.Comment) { idx, tkn, literal := self.idx, self.token, self.literal value := "" self.next() + + comments := self.findComments(false) + switch tkn { case token.IDENTIFIER: value = literal @@ -238,15 +256,14 @@ func (self *_parser) parseObjectPropertyKey() (string, string) { value = literal } } - return literal, value + return literal, value, comments } func (self *_parser) parseObjectProperty() ast.Property { - - literal, value := self.parseObjectPropertyKey() + literal, value, comments := self.parseObjectPropertyKey() if literal == "get" && self.token != token.COLON { idx := self.idx - _, value := self.parseObjectPropertyKey() + _, value, _ := self.parseObjectPropertyKey() parameterList := self.parseFunctionParameterList() node := &ast.FunctionLiteral{ @@ -261,7 +278,7 @@ func (self *_parser) parseObjectProperty() ast.Property { } } else if literal == "set" && self.token != token.COLON { idx := self.idx - _, value := self.parseObjectPropertyKey() + _, value, _ := self.parseObjectPropertyKey() parameterList := self.parseFunctionParameterList() node := &ast.FunctionLiteral{ @@ -277,51 +294,91 @@ func (self *_parser) parseObjectProperty() ast.Property { } self.expect(token.COLON) + comments2 := self.findComments(false) - return ast.Property{ + exp := ast.Property{ Key: value, Kind: "value", Value: self.parseAssignmentExpression(), } + + self.commentMap.AddComments(exp.Value, comments, ast.KEY) + self.commentMap.AddComments(exp.Value, comments2, ast.COLON) + return exp } func (self *_parser) parseObjectLiteral() ast.Expression { var value []ast.Property idx0 := self.expect(token.LEFT_BRACE) + + var comments2 []*ast.Comment for self.token != token.RIGHT_BRACE && self.token != token.EOF { + + // Leading comments for object literal + comments := self.findComments(false) property := self.parseObjectProperty() + self.commentMap.AddComments(property.Value, comments, ast.LEADING) + self.commentMap.AddComments(property.Value, comments2, ast.LEADING) value = append(value, property) if self.token == token.COMMA { self.next() + + // Find leading comments after trailing comma + comments2 = self.findComments(false) continue } } idx1 := self.expect(token.RIGHT_BRACE) - return &ast.ObjectLiteral{ + exp := &ast.ObjectLiteral{ LeftBrace: idx0, RightBrace: idx1, Value: value, } + + self.commentMap.AddComments(exp, comments2, ast.LEADING) + self.consumeComments(exp, ast.FINAL) + + return exp } func (self *_parser) parseArrayLiteral() ast.Expression { - idx0 := self.expect(token.LEFT_BRACKET) + var comments2 []*ast.Comment var value []ast.Expression for self.token != token.RIGHT_BRACKET && self.token != token.EOF { if self.token == token.COMMA { self.next() + + // TODO This kind of comment requires a special empty expression node. + // TODO For now it is not saved. + self.findComments(false) + value = append(value, nil) continue } - value = append(value, self.parseAssignmentExpression()) + + // This comment belongs to the expression before the comma + comments := self.findComments(false) + exp := self.parseAssignmentExpression() + self.commentMap.AddComments(exp, comments, ast.LEADING) + self.commentMap.AddComments(exp, comments2, ast.LEADING) + + value = append(value, exp) if self.token != token.RIGHT_BRACKET { self.expect(token.COMMA) } + + // This comment belongs to the following expression + comments2 = self.findComments(false) + //self.commentMap.AddComments(exp, comments2) + } idx1 := self.expect(token.RIGHT_BRACKET) + // TODO This is where comments after a possible trailing comma should be added + + // TODO COMMENT return &ast.ArrayLiteral{ LeftBracket: idx0, RightBracket: idx1, @@ -333,7 +390,10 @@ func (self *_parser) parseArgumentList() (argumentList []ast.Expression, idx0, i idx0 = self.expect(token.LEFT_PARENTHESIS) if self.token != token.RIGHT_PARENTHESIS { for { - argumentList = append(argumentList, self.parseAssignmentExpression()) + comments := self.findComments(false) + exp := self.parseAssignmentExpression() + self.commentMap.AddComments(exp, comments, ast.LEADING) + argumentList = append(argumentList, exp) if self.token != token.COMMA { break } @@ -346,12 +406,16 @@ func (self *_parser) parseArgumentList() (argumentList []ast.Expression, idx0, i func (self *_parser) parseCallExpression(left ast.Expression) ast.Expression { argumentList, idx0, idx1 := self.parseArgumentList() - return &ast.CallExpression{ + exp := &ast.CallExpression{ Callee: left, LeftParenthesis: idx0, ArgumentList: argumentList, RightParenthesis: idx1, } + + comments := self.findComments(false) + self.commentMap.AddComments(exp, comments, ast.TRAILING) + return exp } func (self *_parser) parseDotMember(left ast.Expression) ast.Expression { @@ -402,6 +466,10 @@ func (self *_parser) parseNewExpression() ast.Expression { node.LeftParenthesis = idx0 node.RightParenthesis = idx1 } + + comments := self.findComments(false) + self.commentMap.AddComments(node, comments, ast.TRAILING) + return node } @@ -414,6 +482,9 @@ func (self *_parser) parseLeftHandSideExpression() ast.Expression { left = self.parsePrimaryExpression() } + comments := self.findComments(false) + self.commentMap.AddComments(left, comments, ast.TRAILING) + for { if self.token == token.PERIOD { left = self.parseDotMember(left) @@ -442,6 +513,9 @@ func (self *_parser) parseLeftHandSideExpressionAllowCall() ast.Expression { left = self.parsePrimaryExpression() } + comments := self.findComments(false) + self.commentMap.AddComments(left, comments, ast.TRAILING) + for { if self.token == token.PERIOD { left = self.parseDotMember(left) @@ -476,12 +550,17 @@ func (self *_parser) parsePostfixExpression() ast.Expression { self.nextStatement() return &ast.BadExpression{From: idx, To: self.idx} } - return &ast.UnaryExpression{ + exp := &ast.UnaryExpression{ Operator: tkn, Idx: idx, Operand: operand, Postfix: true, } + + comments := self.findComments(false) + self.commentMap.AddComments(exp, comments, ast.TRAILING) + + return exp } return operand @@ -496,16 +575,26 @@ func (self *_parser) parseUnaryExpression() ast.Expression { tkn := self.token idx := self.idx self.next() - return &ast.UnaryExpression{ + + comments := self.findComments(false) + + exp := &ast.UnaryExpression{ Operator: tkn, Idx: idx, Operand: self.parseUnaryExpression(), } + + self.commentMap.AddComments(exp.Operand, comments, ast.LEADING) + return exp case token.INCREMENT, token.DECREMENT: tkn := self.token idx := self.idx self.next() + + comments := self.findComments(false) + operand := self.parseUnaryExpression() + self.commentMap.AddComments(operand, comments, ast.LEADING) switch operand.(type) { case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression: default: @@ -531,11 +620,16 @@ func (self *_parser) parseMultiplicativeExpression() ast.Expression { self.token == token.REMAINDER { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -548,11 +642,16 @@ func (self *_parser) parseAdditiveExpression() ast.Expression { for self.token == token.PLUS || self.token == token.MINUS { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -566,11 +665,16 @@ func (self *_parser) parseShiftExpression() ast.Expression { self.token == token.UNSIGNED_SHIFT_RIGHT { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -590,31 +694,49 @@ func (self *_parser) parseRelationalExpression() ast.Expression { case token.LESS, token.LESS_OR_EQUAL, token.GREATER, token.GREATER_OR_EQUAL: tkn := self.token self.next() - return &ast.BinaryExpression{ + + comments := self.findComments(false) + + exp := &ast.BinaryExpression{ Operator: tkn, Left: left, Right: self.parseRelationalExpression(), Comparison: true, } + + self.commentMap.AddComments(exp.Right, comments, ast.LEADING) + return exp case token.INSTANCEOF: tkn := self.token self.next() - return &ast.BinaryExpression{ + + comments := self.findComments(false) + + exp := &ast.BinaryExpression{ Operator: tkn, Left: left, Right: self.parseRelationalExpression(), } + + self.commentMap.AddComments(exp.Right, comments, ast.LEADING) + return exp case token.IN: if !allowIn { return left } tkn := self.token self.next() - return &ast.BinaryExpression{ + + comments := self.findComments(false) + + exp := &ast.BinaryExpression{ Operator: tkn, Left: left, Right: self.parseRelationalExpression(), } + + self.commentMap.AddComments(exp.Right, comments, ast.LEADING) + return exp } return left @@ -628,12 +750,17 @@ func (self *_parser) parseEqualityExpression() ast.Expression { self.token == token.STRICT_EQUAL || self.token == token.STRICT_NOT_EQUAL { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), Comparison: true, } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -646,11 +773,16 @@ func (self *_parser) parseBitwiseAndExpression() ast.Expression { for self.token == token.AND { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -663,11 +795,16 @@ func (self *_parser) parseBitwiseExclusiveOrExpression() ast.Expression { for self.token == token.EXCLUSIVE_OR { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -680,11 +817,16 @@ func (self *_parser) parseBitwiseOrExpression() ast.Expression { for self.token == token.OR { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -697,11 +839,16 @@ func (self *_parser) parseLogicalAndExpression() ast.Expression { for self.token == token.LOGICAL_AND { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -714,11 +861,16 @@ func (self *_parser) parseLogicalOrExpression() ast.Expression { for self.token == token.LOGICAL_OR { tkn := self.token self.next() + + comments := self.findComments(false) + left = &ast.BinaryExpression{ Operator: tkn, Left: left, Right: next(), } + + self.commentMap.AddComments(left.(*ast.BinaryExpression).Right, comments, ast.LEADING) } return left @@ -729,13 +881,25 @@ func (self *_parser) parseConditionlExpression() ast.Expression { if self.token == token.QUESTION_MARK { self.next() + + // Comments before the consequence + comments1 := self.findComments(false) + consequent := self.parseAssignmentExpression() + self.commentMap.AddComments(consequent, comments1, ast.LEADING) + self.expect(token.COLON) - return &ast.ConditionalExpression{ + + // Comments before the alternate + comments2 := self.findComments(false) + exp := &ast.ConditionalExpression{ Test: left, Consequent: consequent, Alternate: self.parseAssignmentExpression(), } + + self.commentMap.AddComments(exp.Alternate, comments2, ast.LEADING) + return exp } return left @@ -783,17 +947,28 @@ func (self *_parser) parseAssignmentExpression() ast.Expression { self.nextStatement() return &ast.BadExpression{From: idx, To: self.idx} } - return &ast.AssignExpression{ + + comments := self.findComments(false) + + exp := &ast.AssignExpression{ Left: left, Operator: operator, Right: self.parseAssignmentExpression(), } + + self.commentMap.AddComments(exp.Right, comments, ast.LEADING) + + return exp } return left } func (self *_parser) parseExpression() ast.Expression { + + comments := self.findComments(false) + statementComments := self.fetchComments() + next := self.parseAssignmentExpression left := next() @@ -811,5 +986,8 @@ func (self *_parser) parseExpression() ast.Expression { } } + self.commentMap.AddComments(left, comments, ast.LEADING) + self.commentMap.AddComments(left, statementComments, ast.LEADING) + return left } diff --git a/parser/lexer.go b/parser/lexer.go index bc3e74f..4b0e804 100644 --- a/parser/lexer.go +++ b/parser/lexer.go @@ -120,6 +120,7 @@ func isLineTerminator(chr rune) bool { func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) { self.implicitSemicolon = false + self.skippedLineBreak = false for { self.skipWhiteSpace() @@ -238,11 +239,14 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) { tkn = self.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN) case '/': if self.chr == '/' { - self.skipSingleLineComment() - continue + runes := self.readSingleLineComment() + literal = string(runes) + tkn = token.COMMENT + return } else if self.chr == '*' { - self.skipMultiLineComment() - continue + literal = string(self.readMultiLineComment()) + tkn = token.COMMENT + return } else { // Could be division, could be RegExp literal tkn = self.switch2(token.SLASH, token.QUOTIENT_ASSIGN) @@ -411,6 +415,39 @@ func (self *_RegExp_parser) read() { } } +func (self *_parser) readSingleLineComment() (result []rune) { + for self.chr != -1 { + self.read() + if isLineTerminator(self.chr) { + return + } + result = append(result, self.chr) + } + + // Get rid of the trailing -1 + result = result[:len(result)-1] + + return +} + +func (self *_parser) readMultiLineComment() (result []rune) { + self.read() + for self.chr >= 0 { + chr := self.chr + self.read() + if chr == '*' && self.chr == '/' { + self.read() + return + } + + result = append(result, chr) + } + + self.errorUnexpected(0, self.chr) + + return +} + func (self *_parser) skipSingleLineComment() { for self.chr != -1 { self.read() @@ -442,6 +479,7 @@ func (self *_parser) skipWhiteSpace() { continue case '\r': if self._peek() == '\n' { + self.skippedLineBreak = true self.read() } fallthrough @@ -449,6 +487,7 @@ func (self *_parser) skipWhiteSpace() { if self.insertSemicolon { return } + self.skippedLineBreak = true self.read() continue } diff --git a/parser/lexer_test.go b/parser/lexer_test.go index 37eb7a4..5b7ac87 100644 --- a/parser/lexer_test.go +++ b/parser/lexer_test.go @@ -97,7 +97,13 @@ func TestLexer(t *testing.T) { test("abc = //", token.IDENTIFIER, "abc", 1, token.ASSIGN, "", 5, - token.EOF, "", 9, + token.COMMENT, "", 7, + ) + + test("abc = /*test*/", + token.IDENTIFIER, "abc", 1, + token.ASSIGN, "", 5, + token.COMMENT, "test", 7, ) test("abc = 1 / 2", @@ -235,13 +241,13 @@ Second line \ ) test("//", - token.EOF, "", 3, + token.COMMENT, "", 1, ) - test(";;//", + test(";;//test", token.SEMICOLON, "", 1, token.SEMICOLON, "", 2, - token.EOF, "", 5, + token.COMMENT, "test", 3, ) test("1", diff --git a/parser/parser.go b/parser/parser.go index 92ac5b0..39c852e 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -52,9 +52,9 @@ const ( ) type _parser struct { - str string - length int - base int + str string + length int + base int chr rune // The current character chrOffset int // The offset of current character @@ -79,15 +79,22 @@ type _parser struct { mode Mode file *file.File + + comments []*ast.Comment + commentMap *ast.CommentMap + skippedLineBreak bool } func _newParser(filename, src string, base int) *_parser { return &_parser{ - chr: ' ', // This is set so we can start scanning by skipping whitespace - str: src, - length: len(src), - base: base, - file: file.NewFile(filename, src, base), + chr: ' ', // This is set so we can start scanning by skipping whitespace + str: src, + length: len(src), + base: base, + file: file.NewFile(filename, src, base), + comments: make([]*ast.Comment, 0), + commentMap: &ast.CommentMap{}, + skippedLineBreak: false, } } @@ -184,6 +191,9 @@ func (self *_parser) parse() (*ast.Program, error) { if false { self.errors.Sort() } + + self.addCommentStatements(program, ast.FINAL) + return program, self.errors.Err() } @@ -270,3 +280,61 @@ func (self *_parser) position(idx file.Idx) file.Position { return position } + +// findComments finds the following comments. +// Comments on the same line will be grouped together and returned. +// After the first line break, comments will be added as statement comments. +func (self *_parser) findComments(ignoreLineBreak bool) []*ast.Comment { + comments := make([]*ast.Comment, 0) + + newline := false + + for self.implicitSemicolon == false || ignoreLineBreak { + + if self.token != token.COMMENT { + break + } + + comment := &ast.Comment{ + Begin: self.idx, + Text: self.literal, + Position: ast.TBD, + } + + newline = self.skippedLineBreak || newline + + if newline && !ignoreLineBreak { + self.comments = append(self.comments, comment) + } else { + comments = append(comments, comment) + } + + self.next() + } + + return comments +} + +// addCommentStatements will add the previously parsed, not positioned comments to the provided node +func (self *_parser) addCommentStatements(node ast.Node, position ast.CommentPosition) { + if len(self.comments) > 0 { + self.commentMap.AddComments(node, self.comments, position) + + // Reset comments + self.comments = make([]*ast.Comment, 0) + } +} + +// fetchComments fetches the current comments, resets the slice and returns the comments +func (self *_parser) fetchComments() (comments []*ast.Comment) { + comments = self.comments + self.comments = nil + + return comments +} + +// consumeComments consumes the current comments and appends them to the provided node +func (self *_parser) consumeComments(node ast.Node, position ast.CommentPosition) { + self.commentMap.AddComments(node, self.comments, position) + self.comments = nil +} diff --git a/parser/statement.go b/parser/statement.go index 2059d38..d310048 100644 --- a/parser/statement.go +++ b/parser/statement.go @@ -7,10 +7,22 @@ import ( func (self *_parser) parseBlockStatement() *ast.BlockStatement { node := &ast.BlockStatement{} + + // Find comments before the leading brace + comments := self.findComments(false) + self.commentMap.AddComments(node, comments, ast.LEADING) + node.LeftBrace = self.expect(token.LEFT_BRACE) node.List = self.parseStatementList() + + self.consumeComments(node, ast.FINAL) + node.RightBrace = self.expect(token.RIGHT_BRACE) + // Find comments after the trailing brace + comments = self.findComments(false) + self.commentMap.AddComments(node, comments, ast.TRAILING) + return node } @@ -21,7 +33,14 @@ func (self *_parser) parseEmptyStatement() ast.Statement { func (self *_parser) parseStatementList() (list []ast.Statement) { for self.token != token.RIGHT_BRACE && self.token != token.EOF { - list = append(list, self.parseStatement()) + if self.token == token.COMMENT { + self.parseCommentElement() + continue + } + statement := self.parseStatement() + list = append(list, statement) + + self.addCommentStatements(statement, ast.LEADING) } return @@ -77,6 +96,9 @@ func (self *_parser) parseStatement() ast.Statement { // LabelledStatement colon := self.idx self.next() // : + + comments := self.findComments(false) + label := identifier.Name for _, value := range self.scope.labels { if label == value { @@ -86,11 +108,15 @@ func (self *_parser) parseStatement() ast.Statement { self.scope.labels = append(self.scope.labels, label) // Push the label statement := self.parseStatement() self.scope.labels = self.scope.labels[:len(self.scope.labels)-1] // Pop the label - return &ast.LabelledStatement{ + exp := &ast.LabelledStatement{ Label: identifier, Colon: colon, Statement: statement, } + + self.commentMap.AddComments(exp, comments, ast.TRAILING) + + return exp } self.optionalSemicolon() @@ -107,16 +133,23 @@ func (self *_parser) parseTryStatement() ast.Statement { Body: self.parseBlockStatement(), } + comments2 := self.findComments(true) + self.commentMap.AddComments(node.Body, comments2, ast.TRAILING) + if self.token == token.CATCH { catch := self.idx self.next() self.expect(token.LEFT_PARENTHESIS) + comments := self.findComments(true) if self.token != token.IDENTIFIER { self.expect(token.IDENTIFIER) self.nextStatement() return &ast.BadStatement{From: catch, To: self.idx} } else { identifier := self.parseIdentifier() + + self.commentMap.AddComments(identifier, comments, ast.LEADING) + self.expect(token.RIGHT_PARENTHESIS) node.Catch = &ast.CatchStatement{ Catch: catch, @@ -124,11 +157,19 @@ func (self *_parser) parseTryStatement() ast.Statement { Body: self.parseBlockStatement(), } } + + comments = self.findComments(true) + self.commentMap.AddComments(node.Catch, comments, ast.TRAILING) } if self.token == token.FINALLY { self.next() + + comments := self.findComments(true) + node.Finally = self.parseBlockStatement() + + self.commentMap.AddComments(node.Finally, comments, ast.LEADING) } if node.Catch == nil && node.Finally == nil { @@ -143,10 +184,13 @@ func (self *_parser) parseFunctionParameterList() *ast.ParameterList { opening := self.expect(token.LEFT_PARENTHESIS) var list []*ast.Identifier for self.token != token.RIGHT_PARENTHESIS && self.token != token.EOF { + comments := self.findComments(true) if self.token != token.IDENTIFIER { self.expect(token.IDENTIFIER) } else { - list = append(list, self.parseIdentifier()) + identifier := self.parseIdentifier() + self.commentMap.AddComments(identifier, comments, ast.LEADING) + list = append(list, identifier) } if self.token != token.RIGHT_PARENTHESIS { self.expect(token.COMMA) @@ -218,12 +262,21 @@ func (self *_parser) parseFunctionBlock(node *ast.FunctionLiteral) { func (self *_parser) parseDebuggerStatement() ast.Statement { idx := self.expect(token.DEBUGGER) + comments := self.findComments(true) + node := &ast.DebuggerStatement{ Debugger: idx, } + self.commentMap.AddComments(node, comments, ast.TRAILING) + self.semicolon() + if !self.skippedLineBreak { + comments = self.findComments(false) + self.commentMap.AddComments(node, comments, ast.TRAILING) + } + return node } @@ -309,30 +362,66 @@ func (self *_parser) parseSwitchStatement() ast.Statement { func (self *_parser) parseWithStatement() ast.Statement { self.expect(token.WITH) + + // Find the comments after with + comments := self.findComments(true) + self.expect(token.LEFT_PARENTHESIS) + node := &ast.WithStatement{ Object: self.parseExpression(), } self.expect(token.RIGHT_PARENTHESIS) + // Add the key comments + self.commentMap.AddComments(node, comments, ast.KEY) + + // Find the leading comments for the body + comments = self.findComments(true) + node.Body = self.parseStatement() + // Add the body comments + self.commentMap.AddComments(node.Body, comments, ast.LEADING) + + // Move the trailing comments to the with statement + self.commentMap.MoveComments(node.Body, node, ast.TRAILING) + return node } func (self *_parser) parseCaseStatement() *ast.CaseStatement { + comments := self.findComments(true) + node := &ast.CaseStatement{ Case: self.idx, } + + self.commentMap.AddComments(node, comments, ast.LEADING) + + // Consume current comments + self.consumeComments(node, ast.LEADING) + if self.token == token.DEFAULT { self.next() } else { self.expect(token.CASE) + + comments = self.findComments(true) + node.Test = self.parseExpression() + self.commentMap.AddComments(node.Test, comments, ast.LEADING) + + comments = self.findComments(true) + self.commentMap.AddComments(node.Test, comments, ast.TRAILING) } + self.expect(token.COLON) + comments = self.findComments(false) + self.commentMap.AddComments(node.Test, comments, ast.TRAILING) + for { if self.token == token.EOF || self.token == token.RIGHT_BRACE || @@ -340,8 +429,11 @@ func (self *_parser) parseCaseStatement() *ast.CaseStatement { self.token == token.DEFAULT { break } - node.Consequent = append(node.Consequent, self.parseStatement()) + consequent := self.parseStatement() + node.Consequent = append(node.Consequent, consequent) + comments = self.findComments(false) + self.commentMap.AddComments(consequent, comments, ast.TRAILING) } return node @@ -360,44 +452,74 @@ func (self *_parser) parseForIn(into ast.Expression) *ast.ForInStatement { // Already have consumed " in" + // Comments after the in, before the expression + comments := self.findComments(true) + source := self.parseExpression() + self.commentMap.AddComments(source, comments, ast.LEADING) + self.expect(token.RIGHT_PARENTHESIS) - return &ast.ForInStatement{ + comments = self.findComments(true) + body := self.parseIterationStatement() + self.commentMap.AddComments(body, comments, ast.LEADING) + + forin := &ast.ForInStatement{ Into: into, Source: source, - Body: self.parseIterationStatement(), + Body: body, } + + self.commentMap.MoveComments(body, forin, ast.TRAILING) + + return forin } func (self *_parser) parseFor(initializer ast.Expression) *ast.ForStatement { // Already have consumed " ;" + comments := self.findComments(true) + var test, update ast.Expression if self.token != token.SEMICOLON { test = self.parseExpression() + self.commentMap.AddComments(test, comments, ast.LEADING) } self.expect(token.SEMICOLON) + comments = self.findComments(true) + if self.token != token.RIGHT_PARENTHESIS { update = self.parseExpression() + self.commentMap.AddComments(update, comments, ast.LEADING) } self.expect(token.RIGHT_PARENTHESIS) - return &ast.ForStatement{ + comments = self.findComments(true) + + body := self.parseIterationStatement() + self.commentMap.AddComments(body, comments, ast.LEADING) + + forstatement := &ast.ForStatement{ Initializer: initializer, Test: test, Update: update, - Body: self.parseIterationStatement(), + Body: body, } + + self.commentMap.MoveComments(body, forstatement, ast.TRAILING) + + return forstatement } func (self *_parser) parseForOrForInStatement() ast.Statement { idx := self.expect(token.FOR) self.expect(token.LEFT_PARENTHESIS) + comments := self.findComments(true) + var left []ast.Expression forIn := false @@ -435,11 +557,15 @@ func (self *_parser) parseForOrForInStatement() ast.Statement { self.nextStatement() return &ast.BadStatement{From: idx, To: self.idx} } + + self.commentMap.AddComments(left[0], comments, ast.LEADING) return self.parseForIn(left[0]) } self.expect(token.SEMICOLON) - return self.parseFor(&ast.SequenceExpression{Sequence: left}) + initializer := &ast.SequenceExpression{Sequence: left} + self.commentMap.AddComments(initializer, comments, ast.LEADING) + return self.parseFor(initializer) } func (self *_parser) parseVariableStatement() *ast.VariableStatement { @@ -447,12 +573,25 @@ func (self *_parser) parseVariableStatement() *ast.VariableStatement { idx := self.expect(token.VAR) list := self.parseVariableDeclarationList(idx) - self.semicolon() - return &ast.VariableStatement{ + statement := &ast.VariableStatement{ Var: idx, List: list, } + + self.commentMap.MoveComments(statement.List[len(statement.List)-1], statement, ast.TRAILING) + + comments := self.findComments(true) + self.commentMap.AddComments(statement, comments, ast.TRAILING) + + self.semicolon() + + if self.skippedLineBreak { + comments = self.findComments(false) + self.commentMap.AddComments(statement, comments, ast.TRAILING) + } + + return statement } func (self *_parser) parseDoWhileStatement() ast.Statement { @@ -463,7 +602,11 @@ func (self *_parser) parseDoWhileStatement() ast.Statement { }() self.expect(token.DO) + + comments := self.findComments(true) + node := &ast.DoWhileStatement{} + self.commentMap.AddComments(node, comments, ast.KEY) if self.token == token.LEFT_BRACE { node.Body = self.parseBlockStatement() } else { @@ -471,49 +614,111 @@ func (self *_parser) parseDoWhileStatement() ast.Statement { } self.expect(token.WHILE) + + comments = self.findComments(true) + self.expect(token.LEFT_PARENTHESIS) node.Test = self.parseExpression() + self.commentMap.AddComments(node.Test, comments, ast.LEADING) + self.expect(token.RIGHT_PARENTHESIS) + comments = self.findComments(false) + self.commentMap.AddComments(node.Test, comments, ast.TRAILING) + return node } func (self *_parser) parseWhileStatement() ast.Statement { self.expect(token.WHILE) + + // Comments after while keyword + comments := self.findComments(true) + self.expect(token.LEFT_PARENTHESIS) node := &ast.WhileStatement{ Test: self.parseExpression(), } + + // Add the while comments + self.commentMap.AddComments(node, comments, ast.KEY) + self.expect(token.RIGHT_PARENTHESIS) + + // Finding comments prior to the body + comments = self.findComments(true) + node.Body = self.parseIterationStatement() + // Adding the comments prior to the body + self.commentMap.AddComments(node.Body, comments, ast.LEADING) + + // Move the trailing comments to the while statement + self.commentMap.MoveComments(node.Body, node, ast.TRAILING) + return node } func (self *_parser) parseIfStatement() ast.Statement { self.expect(token.IF) + + comments := self.findComments(true) + self.expect(token.LEFT_PARENTHESIS) node := &ast.IfStatement{ Test: self.parseExpression(), } + + self.commentMap.AddComments(node, comments, ast.KEY) + self.expect(token.RIGHT_PARENTHESIS) + comments = self.findComments(true) + if self.token == token.LEFT_BRACE { node.Consequent = self.parseBlockStatement() } else { node.Consequent = self.parseStatement() } + self.commentMap.AddComments(node.Consequent, comments, ast.LEADING) + + comments = self.findComments(true) + self.commentMap.AddComments(node.Consequent, comments, ast.TRAILING) + if self.token == token.ELSE { self.next() + comments = self.findComments(true) + node.Alternate = self.parseStatement() + + self.commentMap.AddComments(node.Alternate, comments, ast.LEADING) + comments = self.findComments(false) + self.commentMap.AddComments(node.Alternate, comments, ast.TRAILING) } return node } func (self *_parser) parseSourceElement() ast.Statement { - return self.parseStatement() + + statementComment := self.fetchComments() + + statement := self.parseStatement() + + self.commentMap.AddComments(statement, statementComment, ast.LEADING) + + return statement +} + +func (self *_parser) parseCommentElement() { + literal := self.literal + idx := self.expect(token.COMMENT) + self.comments = append(self.comments, &ast.Comment{ + Begin: idx, + Text: literal, + Position: ast.LEADING, + }) } func (self *_parser) parseSourceElements() []ast.Statement { @@ -524,10 +729,19 @@ func (self *_parser) parseSourceElements() []ast.Statement { break } + if self.token == token.COMMENT { + self.parseCommentElement() + continue + } + body = append(body, self.parseSourceElement()) } for self.token != token.EOF { + if self.token == token.COMMENT { + self.parseCommentElement() + continue + } body = append(body, self.parseSourceElement()) } @@ -546,6 +760,9 @@ func (self *_parser) parseProgram() *ast.Program { func (self *_parser) parseBreakStatement() ast.Statement { idx := self.expect(token.BREAK) + + breakComments := self.findComments(true) + semicolon := self.implicitSemicolon if self.token == token.SEMICOLON { semicolon = true @@ -557,10 +774,14 @@ func (self *_parser) parseBreakStatement() ast.Statement { if !self.scope.inIteration && !self.scope.inSwitch { goto illegal } - return &ast.BranchStatement{ + breakStatement := &ast.BranchStatement{ Idx: idx, Token: token.BREAK, } + + self.commentMap.AddComments(breakStatement, breakComments, ast.TRAILING) + + return breakStatement } if self.token == token.IDENTIFIER {