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  }