github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/addrs/parse_ref_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package addrs 5 6 import ( 7 "testing" 8 9 "github.com/go-test/deep" 10 "github.com/hashicorp/hcl/v2" 11 "github.com/hashicorp/hcl/v2/hclsyntax" 12 "github.com/zclconf/go-cty/cty" 13 14 "github.com/terramate-io/tf/tfdiags" 15 ) 16 17 func TestParseRefInTestingScope(t *testing.T) { 18 tests := []struct { 19 Input string 20 Want *Reference 21 WantErr string 22 }{ 23 { 24 `output.value`, 25 &Reference{ 26 Subject: OutputValue{ 27 Name: "value", 28 }, 29 SourceRange: tfdiags.SourceRange{ 30 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 31 End: tfdiags.SourcePos{Line: 1, Column: 13, Byte: 12}, 32 }, 33 }, 34 ``, 35 }, 36 { 37 `output`, 38 nil, 39 `The "output" object cannot be accessed directly. Instead, access one of its attributes.`, 40 }, 41 { 42 `output["foo"]`, 43 nil, 44 `The "output" object does not support this operation.`, 45 }, 46 47 { 48 `check.health`, 49 &Reference{ 50 Subject: Check{ 51 Name: "health", 52 }, 53 SourceRange: tfdiags.SourceRange{ 54 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 55 End: tfdiags.SourcePos{Line: 1, Column: 13, Byte: 12}, 56 }, 57 }, 58 ``, 59 }, 60 { 61 `check`, 62 nil, 63 `The "check" object cannot be accessed directly. Instead, access one of its attributes.`, 64 }, 65 { 66 `check["foo"]`, 67 nil, 68 `The "check" object does not support this operation.`, 69 }, 70 71 // Sanity check at least one of the others works to verify it does 72 // fall through to the core function. 73 { 74 `count.index`, 75 &Reference{ 76 Subject: CountAttr{ 77 Name: "index", 78 }, 79 SourceRange: tfdiags.SourceRange{ 80 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 81 End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11}, 82 }, 83 }, 84 ``, 85 }, 86 } 87 for _, test := range tests { 88 t.Run(test.Input, func(t *testing.T) { 89 traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1}) 90 if travDiags.HasErrors() { 91 t.Fatal(travDiags.Error()) 92 } 93 94 got, diags := ParseRefFromTestingScope(traversal) 95 96 switch len(diags) { 97 case 0: 98 if test.WantErr != "" { 99 t.Fatalf("succeeded; want error: %s", test.WantErr) 100 } 101 case 1: 102 if test.WantErr == "" { 103 t.Fatalf("unexpected diagnostics: %s", diags.Err()) 104 } 105 if got, want := diags[0].Description().Detail, test.WantErr; got != want { 106 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 107 } 108 default: 109 t.Fatalf("too many diagnostics: %s", diags.Err()) 110 } 111 112 if diags.HasErrors() { 113 return 114 } 115 116 for _, problem := range deep.Equal(got, test.Want) { 117 t.Errorf(problem) 118 } 119 }) 120 } 121 } 122 123 func TestParseRef(t *testing.T) { 124 tests := []struct { 125 Input string 126 Want *Reference 127 WantErr string 128 }{ 129 130 // count 131 { 132 `count.index`, 133 &Reference{ 134 Subject: CountAttr{ 135 Name: "index", 136 }, 137 SourceRange: tfdiags.SourceRange{ 138 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 139 End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11}, 140 }, 141 }, 142 ``, 143 }, 144 { 145 `count.index.blah`, 146 &Reference{ 147 Subject: CountAttr{ 148 Name: "index", 149 }, 150 SourceRange: tfdiags.SourceRange{ 151 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 152 End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11}, 153 }, 154 Remaining: hcl.Traversal{ 155 hcl.TraverseAttr{ 156 Name: "blah", 157 SrcRange: hcl.Range{ 158 Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 159 End: hcl.Pos{Line: 1, Column: 17, Byte: 16}, 160 }, 161 }, 162 }, 163 }, 164 ``, // valid at this layer, but will fail during eval because "index" is a number 165 }, 166 { 167 `count`, 168 nil, 169 `The "count" object cannot be accessed directly. Instead, access one of its attributes.`, 170 }, 171 { 172 `count["hello"]`, 173 nil, 174 `The "count" object does not support this operation.`, 175 }, 176 177 // each 178 { 179 `each.key`, 180 &Reference{ 181 Subject: ForEachAttr{ 182 Name: "key", 183 }, 184 SourceRange: tfdiags.SourceRange{ 185 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 186 End: tfdiags.SourcePos{Line: 1, Column: 9, Byte: 8}, 187 }, 188 }, 189 ``, 190 }, 191 { 192 `each.value.blah`, 193 &Reference{ 194 Subject: ForEachAttr{ 195 Name: "value", 196 }, 197 SourceRange: tfdiags.SourceRange{ 198 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 199 End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10}, 200 }, 201 Remaining: hcl.Traversal{ 202 hcl.TraverseAttr{ 203 Name: "blah", 204 SrcRange: hcl.Range{ 205 Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, 206 End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, 207 }, 208 }, 209 }, 210 }, 211 ``, 212 }, 213 { 214 `each`, 215 nil, 216 `The "each" object cannot be accessed directly. Instead, access one of its attributes.`, 217 }, 218 { 219 `each["hello"]`, 220 nil, 221 `The "each" object does not support this operation.`, 222 }, 223 // data 224 { 225 `data.external.foo`, 226 &Reference{ 227 Subject: Resource{ 228 Mode: DataResourceMode, 229 Type: "external", 230 Name: "foo", 231 }, 232 SourceRange: tfdiags.SourceRange{ 233 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 234 End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, 235 }, 236 }, 237 ``, 238 }, 239 { 240 `data.external.foo.bar`, 241 &Reference{ 242 Subject: ResourceInstance{ 243 Resource: Resource{ 244 Mode: DataResourceMode, 245 Type: "external", 246 Name: "foo", 247 }, 248 }, 249 SourceRange: tfdiags.SourceRange{ 250 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 251 End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, 252 }, 253 Remaining: hcl.Traversal{ 254 hcl.TraverseAttr{ 255 Name: "bar", 256 SrcRange: hcl.Range{ 257 Start: hcl.Pos{Line: 1, Column: 18, Byte: 17}, 258 End: hcl.Pos{Line: 1, Column: 22, Byte: 21}, 259 }, 260 }, 261 }, 262 }, 263 ``, 264 }, 265 { 266 `data.external.foo["baz"].bar`, 267 &Reference{ 268 Subject: ResourceInstance{ 269 Resource: Resource{ 270 Mode: DataResourceMode, 271 Type: "external", 272 Name: "foo", 273 }, 274 Key: StringKey("baz"), 275 }, 276 SourceRange: tfdiags.SourceRange{ 277 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 278 End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24}, 279 }, 280 Remaining: hcl.Traversal{ 281 hcl.TraverseAttr{ 282 Name: "bar", 283 SrcRange: hcl.Range{ 284 Start: hcl.Pos{Line: 1, Column: 25, Byte: 24}, 285 End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, 286 }, 287 }, 288 }, 289 }, 290 ``, 291 }, 292 { 293 `data.external.foo["baz"]`, 294 &Reference{ 295 Subject: ResourceInstance{ 296 Resource: Resource{ 297 Mode: DataResourceMode, 298 Type: "external", 299 Name: "foo", 300 }, 301 Key: StringKey("baz"), 302 }, 303 SourceRange: tfdiags.SourceRange{ 304 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 305 End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24}, 306 }, 307 }, 308 ``, 309 }, 310 { 311 `data`, 312 nil, 313 `The "data" object must be followed by two attribute names: the data source type and the resource name.`, 314 }, 315 { 316 `data.external`, 317 nil, 318 `The "data" object must be followed by two attribute names: the data source type and the resource name.`, 319 }, 320 321 // local 322 { 323 `local.foo`, 324 &Reference{ 325 Subject: LocalValue{ 326 Name: "foo", 327 }, 328 SourceRange: tfdiags.SourceRange{ 329 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 330 End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9}, 331 }, 332 }, 333 ``, 334 }, 335 { 336 `local.foo.blah`, 337 &Reference{ 338 Subject: LocalValue{ 339 Name: "foo", 340 }, 341 SourceRange: tfdiags.SourceRange{ 342 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 343 End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9}, 344 }, 345 Remaining: hcl.Traversal{ 346 hcl.TraverseAttr{ 347 Name: "blah", 348 SrcRange: hcl.Range{ 349 Start: hcl.Pos{Line: 1, Column: 10, Byte: 9}, 350 End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, 351 }, 352 }, 353 }, 354 }, 355 ``, 356 }, 357 { 358 `local.foo["blah"]`, 359 &Reference{ 360 Subject: LocalValue{ 361 Name: "foo", 362 }, 363 SourceRange: tfdiags.SourceRange{ 364 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 365 End: tfdiags.SourcePos{Line: 1, Column: 10, Byte: 9}, 366 }, 367 Remaining: hcl.Traversal{ 368 hcl.TraverseIndex{ 369 Key: cty.StringVal("blah"), 370 SrcRange: hcl.Range{ 371 Start: hcl.Pos{Line: 1, Column: 10, Byte: 9}, 372 End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, 373 }, 374 }, 375 }, 376 }, 377 ``, 378 }, 379 { 380 `local`, 381 nil, 382 `The "local" object cannot be accessed directly. Instead, access one of its attributes.`, 383 }, 384 { 385 `local["foo"]`, 386 nil, 387 `The "local" object does not support this operation.`, 388 }, 389 390 // module 391 { 392 `module.foo`, 393 &Reference{ 394 Subject: ModuleCall{ 395 Name: "foo", 396 }, 397 SourceRange: tfdiags.SourceRange{ 398 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 399 End: tfdiags.SourcePos{Line: 1, Column: 11, Byte: 10}, 400 }, 401 }, 402 ``, 403 }, 404 { 405 `module.foo.bar`, 406 &Reference{ 407 Subject: ModuleCallInstanceOutput{ 408 Call: ModuleCallInstance{ 409 Call: ModuleCall{ 410 Name: "foo", 411 }, 412 }, 413 Name: "bar", 414 }, 415 SourceRange: tfdiags.SourceRange{ 416 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 417 End: tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14}, 418 }, 419 }, 420 ``, 421 }, 422 { 423 `module.foo.bar.baz`, 424 &Reference{ 425 Subject: ModuleCallInstanceOutput{ 426 Call: ModuleCallInstance{ 427 Call: ModuleCall{ 428 Name: "foo", 429 }, 430 }, 431 Name: "bar", 432 }, 433 SourceRange: tfdiags.SourceRange{ 434 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 435 End: tfdiags.SourcePos{Line: 1, Column: 15, Byte: 14}, 436 }, 437 Remaining: hcl.Traversal{ 438 hcl.TraverseAttr{ 439 Name: "baz", 440 SrcRange: hcl.Range{ 441 Start: hcl.Pos{Line: 1, Column: 15, Byte: 14}, 442 End: hcl.Pos{Line: 1, Column: 19, Byte: 18}, 443 }, 444 }, 445 }, 446 }, 447 ``, 448 }, 449 { 450 `module.foo["baz"]`, 451 &Reference{ 452 Subject: ModuleCallInstance{ 453 Call: ModuleCall{ 454 Name: "foo", 455 }, 456 Key: StringKey("baz"), 457 }, 458 SourceRange: tfdiags.SourceRange{ 459 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 460 End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, 461 }, 462 }, 463 ``, 464 }, 465 { 466 `module.foo["baz"].bar`, 467 &Reference{ 468 Subject: ModuleCallInstanceOutput{ 469 Call: ModuleCallInstance{ 470 Call: ModuleCall{ 471 Name: "foo", 472 }, 473 Key: StringKey("baz"), 474 }, 475 Name: "bar", 476 }, 477 SourceRange: tfdiags.SourceRange{ 478 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 479 End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21}, 480 }, 481 }, 482 ``, 483 }, 484 { 485 `module.foo["baz"].bar.boop`, 486 &Reference{ 487 Subject: ModuleCallInstanceOutput{ 488 Call: ModuleCallInstance{ 489 Call: ModuleCall{ 490 Name: "foo", 491 }, 492 Key: StringKey("baz"), 493 }, 494 Name: "bar", 495 }, 496 SourceRange: tfdiags.SourceRange{ 497 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 498 End: tfdiags.SourcePos{Line: 1, Column: 22, Byte: 21}, 499 }, 500 Remaining: hcl.Traversal{ 501 hcl.TraverseAttr{ 502 Name: "boop", 503 SrcRange: hcl.Range{ 504 Start: hcl.Pos{Line: 1, Column: 22, Byte: 21}, 505 End: hcl.Pos{Line: 1, Column: 27, Byte: 26}, 506 }, 507 }, 508 }, 509 }, 510 ``, 511 }, 512 { 513 `module`, 514 nil, 515 `The "module" object cannot be accessed directly. Instead, access one of its attributes.`, 516 }, 517 { 518 `module["foo"]`, 519 nil, 520 `The "module" object does not support this operation.`, 521 }, 522 523 // path 524 { 525 `path.module`, 526 &Reference{ 527 Subject: PathAttr{ 528 Name: "module", 529 }, 530 SourceRange: tfdiags.SourceRange{ 531 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 532 End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11}, 533 }, 534 }, 535 ``, 536 }, 537 { 538 `path.module.blah`, 539 &Reference{ 540 Subject: PathAttr{ 541 Name: "module", 542 }, 543 SourceRange: tfdiags.SourceRange{ 544 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 545 End: tfdiags.SourcePos{Line: 1, Column: 12, Byte: 11}, 546 }, 547 Remaining: hcl.Traversal{ 548 hcl.TraverseAttr{ 549 Name: "blah", 550 SrcRange: hcl.Range{ 551 Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, 552 End: hcl.Pos{Line: 1, Column: 17, Byte: 16}, 553 }, 554 }, 555 }, 556 }, 557 ``, // valid at this layer, but will fail during eval because "module" is a string 558 }, 559 { 560 `path`, 561 nil, 562 `The "path" object cannot be accessed directly. Instead, access one of its attributes.`, 563 }, 564 { 565 `path["module"]`, 566 nil, 567 `The "path" object does not support this operation.`, 568 }, 569 570 // self 571 { 572 `self`, 573 &Reference{ 574 Subject: Self, 575 SourceRange: tfdiags.SourceRange{ 576 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 577 End: tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4}, 578 }, 579 }, 580 ``, 581 }, 582 { 583 `self.blah`, 584 &Reference{ 585 Subject: Self, 586 SourceRange: tfdiags.SourceRange{ 587 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 588 End: tfdiags.SourcePos{Line: 1, Column: 5, Byte: 4}, 589 }, 590 Remaining: hcl.Traversal{ 591 hcl.TraverseAttr{ 592 Name: "blah", 593 SrcRange: hcl.Range{ 594 Start: hcl.Pos{Line: 1, Column: 5, Byte: 4}, 595 End: hcl.Pos{Line: 1, Column: 10, Byte: 9}, 596 }, 597 }, 598 }, 599 }, 600 ``, 601 }, 602 603 // terraform 604 { 605 `terraform.workspace`, 606 &Reference{ 607 Subject: TerraformAttr{ 608 Name: "workspace", 609 }, 610 SourceRange: tfdiags.SourceRange{ 611 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 612 End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19}, 613 }, 614 }, 615 ``, 616 }, 617 { 618 `terraform.workspace.blah`, 619 &Reference{ 620 Subject: TerraformAttr{ 621 Name: "workspace", 622 }, 623 SourceRange: tfdiags.SourceRange{ 624 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 625 End: tfdiags.SourcePos{Line: 1, Column: 20, Byte: 19}, 626 }, 627 Remaining: hcl.Traversal{ 628 hcl.TraverseAttr{ 629 Name: "blah", 630 SrcRange: hcl.Range{ 631 Start: hcl.Pos{Line: 1, Column: 20, Byte: 19}, 632 End: hcl.Pos{Line: 1, Column: 25, Byte: 24}, 633 }, 634 }, 635 }, 636 }, 637 ``, // valid at this layer, but will fail during eval because "workspace" is a string 638 }, 639 { 640 `terraform`, 641 nil, 642 `The "terraform" object cannot be accessed directly. Instead, access one of its attributes.`, 643 }, 644 { 645 `terraform["workspace"]`, 646 nil, 647 `The "terraform" object does not support this operation.`, 648 }, 649 650 // var 651 { 652 `var.foo`, 653 &Reference{ 654 Subject: InputVariable{ 655 Name: "foo", 656 }, 657 SourceRange: tfdiags.SourceRange{ 658 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 659 End: tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7}, 660 }, 661 }, 662 ``, 663 }, 664 { 665 `var.foo.blah`, 666 &Reference{ 667 Subject: InputVariable{ 668 Name: "foo", 669 }, 670 SourceRange: tfdiags.SourceRange{ 671 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 672 End: tfdiags.SourcePos{Line: 1, Column: 8, Byte: 7}, 673 }, 674 Remaining: hcl.Traversal{ 675 hcl.TraverseAttr{ 676 Name: "blah", 677 SrcRange: hcl.Range{ 678 Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, 679 End: hcl.Pos{Line: 1, Column: 13, Byte: 12}, 680 }, 681 }, 682 }, 683 }, 684 ``, // valid at this layer, but will fail during eval because "module" is a string 685 }, 686 { 687 `var`, 688 nil, 689 `The "var" object cannot be accessed directly. Instead, access one of its attributes.`, 690 }, 691 { 692 `var["foo"]`, 693 nil, 694 `The "var" object does not support this operation.`, 695 }, 696 697 // the "resource" prefix forces interpreting the next name as a 698 // resource type name. This is an alias for just using a resource 699 // type name at the top level, to be used only if a later edition 700 // of the Terraform language introduces a new reserved word that 701 // overlaps with a resource type name. 702 { 703 `resource.boop_instance.foo`, 704 &Reference{ 705 Subject: Resource{ 706 Mode: ManagedResourceMode, 707 Type: "boop_instance", 708 Name: "foo", 709 }, 710 SourceRange: tfdiags.SourceRange{ 711 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 712 End: tfdiags.SourcePos{Line: 1, Column: 27, Byte: 26}, 713 }, 714 }, 715 ``, 716 }, 717 718 // We have some names reserved which might be used by a 719 // still-under-discussion proposal for template values or lazy 720 // expressions. 721 { 722 `template.foo`, 723 nil, 724 `The symbol name "template" is reserved for use in a future Terraform version. If you are using a provider that already uses this as a resource type name, add the prefix "resource." to force interpretation as a resource type name.`, 725 }, 726 { 727 `lazy.foo`, 728 nil, 729 `The symbol name "lazy" is reserved for use in a future Terraform version. If you are using a provider that already uses this as a resource type name, add the prefix "resource." to force interpretation as a resource type name.`, 730 }, 731 { 732 `arg.foo`, 733 nil, 734 `The symbol name "arg" is reserved for use in a future Terraform version. If you are using a provider that already uses this as a resource type name, add the prefix "resource." to force interpretation as a resource type name.`, 735 }, 736 737 // anything else, interpreted as a managed resource reference 738 { 739 `boop_instance.foo`, 740 &Reference{ 741 Subject: Resource{ 742 Mode: ManagedResourceMode, 743 Type: "boop_instance", 744 Name: "foo", 745 }, 746 SourceRange: tfdiags.SourceRange{ 747 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 748 End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, 749 }, 750 }, 751 ``, 752 }, 753 { 754 `boop_instance.foo.bar`, 755 &Reference{ 756 Subject: ResourceInstance{ 757 Resource: Resource{ 758 Mode: ManagedResourceMode, 759 Type: "boop_instance", 760 Name: "foo", 761 }, 762 }, 763 SourceRange: tfdiags.SourceRange{ 764 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 765 End: tfdiags.SourcePos{Line: 1, Column: 18, Byte: 17}, 766 }, 767 Remaining: hcl.Traversal{ 768 hcl.TraverseAttr{ 769 Name: "bar", 770 SrcRange: hcl.Range{ 771 Start: hcl.Pos{Line: 1, Column: 18, Byte: 17}, 772 End: hcl.Pos{Line: 1, Column: 22, Byte: 21}, 773 }, 774 }, 775 }, 776 }, 777 ``, 778 }, 779 { 780 `boop_instance.foo["baz"].bar`, 781 &Reference{ 782 Subject: ResourceInstance{ 783 Resource: Resource{ 784 Mode: ManagedResourceMode, 785 Type: "boop_instance", 786 Name: "foo", 787 }, 788 Key: StringKey("baz"), 789 }, 790 SourceRange: tfdiags.SourceRange{ 791 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 792 End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24}, 793 }, 794 Remaining: hcl.Traversal{ 795 hcl.TraverseAttr{ 796 Name: "bar", 797 SrcRange: hcl.Range{ 798 Start: hcl.Pos{Line: 1, Column: 25, Byte: 24}, 799 End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, 800 }, 801 }, 802 }, 803 }, 804 ``, 805 }, 806 { 807 `boop_instance.foo["baz"]`, 808 &Reference{ 809 Subject: ResourceInstance{ 810 Resource: Resource{ 811 Mode: ManagedResourceMode, 812 Type: "boop_instance", 813 Name: "foo", 814 }, 815 Key: StringKey("baz"), 816 }, 817 SourceRange: tfdiags.SourceRange{ 818 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 819 End: tfdiags.SourcePos{Line: 1, Column: 25, Byte: 24}, 820 }, 821 }, 822 ``, 823 }, 824 { 825 `boop_instance`, 826 nil, 827 `A reference to a resource type must be followed by at least one attribute access, specifying the resource name.`, 828 }, 829 830 // Should interpret checks and outputs as resource types. 831 { 832 `output.value`, 833 &Reference{ 834 Subject: Resource{ 835 Mode: ManagedResourceMode, 836 Type: "output", 837 Name: "value", 838 }, 839 SourceRange: tfdiags.SourceRange{ 840 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 841 End: tfdiags.SourcePos{Line: 1, Column: 13, Byte: 12}, 842 }, 843 }, 844 ``, 845 }, 846 { 847 `check.health`, 848 &Reference{ 849 Subject: Resource{ 850 Mode: ManagedResourceMode, 851 Type: "check", 852 Name: "health", 853 }, 854 SourceRange: tfdiags.SourceRange{ 855 Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0}, 856 End: tfdiags.SourcePos{Line: 1, Column: 13, Byte: 12}, 857 }, 858 }, 859 ``, 860 }, 861 } 862 863 for _, test := range tests { 864 t.Run(test.Input, func(t *testing.T) { 865 traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1}) 866 if travDiags.HasErrors() { 867 t.Fatal(travDiags.Error()) 868 } 869 870 got, diags := ParseRef(traversal) 871 872 switch len(diags) { 873 case 0: 874 if test.WantErr != "" { 875 t.Fatalf("succeeded; want error: %s", test.WantErr) 876 } 877 case 1: 878 if test.WantErr == "" { 879 t.Fatalf("unexpected diagnostics: %s", diags.Err()) 880 } 881 if got, want := diags[0].Description().Detail, test.WantErr; got != want { 882 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 883 } 884 default: 885 t.Fatalf("too many diagnostics: %s", diags.Err()) 886 } 887 888 if diags.HasErrors() { 889 return 890 } 891 892 for _, problem := range deep.Equal(got, test.Want) { 893 t.Errorf(problem) 894 } 895 }) 896 } 897 }