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 }