In general, each item in an HCL2 body must be followed by a trailing newline. The printer did not properly insert these newlines for body items without any associated tokens/trivia, or with trivia that did not include a trailing new line. Related to #1635.
154 lines
4.3 KiB
Go
154 lines
4.3 KiB
Go
// Copyright 2016-2020, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/syntax"
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
|
|
)
|
|
|
|
// BodyItem represents either an *Attribute or a *Block that is part of an HCL2 Body.
|
|
type BodyItem interface {
|
|
printable
|
|
|
|
// SyntaxNode returns syntax node of the item.
|
|
SyntaxNode() hclsyntax.Node
|
|
|
|
isBodyItem()
|
|
}
|
|
|
|
// Body represents an HCL2 body. A Body may be the root of an HCL2 file or the contents of an HCL2 block.
|
|
type Body struct {
|
|
// The syntax node for the body, if any.
|
|
Syntax *hclsyntax.Body
|
|
// The tokens for the body.
|
|
Tokens *syntax.BodyTokens
|
|
|
|
// The items that make up the body's contents.
|
|
Items []BodyItem
|
|
}
|
|
|
|
// SyntaxNode returns the syntax node of the body, and will either return an *hclsyntax.Body or syntax.None.
|
|
func (b *Body) SyntaxNode() hclsyntax.Node {
|
|
return syntaxOrNone(b.Syntax)
|
|
}
|
|
|
|
func (b *Body) HasLeadingTrivia() bool {
|
|
return len(b.Items) > 0 && b.Items[0].HasLeadingTrivia()
|
|
}
|
|
|
|
func (b *Body) HasTrailingTrivia() bool {
|
|
if eof := b.Tokens.GetEndOfFile(); eof != nil {
|
|
return true
|
|
}
|
|
return len(b.Items) > 0 && b.Items[len(b.Items)-1].HasTrailingTrivia()
|
|
}
|
|
|
|
func (b *Body) GetLeadingTrivia() syntax.TriviaList {
|
|
if len(b.Items) == 0 {
|
|
return nil
|
|
}
|
|
return b.Items[0].GetLeadingTrivia()
|
|
}
|
|
|
|
func (b *Body) GetTrailingTrivia() syntax.TriviaList {
|
|
if eof := b.Tokens.GetEndOfFile(); eof != nil {
|
|
return eof.TrailingTrivia
|
|
}
|
|
if len(b.Items) == 0 {
|
|
return nil
|
|
}
|
|
return b.Items[len(b.Items)-1].GetTrailingTrivia()
|
|
}
|
|
|
|
func (b *Body) Format(f fmt.State, c rune) {
|
|
b.print(f, &printer{})
|
|
}
|
|
|
|
func (b *Body) print(w io.Writer, p *printer) {
|
|
// Print the items, separated by newlines.
|
|
for _, item := range b.Items {
|
|
p.fprintf(w, "% v", item)
|
|
if !item.GetTrailingTrivia().EndsOnNewLine() {
|
|
p.fprintf(w, "\n")
|
|
}
|
|
}
|
|
|
|
// If the body has an end-of-file token, print it.
|
|
if b.Tokens.GetEndOfFile() != nil {
|
|
p.fprintf(w, "%v", b.Tokens.EndOfFile)
|
|
}
|
|
}
|
|
|
|
// Attribute returns the attribute with the givne in the body if any exists.
|
|
func (b *Body) Attribute(name string) (*Attribute, bool) {
|
|
for _, item := range b.Items {
|
|
if attr, ok := item.(*Attribute); ok && attr.Name == name {
|
|
return attr, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// Blocks returns all blocks in the body with the given type.
|
|
func (b *Body) Blocks(typ string) []*Block {
|
|
var blocks []*Block
|
|
for _, item := range b.Items {
|
|
if block, ok := item.(*Block); ok && block.Type == typ {
|
|
blocks = append(blocks, block)
|
|
}
|
|
}
|
|
return blocks
|
|
}
|
|
|
|
// BindBody binds an HCL2 body using the given scopes and token map.
|
|
func BindBody(body *hclsyntax.Body, scopes Scopes, tokens syntax.TokenMap,
|
|
opts ...BindOption) (*Body, hcl.Diagnostics) {
|
|
|
|
var diagnostics hcl.Diagnostics
|
|
|
|
syntaxItems := SourceOrderBody(body)
|
|
items := make([]BodyItem, len(syntaxItems))
|
|
for i, syntaxItem := range syntaxItems {
|
|
var itemDiags hcl.Diagnostics
|
|
switch syntaxItem := syntaxItem.(type) {
|
|
case *hclsyntax.Attribute:
|
|
scope, scopeDiags := scopes.GetScopeForAttribute(syntaxItem)
|
|
diagnostics = append(diagnostics, scopeDiags...)
|
|
|
|
items[i], itemDiags = BindAttribute(syntaxItem, scope, tokens, opts...)
|
|
case *hclsyntax.Block:
|
|
scopes, scopesDiags := scopes.GetScopesForBlock(syntaxItem)
|
|
diagnostics = append(diagnostics, scopesDiags...)
|
|
|
|
items[i], itemDiags = BindBlock(syntaxItem, scopes, tokens, opts...)
|
|
default:
|
|
contract.Failf("unexpected syntax item of type %T (%v)", syntaxItem, syntaxItem.Range())
|
|
}
|
|
diagnostics = append(diagnostics, itemDiags...)
|
|
}
|
|
|
|
bodyTokens, _ := tokens.ForNode(body).(*syntax.BodyTokens)
|
|
return &Body{
|
|
Syntax: body,
|
|
Tokens: bodyTokens,
|
|
Items: items,
|
|
}, diagnostics
|
|
}
|