github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/hcl2/syntax/comments_test.go (about)

     1  package syntax
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/hashicorp/hcl/v2/hclsyntax"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/zclconf/go-cty/cty"
    13  	"github.com/zclconf/go-cty/cty/convert"
    14  )
    15  
    16  func commentString(trivia []Trivia) string {
    17  	s := ""
    18  	for _, t := range trivia {
    19  		if comment, ok := t.(Comment); ok {
    20  			for _, l := range comment.Lines {
    21  				s += strings.Replace(l, "✱", "*", -1)
    22  			}
    23  		}
    24  	}
    25  	return normString(s)
    26  }
    27  
    28  func validateTokenLeadingTrivia(t *testing.T, token Token) {
    29  	// There is nowhere to attach leading trivia to template control sequences.
    30  	if token.Raw.Type == hclsyntax.TokenTemplateControl {
    31  		assert.Len(t, token.LeadingTrivia, 0)
    32  		return
    33  	}
    34  
    35  	leadingText := commentString(token.LeadingTrivia)
    36  	if !assert.Equal(t, string(token.Raw.Bytes), leadingText) {
    37  		t.Logf("leading trivia mismatch for token @ %v", token.Range())
    38  	}
    39  }
    40  
    41  func validateTokenTrailingTrivia(t *testing.T, token Token) {
    42  	trailingText := commentString(token.TrailingTrivia)
    43  	if trailingText != "" && !assert.Equal(t, string(token.Raw.Bytes), trailingText) {
    44  		t.Logf("trailing trivia mismatch for token @ %v", token.Range())
    45  	}
    46  }
    47  
    48  func validateTokenTrivia(t *testing.T, token Token) {
    49  	validateTokenLeadingTrivia(t, token)
    50  	validateTokenTrailingTrivia(t, token)
    51  }
    52  
    53  func validateTrivia(t *testing.T, tokens ...interface{}) {
    54  	for _, te := range tokens {
    55  		switch te := te.(type) {
    56  		case Token:
    57  			validateTokenTrivia(t, te)
    58  		case *Token:
    59  			if te != nil {
    60  				validateTokenTrivia(t, *te)
    61  			}
    62  		case []Token:
    63  			for _, token := range te {
    64  				validateTokenTrivia(t, token)
    65  			}
    66  		case []ObjectConsItemTokens:
    67  			for _, token := range te {
    68  				validateTrivia(t, token.Equals, token.Comma)
    69  			}
    70  		case []TraverserTokens:
    71  			for _, tt := range te {
    72  				switch token := tt.(type) {
    73  				case *DotTraverserTokens:
    74  					validateTrivia(t, token.Dot, token.Index)
    75  				case *BracketTraverserTokens:
    76  					validateTrivia(t, token.OpenBracket, token.Index, token.CloseBracket)
    77  				}
    78  			}
    79  		}
    80  	}
    81  }
    82  
    83  func validateTemplateStringTrivia(t *testing.T, template *hclsyntax.TemplateExpr, n *hclsyntax.LiteralValueExpr,
    84  	tokens *LiteralValueTokens) {
    85  
    86  	index := -1
    87  	for i := range template.Parts {
    88  		if template.Parts[i] == n {
    89  			index = i
    90  			break
    91  		}
    92  	}
    93  	assert.NotEqual(t, -1, index)
    94  
    95  	v, err := convert.Convert(n.Val, cty.String)
    96  	assert.NoError(t, err)
    97  	if v.AsString() == "" || !assert.Len(t, tokens.Value, 1) {
    98  		return
    99  	}
   100  
   101  	value := tokens.Value[0]
   102  	if index == 0 {
   103  		assert.Len(t, value.LeadingTrivia, 0)
   104  	} else {
   105  		delim, ok := value.LeadingTrivia[0].(TemplateDelimiter)
   106  		assert.True(t, ok)
   107  		assert.Equal(t, hclsyntax.TokenTemplateSeqEnd, delim.Type)
   108  	}
   109  	if index == len(template.Parts)-1 {
   110  		assert.Len(t, value.TrailingTrivia, 0)
   111  	} else if len(value.TrailingTrivia) != 0 {
   112  		if !assert.Len(t, value.TrailingTrivia, 1) {
   113  			return
   114  		}
   115  		delim, ok := value.TrailingTrivia[0].(TemplateDelimiter)
   116  		assert.True(t, ok)
   117  		assert.Equal(t, hclsyntax.TokenTemplateInterp, delim.Type)
   118  	}
   119  }
   120  
   121  type validator struct {
   122  	t      *testing.T
   123  	tokens TokenMap
   124  	stack  []hclsyntax.Node
   125  }
   126  
   127  func (v *validator) Enter(n hclsyntax.Node) hcl.Diagnostics {
   128  	switch n := n.(type) {
   129  	case *hclsyntax.Attribute:
   130  		tokens := v.tokens.ForNode(n).(*AttributeTokens)
   131  		validateTrivia(v.t, tokens.Name, tokens.Equals)
   132  	case *hclsyntax.BinaryOpExpr:
   133  		tokens := v.tokens.ForNode(n).(*BinaryOpTokens)
   134  		validateTrivia(v.t, tokens.Operator)
   135  	case *hclsyntax.Block:
   136  		tokens := v.tokens.ForNode(n).(*BlockTokens)
   137  		validateTrivia(v.t, tokens.Type, tokens.Labels, tokens.OpenBrace, tokens.CloseBrace)
   138  	case *hclsyntax.ConditionalExpr:
   139  		switch tokens := v.tokens.ForNode(n).(type) {
   140  		case *ConditionalTokens:
   141  			validateTrivia(v.t, tokens.QuestionMark, tokens.Colon)
   142  		case *TemplateConditionalTokens:
   143  			validateTrivia(v.t, tokens.OpenIf, tokens.If, tokens.CloseIf, tokens.OpenElse, tokens.Else, tokens.CloseElse,
   144  				tokens.OpenEndif, tokens.Endif, tokens.CloseEndif)
   145  		default:
   146  			v.t.Errorf("unexpected tokens of type %T for conditional expression", tokens)
   147  		}
   148  	case *hclsyntax.ForExpr:
   149  		switch tokens := v.tokens.ForNode(n).(type) {
   150  		case *ForTokens:
   151  			validateTrivia(v.t, tokens.Open, tokens.For, tokens.Key, tokens.Comma, tokens.Value, tokens.In, tokens.Colon,
   152  				tokens.Arrow, tokens.Group, tokens.If, tokens.Close)
   153  		case *TemplateForTokens:
   154  			validateTrivia(v.t, tokens.OpenFor, tokens.For, tokens.CloseFor, tokens.Key, tokens.Comma, tokens.Value, tokens.In,
   155  				tokens.OpenEndfor, tokens.Endfor, tokens.CloseEndfor)
   156  		default:
   157  			v.t.Errorf("unexpected tokens of type %T for for expression", tokens)
   158  		}
   159  	case *hclsyntax.FunctionCallExpr:
   160  		tokens := v.tokens.ForNode(n).(*FunctionCallTokens)
   161  		validateTrivia(v.t, tokens.Name, tokens.OpenParen, tokens.CloseParen)
   162  	case *hclsyntax.IndexExpr:
   163  		tokens := v.tokens.ForNode(n).(*IndexTokens)
   164  		validateTrivia(v.t, tokens.OpenBracket, tokens.CloseBracket)
   165  	case *hclsyntax.LiteralValueExpr:
   166  		template, isTemplateString := (*hclsyntax.TemplateExpr)(nil), false
   167  		if len(v.stack) > 0 && n.Val.Type().Equals(cty.String) {
   168  			template, isTemplateString = v.stack[len(v.stack)-1].(*hclsyntax.TemplateExpr)
   169  		}
   170  
   171  		tokens := v.tokens.ForNode(n).(*LiteralValueTokens)
   172  		if isTemplateString {
   173  			validateTemplateStringTrivia(v.t, template, n, tokens)
   174  		} else {
   175  			validateTrivia(v.t, tokens.Value)
   176  		}
   177  	case *hclsyntax.ObjectConsExpr:
   178  		tokens := v.tokens.ForNode(n).(*ObjectConsTokens)
   179  		validateTrivia(v.t, tokens.OpenBrace, tokens.Items, tokens.CloseBrace)
   180  	case *hclsyntax.RelativeTraversalExpr:
   181  		tokens := v.tokens.ForNode(n).(*RelativeTraversalTokens)
   182  		validateTrivia(v.t, tokens.Traversal)
   183  	case *hclsyntax.ScopeTraversalExpr:
   184  		tokens := v.tokens.ForNode(n).(*ScopeTraversalTokens)
   185  		validateTrivia(v.t, tokens.Root, tokens.Traversal)
   186  	case *hclsyntax.SplatExpr:
   187  		tokens := v.tokens.ForNode(n).(*SplatTokens)
   188  		validateTrivia(v.t, tokens.Open, tokens.Star, tokens.Close)
   189  	case *hclsyntax.TemplateExpr:
   190  		tokens := v.tokens.ForNode(n).(*TemplateTokens)
   191  
   192  		validateTokenLeadingTrivia(v.t, tokens.Open)
   193  		assert.Equal(v.t, "", commentString(tokens.Open.TrailingTrivia))
   194  
   195  		validateTokenTrailingTrivia(v.t, tokens.Close)
   196  		assert.Equal(v.t, "", commentString(tokens.Close.LeadingTrivia))
   197  	case *hclsyntax.TupleConsExpr:
   198  		tokens := v.tokens.ForNode(n).(*TupleConsTokens)
   199  		validateTrivia(v.t, tokens.OpenBracket, tokens.Commas, tokens.CloseBracket)
   200  	case *hclsyntax.UnaryOpExpr:
   201  		tokens := v.tokens.ForNode(n).(*UnaryOpTokens)
   202  		validateTrivia(v.t, tokens.Operator)
   203  	}
   204  
   205  	v.stack = append(v.stack, n)
   206  	return nil
   207  }
   208  
   209  func (v *validator) Exit(n hclsyntax.Node) hcl.Diagnostics {
   210  	v.stack = v.stack[:len(v.stack)-1]
   211  	return nil
   212  }
   213  
   214  func TestComments(t *testing.T) {
   215  	t.Parallel()
   216  
   217  	contents, err := ioutil.ReadFile("./testdata/comments_all.hcl")
   218  	if err != nil {
   219  		t.Fatalf("failed to read test data: %v", err)
   220  	}
   221  
   222  	parser := NewParser()
   223  	err = parser.ParseFile(bytes.NewReader(contents), "comments_all.hcl")
   224  	assert.NoError(t, err)
   225  
   226  	assert.Len(t, parser.Diagnostics, 0)
   227  
   228  	f := parser.Files[0]
   229  	diags := hclsyntax.Walk(f.Body, &validator{t: t, tokens: f.Tokens})
   230  	assert.Nil(t, diags)
   231  }
   232  
   233  func normString(s string) string {
   234  	return strings.TrimSuffix(s, "\r")
   235  }