github.com/pulumi/terraform@v1.4.0/pkg/addrs/parse_ref_test.go (about)

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