printer: Simplify the formatting of single-line lists.

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.
This commit is contained in:
Florian Forster 2018-03-27 14:23:59 +02:00
parent 26f11450d7
commit b07f6fa7a2
4 changed files with 116 additions and 102 deletions

View File

@ -517,8 +517,13 @@ func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {
// list returns the printable HCL form of an list type. // list returns the printable HCL form of an list type.
func (p *printer) list(l *ast.ListType) []byte { func (p *printer) list(l *ast.ListType) []byte {
if p.isSingleLineList(l) {
return p.singleLineList(l)
}
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString("[") buf.WriteString("[")
buf.WriteByte(newline)
var longestLine int var longestLine int
for _, item := range l.List { for _, item := range l.List {
@ -531,32 +536,16 @@ func (p *printer) list(l *ast.ListType) []byte {
} }
} }
insertSpaceBeforeItem := false haveEmptyLine := false
lastHadLeadComment := false
for i, item := range l.List { 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 // If we have a lead comment, then we want to write that first
leadComment := false leadComment := false
if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil { if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil {
leadComment = true leadComment = true
// If this isn't the first item and the previous element // Ensure an empty line before every element with a
// didn't have a lead comment, then we need to add an extra // lead comment (except the first item in a list).
// newline to properly space things out. If it did have a if !haveEmptyLine && i != 0 {
// lead comment previously then this would be done
// automatically.
if i > 0 && !lastHadLeadComment {
buf.WriteByte(newline) buf.WriteByte(newline)
} }
@ -574,7 +563,7 @@ func (p *printer) list(l *ast.ListType) []byte {
// if this item is a heredoc, then we output the comma on // if this item is a heredoc, then we output the comma on
// the next line. This is the only case this happens. // the next line. This is the only case this happens.
comma := []byte{','} comma := []byte{','}
if heredoc { if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
buf.WriteByte(newline) buf.WriteByte(newline)
comma = p.indent(comma) comma = p.indent(comma)
} }
@ -593,53 +582,66 @@ func (p *printer) list(l *ast.ListType) []byte {
} }
} }
lastItem := i == len(l.List)-1 buf.WriteByte(newline)
if lastItem {
// 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) buf.WriteByte(newline)
} }
if leadComment && !lastItem {
buf.WriteByte(newline)
} }
lastHadLeadComment = leadComment buf.WriteString("]")
} else { return buf.Bytes()
if insertSpaceBeforeItem { }
buf.WriteByte(blank)
insertSpaceBeforeItem = false // 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
}
lit, ok := item.(*ast.LiteralType)
if !ok {
return false
}
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 // Output the item itself
// also indent each line buf.Write(p.output(item))
val := p.output(item)
curLen := len(val)
buf.Write(val)
// If this is a heredoc item we always have to output a newline // The heredoc marker needs to be at the end of line.
// so that it parses properly. if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
if heredoc {
buf.WriteByte(newline) buf.WriteByte(newline)
} }
// If this isn't the last element, write a comma.
if i != len(l.List)-1 {
buf.WriteString(",")
insertSpaceBeforeItem = true
}
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)
}
for _, comment := range lit.LineComment.List {
buf.WriteString(comment.Text)
}
}
}
} }
buf.WriteString("]") buf.WriteString("]")

View File

@ -2,11 +2,14 @@ foo = ["fatih", "arslan"]
foo = ["bar", "qaz"] foo = ["bar", "qaz"]
foo = ["zeynep", foo = [
"zeynep",
"arslan", "arslan",
] ]
foo = ["fatih", "zeynep", foo = [
"fatih",
"zeynep",
"arslan", "arslan",
] ]

View File

@ -1,7 +1,13 @@
foo = [1, # Hello foo = [
1, # Hello
2, 2,
] ]
foo = [1, # Hello foo = [
1, # Hello
2, # World 2, # World
] ]
foo = [
1, # Hello
]

View File

@ -4,3 +4,6 @@ foo = [1, # Hello
foo = [1, # Hello foo = [1, # Hello
2, # World 2, # World
] ]
foo = [1, # Hello
]