github.com/hashicorp/hcl/v2@v2.20.0/hclsyntax/structure_at_pos_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclsyntax 5 6 import ( 7 "reflect" 8 "testing" 9 10 "github.com/hashicorp/hcl/v2" 11 ) 12 13 func TestBlocksAtPos(t *testing.T) { 14 tests := map[string]struct { 15 Src string 16 Pos hcl.Pos 17 WantTypes []string 18 }{ 19 "empty": { 20 ``, 21 hcl.Pos{Byte: 0}, 22 nil, 23 }, 24 "spaces": { 25 ` `, 26 hcl.Pos{Byte: 1}, 27 nil, 28 }, 29 "single in header": { 30 `foo {}`, 31 hcl.Pos{Byte: 1}, 32 []string{"foo"}, 33 }, 34 "single in body": { 35 `foo { }`, 36 hcl.Pos{Byte: 7}, 37 []string{"foo"}, 38 }, 39 "single in body with unselected nested": { 40 ` 41 foo { 42 43 bar { 44 45 } 46 } 47 `, 48 hcl.Pos{Byte: 10}, 49 []string{"foo"}, 50 }, 51 "single in body with unselected sibling": { 52 ` 53 foo { } 54 bar { } 55 `, 56 hcl.Pos{Byte: 10}, 57 []string{"foo"}, 58 }, 59 "selected nested two levels": { 60 ` 61 foo { 62 bar { 63 64 } 65 } 66 `, 67 hcl.Pos{Byte: 20}, 68 []string{"foo", "bar"}, 69 }, 70 "selected nested three levels": { 71 ` 72 foo { 73 bar { 74 baz { 75 76 } 77 } 78 } 79 `, 80 hcl.Pos{Byte: 31}, 81 []string{"foo", "bar", "baz"}, 82 }, 83 "selected nested three levels with unselected sibling after": { 84 ` 85 foo { 86 bar { 87 baz { 88 89 } 90 } 91 not_wanted {} 92 } 93 `, 94 hcl.Pos{Byte: 31}, 95 []string{"foo", "bar", "baz"}, 96 }, 97 "selected nested three levels with unselected sibling before": { 98 ` 99 foo { 100 not_wanted {} 101 bar { 102 baz { 103 104 } 105 } 106 } 107 `, 108 hcl.Pos{Byte: 49}, 109 []string{"foo", "bar", "baz"}, 110 }, 111 "unterminated": { 112 `foo { `, 113 hcl.Pos{Byte: 7}, 114 []string{"foo"}, 115 }, 116 "unterminated nested": { 117 ` 118 foo { 119 bar { 120 } 121 `, 122 hcl.Pos{Byte: 16}, 123 []string{"foo", "bar"}, 124 }, 125 } 126 127 for name, test := range tests { 128 t.Run(name, func(t *testing.T) { 129 f, diags := ParseConfig([]byte(test.Src), "", hcl.Pos{Line: 1, Column: 1}) 130 for _, diag := range diags { 131 // We intentionally ignore diagnostics here because we should be 132 // able to work with the incomplete configuration that results 133 // when the parser does its recovery behavior. However, we do 134 // log them in case it's helpful to someone debugging a failing 135 // test. 136 t.Logf(diag.Error()) 137 } 138 139 blocks := f.BlocksAtPos(test.Pos) 140 outermost := f.OutermostBlockAtPos(test.Pos) 141 innermost := f.InnermostBlockAtPos(test.Pos) 142 143 gotTypes := make([]string, len(blocks)) 144 for i, block := range blocks { 145 gotTypes[i] = block.Type 146 } 147 148 if len(test.WantTypes) == 0 { 149 if len(gotTypes) != 0 { 150 t.Errorf("wrong block types\ngot: %#v\nwant: (none)", gotTypes) 151 } 152 if outermost != nil { 153 t.Errorf("wrong outermost type\ngot: %#v\nwant: (none)", outermost.Type) 154 } 155 if innermost != nil { 156 t.Errorf("wrong innermost type\ngot: %#v\nwant: (none)", innermost.Type) 157 } 158 return 159 } 160 161 if !reflect.DeepEqual(gotTypes, test.WantTypes) { 162 if len(gotTypes) != 0 { 163 t.Errorf("wrong block types\ngot: %#v\nwant: %#v", gotTypes, test.WantTypes) 164 } 165 } 166 if got, want := outermost.Type, test.WantTypes[0]; got != want { 167 t.Errorf("wrong outermost type\ngot: %#v\nwant: %#v", got, want) 168 } 169 if got, want := innermost.Type, test.WantTypes[len(test.WantTypes)-1]; got != want { 170 t.Errorf("wrong innermost type\ngot: %#v\nwant: %#v", got, want) 171 } 172 }) 173 } 174 } 175 176 func TestAttributeAtPos(t *testing.T) { 177 tests := map[string]struct { 178 Src string 179 Pos hcl.Pos 180 WantName string 181 }{ 182 "empty": { 183 ``, 184 hcl.Pos{Byte: 0}, 185 "", 186 }, 187 "top-level": { 188 `foo = 1`, 189 hcl.Pos{Byte: 0}, 190 "foo", 191 }, 192 "top-level with ignored sibling after": { 193 ` 194 foo = 1 195 bar = 2 196 `, 197 hcl.Pos{Byte: 6}, 198 "foo", 199 }, 200 "top-level ignored sibling before": { 201 ` 202 foo = 1 203 bar = 2 204 `, 205 hcl.Pos{Byte: 17}, 206 "bar", 207 }, 208 "nested": { 209 ` 210 foo { 211 bar = 2 212 } 213 `, 214 hcl.Pos{Byte: 17}, 215 "bar", 216 }, 217 "nested in unterminated block": { 218 ` 219 foo { 220 bar = 2 221 `, 222 hcl.Pos{Byte: 17}, 223 "bar", 224 }, 225 } 226 227 for name, test := range tests { 228 t.Run(name, func(t *testing.T) { 229 f, diags := ParseConfig([]byte(test.Src), "", hcl.Pos{Line: 1, Column: 1}) 230 for _, diag := range diags { 231 // We intentionally ignore diagnostics here because we should be 232 // able to work with the incomplete configuration that results 233 // when the parser does its recovery behavior. However, we do 234 // log them in case it's helpful to someone debugging a failing 235 // test. 236 t.Logf(diag.Error()) 237 } 238 239 got := f.AttributeAtPos(test.Pos) 240 241 if test.WantName == "" { 242 if got != nil { 243 t.Errorf("wrong attribute name\ngot: %#v\nwant: (none)", got.Name) 244 } 245 return 246 } 247 248 if got == nil { 249 t.Fatalf("wrong attribute name\ngot: (none)\nwant: %#v", test.WantName) 250 } 251 252 if got.Name != test.WantName { 253 t.Errorf("wrong attribute name\ngot: %#v\nwant: %#v", got.Name, test.WantName) 254 } 255 }) 256 } 257 } 258 259 func TestOutermostExprAtPos(t *testing.T) { 260 tests := map[string]struct { 261 Src string 262 Pos hcl.Pos 263 WantSrc string 264 }{ 265 "empty": { 266 ``, 267 hcl.Pos{Byte: 0}, 268 ``, 269 }, 270 "simple bool": { 271 `a = true`, 272 hcl.Pos{Byte: 6}, 273 `true`, 274 }, 275 "simple reference": { 276 `a = blah`, 277 hcl.Pos{Byte: 6}, 278 `blah`, 279 }, 280 "attribute reference": { 281 `a = blah.foo`, 282 hcl.Pos{Byte: 6}, 283 `blah.foo`, 284 }, 285 "parens": { 286 `a = (1 + 1)`, 287 hcl.Pos{Byte: 6}, 288 `(1 + 1)`, 289 }, 290 "tuple cons": { 291 `a = [1, 2, 3]`, 292 hcl.Pos{Byte: 5}, 293 `[1, 2, 3]`, 294 }, 295 "function call": { 296 `a = foom("a")`, 297 hcl.Pos{Byte: 10}, 298 `foom("a")`, 299 }, 300 } 301 302 for name, test := range tests { 303 t.Run(name, func(t *testing.T) { 304 inputSrc := []byte(test.Src) 305 f, diags := ParseConfig(inputSrc, "", hcl.Pos{Line: 1, Column: 1}) 306 for _, diag := range diags { 307 // We intentionally ignore diagnostics here because we should be 308 // able to work with the incomplete configuration that results 309 // when the parser does its recovery behavior. However, we do 310 // log them in case it's helpful to someone debugging a failing 311 // test. 312 t.Logf(diag.Error()) 313 } 314 315 gotExpr := f.OutermostExprAtPos(test.Pos) 316 var gotSrc string 317 if gotExpr != nil { 318 rng := gotExpr.Range() 319 gotSrc = string(rng.SliceBytes(inputSrc)) 320 } 321 322 if test.WantSrc == "" { 323 if gotExpr != nil { 324 t.Errorf("wrong expression source\ngot: %s\nwant: (none)", gotSrc) 325 } 326 return 327 } 328 329 if gotExpr == nil { 330 t.Fatalf("wrong expression source\ngot: (none)\nwant: %s", test.WantSrc) 331 } 332 333 if gotSrc != test.WantSrc { 334 t.Errorf("wrong expression source\ngot: %#v\nwant: %#v", gotSrc, test.WantSrc) 335 } 336 }) 337 } 338 }