Skip to content

Commit

Permalink
printer: Simplify the formatting of single-line lists.
Browse files Browse the repository at this point in the history
This change splits out the formatting of simple single-line lists. A list
is considered "simple" if all of its elements are on one line, all
elements are literals (except heredoc) and there are no line comments.
As an exception, a heredoc string is allowed when it is the only element
in the list.

This fixes an issue with a single-line list with one element and a line
comment. The formatter used to pull the closing bracket on the same line
(after the comment), causing parse errors.
  • Loading branch information
octo committed Apr 3, 2018
1 parent 1094e8f commit 396cf29
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 93 deletions.
180 changes: 91 additions & 89 deletions hcl/printer/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,8 +509,13 @@ func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {

// list returns the printable HCL form of an list type.
func (p *printer) list(l *ast.ListType) []byte {
if p.isSingleLineList(l) {
return p.singleLineList(l)
}

var buf bytes.Buffer
buf.WriteString("[")
buf.WriteByte(newline)

var longestLine int
for _, item := range l.List {
Expand All @@ -523,115 +528,112 @@ func (p *printer) list(l *ast.ListType) []byte {
}
}

insertSpaceBeforeItem := false
lastHadLeadComment := false
haveEmptyLine := false
for i, item := range l.List {
// Keep track of whether this item is a heredoc since that has
// unique behavior.
heredoc := false
if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
heredoc = true
}

if item.Pos().Line != l.Lbrack.Line {
// multiline list, add newline before we add each item
buf.WriteByte(newline)
insertSpaceBeforeItem = false

// If we have a lead comment, then we want to write that first
leadComment := false
if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil {
leadComment = true

// If this isn't the first item and the previous element
// didn't have a lead comment, then we need to add an extra
// newline to properly space things out. If it did have a
// lead comment previously then this would be done
// automatically.
if i > 0 && !lastHadLeadComment {
buf.WriteByte(newline)
}

for _, comment := range lit.LeadComment.List {
buf.Write(p.indent([]byte(comment.Text)))
buf.WriteByte(newline)
}
// If we have a lead comment, then we want to write that first
leadComment := false
if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil {
leadComment = true

// Ensure an empty line before every element with a
// lead comment (except the first item in a list).
if !haveEmptyLine && i != 0 {
buf.WriteByte(newline)
}

// also indent each line
val := p.output(item)
curLen := len(val)
buf.Write(p.indent(val))

// if this item is a heredoc, then we output the comma on
// the next line. This is the only case this happens.
comma := []byte{','}
if heredoc {
for _, comment := range lit.LeadComment.List {
buf.Write(p.indent([]byte(comment.Text)))
buf.WriteByte(newline)
comma = p.indent(comma)
}
}

buf.Write(comma)
// also indent each line
val := p.output(item)
curLen := len(val)
buf.Write(p.indent(val))

if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
// if the next item doesn't have any comments, do not align
buf.WriteByte(blank) // align one space
for i := 0; i < longestLine-curLen; i++ {
buf.WriteByte(blank)
}
// if this item is a heredoc, then we output the comma on
// the next line. This is the only case this happens.
comma := []byte{','}
if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
buf.WriteByte(newline)
comma = p.indent(comma)
}

for _, comment := range lit.LineComment.List {
buf.WriteString(comment.Text)
}
}
buf.Write(comma)

lastItem := i == len(l.List)-1
if lastItem {
buf.WriteByte(newline)
if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
// if the next item doesn't have any comments, do not align
buf.WriteByte(blank) // align one space
for i := 0; i < longestLine-curLen; i++ {
buf.WriteByte(blank)
}

if leadComment && !lastItem {
buf.WriteByte(newline)
for _, comment := range lit.LineComment.List {
buf.WriteString(comment.Text)
}
}

lastHadLeadComment = leadComment
} else {
if insertSpaceBeforeItem {
buf.WriteByte(blank)
insertSpaceBeforeItem = false
}
buf.WriteByte(newline)

// Output the item itself
// also indent each line
val := p.output(item)
curLen := len(val)
buf.Write(val)
// Ensure an empty line after every element with a
// lead comment (except the first item in a list).
haveEmptyLine = leadComment && i != len(l.List)-1
if haveEmptyLine {
buf.WriteByte(newline)
}
}

// If this is a heredoc item we always have to output a newline
// so that it parses properly.
if heredoc {
buf.WriteByte(newline)
}
buf.WriteString("]")
return buf.Bytes()
}

// If this isn't the last element, write a comma.
if i != len(l.List)-1 {
buf.WriteString(",")
insertSpaceBeforeItem = true
}
// isSingleLineList returns true if:
// * they were previously formatted entirely on one line
// * they consist entirely of literals
// * there are either no heredoc strings or the list has exactly one element
// * there are no line comments
func (printer) isSingleLineList(l *ast.ListType) bool {
for _, item := range l.List {
if item.Pos().Line != l.Lbrack.Line {
return false
}

if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
// if the next item doesn't have any comments, do not align
buf.WriteByte(blank) // align one space
for i := 0; i < longestLine-curLen; i++ {
buf.WriteByte(blank)
}
lit, ok := item.(*ast.LiteralType)
if !ok {
return false
}

for _, comment := range lit.LineComment.List {
buf.WriteString(comment.Text)
}
}
if lit.Token.Type == token.HEREDOC && len(l.List) != 1 {
return false
}

if lit.LineComment != nil {
return false
}
}

return true
}

// singleLineList prints a simple single line list.
// For a definition of "simple", see isSingleLineList above.
func (p *printer) singleLineList(l *ast.ListType) []byte {
buf := &bytes.Buffer{}

buf.WriteString("[")
for i, item := range l.List {
if i != 0 {
buf.WriteString(", ")
}

// Output the item itself
buf.Write(p.output(item))

// The heredoc marker needs to be at the end of line.
if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
buf.WriteByte(newline)
}
}

buf.WriteString("]")
Expand Down
7 changes: 5 additions & 2 deletions hcl/printer/testdata/list.golden
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ foo = ["fatih", "arslan"]

foo = ["bar", "qaz"]

foo = ["zeynep",
foo = [
"zeynep",
"arslan",
]

foo = ["fatih", "zeynep",
foo = [
"fatih",
"zeynep",
"arslan",
]

Expand Down
10 changes: 8 additions & 2 deletions hcl/printer/testdata/list_comment.golden
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
foo = [1, # Hello
foo = [
1, # Hello
2,
]

foo = [1, # Hello
foo = [
1, # Hello
2, # World
]

foo = [
1, # Hello
]
3 changes: 3 additions & 0 deletions hcl/printer/testdata/list_comment.input
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ foo = [1, # Hello
foo = [1, # Hello
2, # World
]

foo = [1, # Hello
]

0 comments on commit 396cf29

Please sign in to comment.