// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package hclsyntax import ( "fmt" "testing" "github.com/go-test/deep" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/hcl/v2" ) func TestParseTraversalAbs(t *testing.T) { tests := []struct { src string want hcl.Traversal diagCount int }{ { "", nil, 1, // variable name required }, { "foo", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, }, 0, }, { "foo.bar.baz", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, hcl.TraverseAttr{ Name: "bar", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 4, Byte: 3}, End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, }, }, hcl.TraverseAttr{ Name: "baz", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, }, }, }, 0, }, { "foo[1]", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, hcl.TraverseIndex{ Key: cty.NumberIntVal(1), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 4, Byte: 3}, End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, }, }, }, 0, }, { "foo[1][2]", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, hcl.TraverseIndex{ Key: cty.NumberIntVal(1), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 4, Byte: 3}, End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, }, }, hcl.TraverseIndex{ Key: cty.NumberIntVal(2), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, End: hcl.Pos{Line: 1, Column: 10, Byte: 9}, }, }, }, 0, }, { "foo[1].bar", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, hcl.TraverseIndex{ Key: cty.NumberIntVal(1), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 4, Byte: 3}, End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, }, }, hcl.TraverseAttr{ Name: "bar", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 7, Byte: 6}, End: hcl.Pos{Line: 1, Column: 11, Byte: 10}, }, }, }, 0, }, { "foo.", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, }, 1, // attribute name required }, { "foo[", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, }, 1, // index required }, { "foo[index]", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, }, 1, // index must be literal }, { "foo[0", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, hcl.TraverseIndex{ Key: cty.NumberIntVal(0), SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 4, Byte: 3}, End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, }, }, }, 1, // missing close bracket }, { "foo 0", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, }, 1, // extra junk after traversal }, { "foo[*]", hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, hcl.TraverseSplat{ SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 4, Byte: 3}, End: hcl.Pos{Line: 1, Column: 7, Byte: 6}, }, }, }, 0, }, { "foo.*", // Still not supporting this. hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, }, 1, }, { "foo[*].bar", // Run this through the unsupported function. hcl.Traversal{ hcl.TraverseRoot{ Name: "foo", SrcRange: hcl.Range{ Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, End: hcl.Pos{Line: 1, Column: 4, Byte: 3}, }, }, }, 1, }, } for _, test := range tests { t.Run(test.src, func(t *testing.T) { if test.src == "foo[*]" { // The foo[*] test will fail because the function we test in // this branch does not support the splat syntax. So we will // skip this test case here. t.Skip("skipping test for unsupported splat syntax") } got, diags := ParseTraversalAbs([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) if len(diags) != test.diagCount { for _, diag := range diags { t.Logf(" - %s", diag.Error()) } t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount) } if diff := deep.Equal(got, test.want); diff != nil { for _, problem := range diff { t.Error(problem) } } }) t.Run(fmt.Sprintf("partial_%s", test.src), func(t *testing.T) { if test.src == "foo[*].bar" { // The foo[*].bar test will fail because the function we test in // this branch does support the splat syntax and this test is // designed to make sure that the other branch still fails with // the splat syntax. So we will skip this test case here. t.Skip("skipping test that fails for splat syntax") } got, diags := ParseTraversalPartial([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1}) if len(diags) != test.diagCount { for _, diag := range diags { t.Logf(" - %s", diag.Error()) } t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount) } if diff := deep.Equal(got, test.want); diff != nil { for _, problem := range diff { t.Error(problem) } } }) } }