c03d57b578
Fixes https://github.com/hashicorp/terraform/issues/8886 While parsing JSON, an empty list value would be assumed to be a non-existent list of objects, and would be removed from the result. This may have never been the correct behavior but always worked okay because we previously didn't support lists as first class types. With the support of lists, we need to actually return the empty list as the type. If we return nothing, then projects like Terraform will think that the value was never set, which is false.
118 lines
3.0 KiB
Go
118 lines
3.0 KiB
Go
package parser
|
|
|
|
import "github.com/hashicorp/hcl/hcl/ast"
|
|
|
|
// flattenObjects takes an AST node, walks it, and flattens
|
|
func flattenObjects(node ast.Node) {
|
|
ast.Walk(node, func(n ast.Node) (ast.Node, bool) {
|
|
// We only care about lists, because this is what we modify
|
|
list, ok := n.(*ast.ObjectList)
|
|
if !ok {
|
|
return n, true
|
|
}
|
|
|
|
// Rebuild the item list
|
|
items := make([]*ast.ObjectItem, 0, len(list.Items))
|
|
frontier := make([]*ast.ObjectItem, len(list.Items))
|
|
copy(frontier, list.Items)
|
|
for len(frontier) > 0 {
|
|
// Pop the current item
|
|
n := len(frontier)
|
|
item := frontier[n-1]
|
|
frontier = frontier[:n-1]
|
|
|
|
switch v := item.Val.(type) {
|
|
case *ast.ObjectType:
|
|
items, frontier = flattenObjectType(v, item, items, frontier)
|
|
case *ast.ListType:
|
|
items, frontier = flattenListType(v, item, items, frontier)
|
|
default:
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
|
|
// Reverse the list since the frontier model runs things backwards
|
|
for i := len(items)/2 - 1; i >= 0; i-- {
|
|
opp := len(items) - 1 - i
|
|
items[i], items[opp] = items[opp], items[i]
|
|
}
|
|
|
|
// Done! Set the original items
|
|
list.Items = items
|
|
return n, true
|
|
})
|
|
}
|
|
|
|
func flattenListType(
|
|
ot *ast.ListType,
|
|
item *ast.ObjectItem,
|
|
items []*ast.ObjectItem,
|
|
frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
|
|
// If the list is empty, keep the original list
|
|
if len(ot.List) == 0 {
|
|
items = append(items, item)
|
|
return items, frontier
|
|
}
|
|
|
|
// All the elements of this object must also be objects!
|
|
for _, subitem := range ot.List {
|
|
if _, ok := subitem.(*ast.ObjectType); !ok {
|
|
items = append(items, item)
|
|
return items, frontier
|
|
}
|
|
}
|
|
|
|
// Great! We have a match go through all the items and flatten
|
|
for _, elem := range ot.List {
|
|
// Add it to the frontier so that we can recurse
|
|
frontier = append(frontier, &ast.ObjectItem{
|
|
Keys: item.Keys,
|
|
Assign: item.Assign,
|
|
Val: elem,
|
|
LeadComment: item.LeadComment,
|
|
LineComment: item.LineComment,
|
|
})
|
|
}
|
|
|
|
return items, frontier
|
|
}
|
|
|
|
func flattenObjectType(
|
|
ot *ast.ObjectType,
|
|
item *ast.ObjectItem,
|
|
items []*ast.ObjectItem,
|
|
frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
|
|
// If the list has no items we do not have to flatten anything
|
|
if ot.List.Items == nil {
|
|
items = append(items, item)
|
|
return items, frontier
|
|
}
|
|
|
|
// All the elements of this object must also be objects!
|
|
for _, subitem := range ot.List.Items {
|
|
if _, ok := subitem.Val.(*ast.ObjectType); !ok {
|
|
items = append(items, item)
|
|
return items, frontier
|
|
}
|
|
}
|
|
|
|
// Great! We have a match go through all the items and flatten
|
|
for _, subitem := range ot.List.Items {
|
|
// Copy the new key
|
|
keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys))
|
|
copy(keys, item.Keys)
|
|
copy(keys[len(item.Keys):], subitem.Keys)
|
|
|
|
// Add it to the frontier so that we can recurse
|
|
frontier = append(frontier, &ast.ObjectItem{
|
|
Keys: keys,
|
|
Assign: item.Assign,
|
|
Val: subitem.Val,
|
|
LeadComment: item.LeadComment,
|
|
LineComment: item.LineComment,
|
|
})
|
|
}
|
|
|
|
return items, frontier
|
|
}
|