summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hclsyntax/parse_traversal_test.go81
-rw-r--r--hclsyntax/parser_traversal.go51
-rw-r--r--hclsyntax/public.go31
3 files changed, 161 insertions, 2 deletions
diff --git a/hclsyntax/parse_traversal_test.go b/hclsyntax/parse_traversal_test.go
index 3ca5fc2..5d8d24e 100644
--- a/hclsyntax/parse_traversal_test.go
+++ b/hclsyntax/parse_traversal_test.go
@@ -4,11 +4,13 @@
package hclsyntax
import (
+ "fmt"
"testing"
"github.com/go-test/deep"
- "github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
+
+ "github.com/hashicorp/hcl/v2"
)
func TestParseTraversalAbs(t *testing.T) {
@@ -208,10 +210,63 @@ func TestParseTraversalAbs(t *testing.T) {
},
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 {
@@ -226,5 +281,29 @@ func TestParseTraversalAbs(t *testing.T) {
}
}
})
+
+ 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)
+ }
+ }
+ })
}
}
diff --git a/hclsyntax/parser_traversal.go b/hclsyntax/parser_traversal.go
index 3afa6ab..f7d4062 100644
--- a/hclsyntax/parser_traversal.go
+++ b/hclsyntax/parser_traversal.go
@@ -4,8 +4,9 @@
package hclsyntax
import (
- "github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
+
+ "github.com/hashicorp/hcl/v2"
)
// ParseTraversalAbs parses an absolute traversal that is assumed to consume
@@ -13,6 +14,26 @@ import (
// behavior is not supported here because traversals are not expected to
// be parsed as part of a larger program.
func (p *parser) ParseTraversalAbs() (hcl.Traversal, hcl.Diagnostics) {
+ return p.parseTraversal(false)
+}
+
+// ParseTraversalPartial parses an absolute traversal that is permitted
+// to contain splat ([*]) expressions. Only splat expressions within square
+// brackets are permitted ([*]); splat expressions within attribute names are
+// not permitted (.*).
+//
+// The meaning of partial here is that the traversal may be incomplete, in that
+// any splat expression indicates reference to a potentially unknown number of
+// elements.
+//
+// Traversals that include splats cannot be automatically traversed by HCL using
+// the TraversalAbs or TraversalRel methods. Instead, the caller must handle
+// the traversals manually.
+func (p *parser) ParseTraversalPartial() (hcl.Traversal, hcl.Diagnostics) {
+ return p.parseTraversal(true)
+}
+
+func (p *parser) parseTraversal(allowSplats bool) (hcl.Traversal, hcl.Diagnostics) {
var ret hcl.Traversal
var diags hcl.Diagnostics
@@ -127,6 +148,34 @@ func (p *parser) ParseTraversalAbs() (hcl.Traversal, hcl.Diagnostics) {
return ret, diags
}
+ case TokenStar:
+ if allowSplats {
+
+ p.Read() // Eat the star.
+ close := p.Read()
+ if close.Type != TokenCBrack {
+ diags = append(diags, &hcl.Diagnostic{
+ Severity: hcl.DiagError,
+ Summary: "Unclosed index brackets",
+ Detail: "Index key must be followed by a closing bracket.",
+ Subject: &close.Range,
+ Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
+ })
+ }
+
+ ret = append(ret, hcl.TraverseSplat{
+ SrcRange: hcl.RangeBetween(open.Range, close.Range),
+ })
+
+ if diags.HasErrors() {
+ return ret, diags
+ }
+
+ continue
+ }
+
+ // Otherwise, return the error below for the star.
+ fallthrough
default:
if next.Type == TokenStar {
diags = append(diags, &hcl.Diagnostic{
diff --git a/hclsyntax/public.go b/hclsyntax/public.go
index d56f8e5..17dc1ed 100644
--- a/hclsyntax/public.go
+++ b/hclsyntax/public.go
@@ -118,6 +118,37 @@ func ParseTraversalAbs(src []byte, filename string, start hcl.Pos) (hcl.Traversa
return expr, diags
}
+// ParseTraversalPartial matches the behavior of ParseTraversalAbs except
+// that it allows splat expressions ([*]) to appear in the traversal.
+//
+// The returned traversals are "partial" in that the splat expression indicates
+// an unknown value for the index.
+//
+// Traversals that include splats cannot be automatically traversed by HCL using
+// the TraversalAbs or TraversalRel methods. Instead, the caller must handle
+// the traversals manually.
+func ParseTraversalPartial(src []byte, filename string, start hcl.Pos) (hcl.Traversal, hcl.Diagnostics) {
+ tokens, diags := LexExpression(src, filename, start)
+ peeker := newPeeker(tokens, false)
+ parser := &parser{peeker: peeker}
+
+ // Bare traverals are always parsed in "ignore newlines" mode, as if
+ // they were wrapped in parentheses.
+ parser.PushIncludeNewlines(false)
+
+ expr, parseDiags := parser.ParseTraversalPartial()
+ diags = append(diags, parseDiags...)
+
+ parser.PopIncludeNewlines()
+
+ // Panic if the parser uses incorrect stack discipline with the peeker's
+ // newlines stack, since otherwise it will produce confusing downstream
+ // errors.
+ peeker.AssertEmptyIncludeNewlinesStack()
+
+ return expr, diags
+}
+
// LexConfig performs lexical analysis on the given buffer, treating it as a
// whole HCL config file, and returns the resulting tokens.
//