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