From 9b5083066a4b4d13a09889e9d9acc1a355f4be90 Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Sat, 31 Oct 2015 15:01:49 +0300 Subject: [PATCH] printer: implement standalone comments, still WIP --- ast/ast.go | 14 ++ printer/nodes.go | 256 +++++++++++++++++++++----------- printer/printer.go | 2 + printer/testdata/comment.golden | 13 ++ printer/testdata/comment.input | 14 +- token/position.go | 10 ++ 6 files changed, 223 insertions(+), 86 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 6619eef..3e5bc6c 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -10,6 +10,12 @@ type Node interface { Pos() token.Pos } +// NewNode returns a non usable Node interface implementer. The position is +// initalizied to zero. +func NewNode() Node { + return &zero{} +} + func (File) node() {} func (ObjectList) node() {} func (ObjectKey) node() {} @@ -21,6 +27,14 @@ func (ObjectType) node() {} func (LiteralType) node() {} func (ListType) node() {} +type zero struct{} + +func (zero) node() {} + +func (z *zero) Pos() token.Pos { + return token.Pos{} +} + // File represents a single HCL file type File struct { Node Node // usually a *ObjectList diff --git a/printer/nodes.go b/printer/nodes.go index 8b44b0a..e84e9b0 100644 --- a/printer/nodes.go +++ b/printer/nodes.go @@ -3,6 +3,7 @@ package printer import ( "bytes" "fmt" + "sort" "github.com/fatih/hcl/ast" "github.com/fatih/hcl/token" @@ -15,11 +16,22 @@ const ( ) type printer struct { - cfg Config + cfg Config + prev ast.Node + comments []*ast.CommentGroup // may be nil, contains all comments standaloneComments []*ast.CommentGroup // contains all standalone comments (not assigned to any node) + + enableTrace bool + indentTrace int } +type ByPosition []*ast.CommentGroup + +func (b ByPosition) Len() int { return len(b) } +func (b ByPosition) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b ByPosition) Less(i, j int) bool { return b[i].Pos().Before(b[j].Pos()) } + func (p *printer) collectComments(node ast.Node) { // first collect all comments. This is already stored in // ast.File.(comments) @@ -64,28 +76,33 @@ func (p *printer) collectComments(node ast.Node) { }) for _, c := range standaloneComments { + p.standaloneComments = append(p.standaloneComments, c) + } + sort.Sort(ByPosition(p.standaloneComments)) + + fmt.Printf("standaloneComments = %+v\n", len(p.standaloneComments)) + for _, c := range p.standaloneComments { for _, comment := range c.List { fmt.Printf("comment = %+v\n", comment) } - p.standaloneComments = append(p.standaloneComments, c) } + } -// output prints creates a printable HCL output and returns it. +var count int + +// output prints creates b printable HCL output and returns it. func (p *printer) output(n interface{}) []byte { var buf bytes.Buffer + count++ switch t := n.(type) { case *ast.File: - // for i, group := range t.Comments { - // for _, comment := range group.List { - // fmt.Printf("[%d] comment = %+v\n", i, comment) - // } - // } return p.output(t.Node) case *ast.ObjectList: for i, item := range t.Items { - buf.Write(p.objectItem(item)) + fmt.Printf("[%d] item: %s\n", i, item.Keys[0].Token.Text) + buf.Write(p.output(item)) if i != len(t.Items)-1 { buf.Write([]byte{newline, newline}) } @@ -93,6 +110,20 @@ func (p *printer) output(n interface{}) []byte { case *ast.ObjectKey: buf.WriteString(t.Token.Text) case *ast.ObjectItem: + for _, c := range p.standaloneComments { + for _, comment := range c.List { + fmt.Printf("[%d] OBJECTITEM p.prev = %+v\n", count, p.prev.Pos()) + fmt.Printf("[%d] OBJECTITEM comment.Pos() = %+v\n", count, comment.Pos()) + fmt.Printf("[%d] OBJECTTYPE t.Pos() = %+v\n", count, t.Pos()) + if comment.Pos().After(p.prev.Pos()) && comment.Pos().Before(t.Pos()) { + buf.WriteString(comment.Text) + buf.WriteByte(newline) + buf.WriteByte(newline) + } + } + } + + p.prev = t buf.Write(p.objectItem(t)) case *ast.LiteralType: buf.WriteString(t.Token.Text) @@ -104,10 +135,15 @@ func (p *printer) output(n interface{}) []byte { fmt.Printf(" unknown type: %T\n", n) } + // if item, ok := n.(ast.Node); ok { + // p.prev = item + // } + return buf.Bytes() } func (p *printer) objectItem(o *ast.ObjectItem) []byte { + defer un(trace(p, fmt.Sprintf("ObjectItem: %s", o.Keys[0].Token.Text))) var buf bytes.Buffer if o.LeadComment != nil { @@ -140,6 +176,100 @@ func (p *printer) objectItem(o *ast.ObjectItem) []byte { return buf.Bytes() } +func (p *printer) objectType(o *ast.ObjectType) []byte { + defer un(trace(p, "ObjectType")) + var buf bytes.Buffer + buf.WriteString("{") + buf.WriteByte(newline) + + for _, c := range p.standaloneComments { + for _, comment := range c.List { + fmt.Printf("[%d] OBJECTTYPE p.prev = %+v\n", count, p.prev.Pos()) + fmt.Printf("[%d] OBJECTTYPE comment.Pos() = %+v\n", count, comment.Pos()) + fmt.Printf("[%d] OBJECTTYPE t.Pos() = %+v\n", count, o.Pos()) + firstItem := o.List.Pos() + if comment.Pos().After(p.prev.Pos()) && comment.Pos().Before(firstItem) { + buf.Write(p.indent([]byte(comment.Text))) // TODO(arslan): indent + buf.WriteByte(newline) + buf.WriteByte(newline) + } + } + } + + var index int + for { + // check if we have adjacent one liner items. If yes we'll going to align + // the comments. + var aligned []*ast.ObjectItem + for i, item := range o.List.Items[index:] { + // we don't group one line lists + if len(o.List.Items) == 1 { + break + } + + // one means a oneliner with out any lead comment + // two means a oneliner with lead comment + // anything else might be something else + cur := lines(string(p.objectItem(item))) + if cur > 2 { + break + } + + next := 0 + if index != len(o.List.Items)-1 { + next = lines(string(p.objectItem(o.List.Items[index+1]))) + } + + prev := 0 + if index != 0 { + prev = lines(string(p.objectItem(o.List.Items[index-1]))) + } + + if (cur == next && next == 1) || (next == 1 && cur == 2 && i == 0) { + aligned = append(aligned, item) + index++ + } else if (cur == prev && prev == 1) || (prev == 2 && cur == 1) { + aligned = append(aligned, item) + index++ + } else { + break + } + } + + // fmt.Printf("==================> len(aligned) = %+v\n", len(aligned)) + // for _, b := range aligned { + // fmt.Printf("b = %+v\n", b) + // } + + // put newlines if the items are between other non aligned items + if index != len(aligned) { + buf.WriteByte(newline) + } + + if len(aligned) >= 1 { + p.prev = aligned[len(aligned)-1] + + items := p.alignedItems(aligned) + buf.Write(p.indent(items)) + } else { + p.prev = o.List.Items[index] + + buf.Write(p.indent(p.objectItem(o.List.Items[index]))) + index++ + } + + buf.WriteByte(newline) + + if index == len(o.List.Items) { + break + } + + } + + buf.WriteString("}") + return buf.Bytes() +} + func (p *printer) alignedItems(items []*ast.ObjectItem) []byte { var buf bytes.Buffer @@ -199,82 +329,6 @@ func (p *printer) literal(l *ast.LiteralType) []byte { return []byte(l.Token.Text) } -func (p *printer) objectType(o *ast.ObjectType) []byte { - var buf bytes.Buffer - buf.WriteString("{") - buf.WriteByte(newline) - - var index int - for { - // check if we have adjacent one liner items. If yes we'll going to align - // the comments. - var aligned []*ast.ObjectItem - for i, item := range o.List.Items[index:] { - // we don't group one line lists - if len(o.List.Items) == 1 { - break - } - - // one means a oneliner with out any lead comment - // two means a oneliner with lead comment - // anything else might be something else - cur := lines(string(p.objectItem(item))) - if cur > 2 { - break - } - - next := 0 - if index != len(o.List.Items)-1 { - next = lines(string(p.objectItem(o.List.Items[index+1]))) - } - - prev := 0 - if index != 0 { - prev = lines(string(p.objectItem(o.List.Items[index-1]))) - } - - if (cur == next && next == 1) || (next == 1 && cur == 2 && i == 0) { - aligned = append(aligned, item) - index++ - } else if (cur == prev && prev == 1) || (prev == 2 && cur == 1) { - aligned = append(aligned, item) - index++ - } else { - break - } - } - - // fmt.Printf("==================> len(aligned) = %+v\n", len(aligned)) - // for _, b := range aligned { - // fmt.Printf("b = %+v\n", b) - // } - - // put newlines if the items are between other non aligned items - if index != len(aligned) { - buf.WriteByte(newline) - } - - if len(aligned) >= 1 { - items := p.alignedItems(aligned) - - buf.Write(p.indent(items)) - } else { - buf.Write(p.indent(p.objectItem(o.List.Items[index]))) - index++ - } - - buf.WriteByte(newline) - - if index == len(o.List.Items) { - break - } - - } - - buf.WriteString("}") - return buf.Bytes() -} - // printList prints a HCL list func (p *printer) list(l *ast.ListType) []byte { var buf bytes.Buffer @@ -335,3 +389,35 @@ func lines(txt string) int { } return endline } + +// ---------------------------------------------------------------------------- +// Tracing support + +func (p *printer) printTrace(a ...interface{}) { + if !p.enableTrace { + return + } + + const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + const n = len(dots) + i := 2 * p.indentTrace + for i > n { + fmt.Print(dots) + i -= n + } + // i <= n + fmt.Print(dots[0:i]) + fmt.Println(a...) +} + +func trace(p *printer, msg string) *printer { + p.printTrace(msg, "(") + p.indentTrace++ + return p +} + +// Usage pattern: defer un(trace(p, "...")) +func un(p *printer) { + p.indentTrace-- + p.printTrace(")") +} diff --git a/printer/printer.go b/printer/printer.go index 1d15fc8..af7a939 100644 --- a/printer/printer.go +++ b/printer/printer.go @@ -22,6 +22,8 @@ func (c *Config) Fprint(output io.Writer, node ast.Node) error { cfg: *c, comments: make([]*ast.CommentGroup, 0), standaloneComments: make([]*ast.CommentGroup, 0), + prev: ast.NewNode(), + // enableTrace: true, } p.collectComments(node) diff --git a/printer/testdata/comment.golden b/printer/testdata/comment.golden index 65a4e4e..e77ebe9 100644 --- a/printer/testdata/comment.golden +++ b/printer/testdata/comment.golden @@ -1,9 +1,16 @@ +// A standalone comment is a comment which is not attached to any kind of node + // This comes from Terraform, as a test variable "foo" { + # Standalone comment should be still here + default = "bar" description = "bar" # yooo } +/* This is a multi line standalone +comment*/ + // fatih arslan /* This is a developer test account and a multine comment */ @@ -15,6 +22,12 @@ numbers = [1, 2] // another line here # Another comment variable = { description = "bar" # another yooo + + foo = { + # Nested standalone + + bar = "fatih" + } } // lead comment diff --git a/printer/testdata/comment.input b/printer/testdata/comment.input index 7d4e07a..57c37ac 100644 --- a/printer/testdata/comment.input +++ b/printer/testdata/comment.input @@ -1,9 +1,17 @@ +// A standalone comment is a comment which is not attached to any kind of node + // This comes from Terraform, as a test variable "foo" { + # Standalone comment should be still here + default = "bar" description = "bar" # yooo } +/* This is a multi line standalone +comment*/ + + // fatih arslan /* This is a developer test account and a multine comment */ @@ -15,9 +23,13 @@ numbers = [1,2] // another line here # Another comment variable = { description = "bar" # another yooo + foo { + # Nested standalone + + bar = "fatih" + } } - // lead comment foo { bar = "fatih" // line comment 2 diff --git a/token/position.go b/token/position.go index c151e50..59c1bb7 100644 --- a/token/position.go +++ b/token/position.go @@ -34,3 +34,13 @@ func (p Pos) String() string { } return s } + +// Before reports whether the position p is before u. +func (p Pos) Before(u Pos) bool { + return u.Offset > p.Offset || u.Line > p.Line +} + +// After reports whether the position p is after u. +func (p Pos) After(u Pos) bool { + return u.Offset < p.Offset || u.Line < p.Line +}