From 3596e57734890acc157191cf40a863be0a991200 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 24 Mar 2023 18:46:00 +0000 Subject: [PATCH 01/13] decoder: Finish implementing reference origins for List --- decoder/expr_list_ref_origins.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/decoder/expr_list_ref_origins.go b/decoder/expr_list_ref_origins.go index 4787e5bb..52eda51e 100644 --- a/decoder/expr_list_ref_origins.go +++ b/decoder/expr_list_ref_origins.go @@ -4,22 +4,22 @@ import ( "context" "github.com/hashicorp/hcl-lang/reference" - "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2" ) func (list List) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { - eType, ok := list.expr.(*hclsyntax.TupleConsExpr) - if !ok { + elems, diags := hcl.ExprList(list.expr) + if diags.HasErrors() { return reference.Origins{} } - if len(eType.Exprs) == 0 || list.cons.Elem == nil { + if len(elems) == 0 || list.cons.Elem == nil { return reference.Origins{} } origins := make(reference.Origins, 0) - for _, elemExpr := range eType.Exprs { + for _, elemExpr := range elems { expr := newExpression(list.pathCtx, elemExpr, list.cons.Elem) if e, ok := expr.(ReferenceOriginsExpression); ok { origins = append(origins, e.ReferenceOrigins(ctx, allowSelfRefs)...) From 9795032cdc56b5b68feb6ed6e801454191cbf930 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 24 Mar 2023 18:46:09 +0000 Subject: [PATCH 02/13] decoder: Finish implementing reference origins for Set --- decoder/expr_set_ref_origins.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/decoder/expr_set_ref_origins.go b/decoder/expr_set_ref_origins.go index 34dfc2c3..5cc3978b 100644 --- a/decoder/expr_set_ref_origins.go +++ b/decoder/expr_set_ref_origins.go @@ -4,22 +4,22 @@ import ( "context" "github.com/hashicorp/hcl-lang/reference" - "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2" ) func (set Set) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { - eType, ok := set.expr.(*hclsyntax.TupleConsExpr) - if !ok { + elems, diags := hcl.ExprList(set.expr) + if diags.HasErrors() { return reference.Origins{} } - if len(eType.Exprs) == 0 || set.cons.Elem == nil { + if len(elems) == 0 || set.cons.Elem == nil { return reference.Origins{} } origins := make(reference.Origins, 0) - for _, elemExpr := range eType.Exprs { + for _, elemExpr := range elems { expr := newExpression(set.pathCtx, elemExpr, set.cons.Elem) if e, ok := expr.(ReferenceOriginsExpression); ok { origins = append(origins, e.ReferenceOrigins(ctx, allowSelfRefs)...) From f229b9dfddcc91ef9b5e4b57c7e78ddf55680122 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 24 Mar 2023 18:46:29 +0000 Subject: [PATCH 03/13] decoder: Finish implementing reference origins for Tuple --- decoder/expr_tuple_ref_origins.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/decoder/expr_tuple_ref_origins.go b/decoder/expr_tuple_ref_origins.go index 8fa9ea8d..eebe45ca 100644 --- a/decoder/expr_tuple_ref_origins.go +++ b/decoder/expr_tuple_ref_origins.go @@ -4,22 +4,22 @@ import ( "context" "github.com/hashicorp/hcl-lang/reference" - "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2" ) func (tuple Tuple) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { - eType, ok := tuple.expr.(*hclsyntax.TupleConsExpr) - if !ok { + elems, diags := hcl.ExprList(tuple.expr) + if diags.HasErrors() { return reference.Origins{} } - if len(eType.Exprs) == 0 || len(tuple.cons.Elems) == 0 { + if len(elems) == 0 || len(tuple.cons.Elems) == 0 { return reference.Origins{} } origins := make(reference.Origins, 0) - for i, elemExpr := range eType.Exprs { + for i, elemExpr := range elems { if i+1 > len(tuple.cons.Elems) { break } From d632bc4c49ab6b0a22148c497c29bae488e62242 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 24 Mar 2023 08:20:35 +0000 Subject: [PATCH 04/13] decoder/reference: Merge constraints for multiple origins from OneOf --- decoder/expr_one_of_ref_origins.go | 40 +++++++++++++++++++++++++++--- reference/origin.go | 1 + reference/origin_local.go | 5 ++++ reference/origin_path.go | 5 ++++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/decoder/expr_one_of_ref_origins.go b/decoder/expr_one_of_ref_origins.go index e2380d86..85ea65d6 100644 --- a/decoder/expr_one_of_ref_origins.go +++ b/decoder/expr_one_of_ref_origins.go @@ -7,17 +7,49 @@ import ( ) func (oo OneOf) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { + origins := make(reference.Origins, 0) + for _, con := range oo.cons { expr := newExpression(oo.pathCtx, oo.expr, con) e, ok := expr.(ReferenceOriginsExpression) if !ok { continue } - origins := e.ReferenceOrigins(ctx, allowSelfRefs) - if len(origins) > 0 { - return origins + + origins = appendOrigins(origins, e.ReferenceOrigins(ctx, allowSelfRefs)) + } + + return origins +} + +func appendOrigins(origins, newOrigins reference.Origins) reference.Origins { + // Deduplicating origins like this is probably not ideal + // from performance perspective (N^2) but improving it would + // require redesign of the schema.Reference constraint, + // such that it doesn't necessitate the need of OneOf for multiple ScopeIds + // and maintains all possible ScopeIds & Types as a *single* slice. + for _, newOrigin := range newOrigins { + newMatchableOrigin, ok := newOrigin.(reference.MatchableOrigin) + if ok { + foundMatch := false + for i, origin := range origins { + existingOrigin, ok := origin.(reference.MatchableOrigin) + if ok && + existingOrigin.Address().Equals(newMatchableOrigin.Address()) && + rangesEqual(existingOrigin.OriginRange(), newMatchableOrigin.OriginRange()) { + + origins[i] = existingOrigin.AppendConstraints(newMatchableOrigin.OriginConstraints()) + foundMatch = true + break + } + } + if !foundMatch { + origins = append(origins, newOrigin) + } + } else { + origins = append(origins, newOrigin) } } - return reference.Origins{} + return origins } diff --git a/reference/origin.go b/reference/origin.go index f3608beb..a5393c69 100644 --- a/reference/origin.go +++ b/reference/origin.go @@ -16,5 +16,6 @@ type Origin interface { type MatchableOrigin interface { Origin OriginConstraints() OriginConstraints + AppendConstraints(OriginConstraints) MatchableOrigin Address() lang.Address } diff --git a/reference/origin_local.go b/reference/origin_local.go index 07fb66f7..b7f7a6a7 100644 --- a/reference/origin_local.go +++ b/reference/origin_local.go @@ -43,6 +43,11 @@ func (lo LocalOrigin) OriginConstraints() OriginConstraints { return lo.Constraints } +func (lo LocalOrigin) AppendConstraints(oc OriginConstraints) MatchableOrigin { + lo.Constraints = append(lo.Constraints, oc...) + return lo +} + func (lo LocalOrigin) Address() lang.Address { return lo.Addr } diff --git a/reference/origin_path.go b/reference/origin_path.go index f4337ad4..de02bf05 100644 --- a/reference/origin_path.go +++ b/reference/origin_path.go @@ -43,6 +43,11 @@ func (po PathOrigin) OriginConstraints() OriginConstraints { return po.Constraints } +func (po PathOrigin) AppendConstraints(oc OriginConstraints) MatchableOrigin { + po.Constraints = append(po.Constraints, oc...) + return po +} + func (po PathOrigin) Address() lang.Address { return po.TargetAddr } From 27e6eea94455921189b6c0e5b8b86ee0de9c64e5 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 27 Mar 2023 18:22:47 +0100 Subject: [PATCH 05/13] decoder: Account for JSON keys in rawObjectKey --- decoder/expression.go | 13 ++++ decoder/expression_test.go | 146 +++++++++++++++++++++++++++++++++++-- 2 files changed, 152 insertions(+), 7 deletions(-) diff --git a/decoder/expression.go b/decoder/expression.go index 2c5e252e..377df820 100644 --- a/decoder/expression.go +++ b/decoder/expression.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" "github.com/zclconf/go-cty/cty" ) @@ -254,6 +255,18 @@ func isObjectItemTerminatingRune(r rune) bool { // It does *not* account for interpolation inside the key, // such as { (var.key_name) = "foo" }. func rawObjectKey(expr hcl.Expression) (string, *hcl.Range, bool) { + if json.IsJSONExpression(expr) { + val, diags := expr.Value(&hcl.EvalContext{}) + if diags.HasErrors() { + return "", nil, false + } + if val.Type() != cty.String { + return "", nil, false + } + + return val.AsString(), expr.Range().Ptr(), true + } + // regardless of what expression it is always wrapped keyExpr, ok := expr.(*hclsyntax.ObjectConsKeyExpr) if !ok { diff --git a/decoder/expression_test.go b/decoder/expression_test.go index 02cf6c80..901aa94b 100644 --- a/decoder/expression_test.go +++ b/decoder/expression_test.go @@ -9,6 +9,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" ) var ( @@ -118,6 +119,26 @@ func TestRawObjectKey(t *testing.T) { nil, false, }, + { + `attr = { "42" = "bar" }`, + "42", + &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + End: hcl.Pos{Line: 1, Column: 13, Byte: 12}, + }, + true, + }, + { + `attr = { "foo-bar" = "bar" }`, + "foo-bar", + &hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, + }, + true, + }, { `attr = { foo.x = "bar" }`, "", @@ -174,21 +195,132 @@ func TestRawObjectKey(t *testing.T) { t.Fatalf("expected to find attribute %q", "attr") } - objConsExpr, ok := attr.Expr.(*hclsyntax.ObjectConsExpr) + items, diags := hcl.ExprMap(attr.Expr) + if diags.HasErrors() { + t.Fatal(diags) + } + + if len(items) != 1 { + t.Fatalf("expected exactly 1 object item, %d given", len(items)) + } + + rawKey, rng, ok := rawObjectKey(items[0].Key) + if !tc.expectedOk && ok { + t.Fatalf("expected parsing to fail, produced: %q at %#v", rawKey, rng) + } + if tc.expectedOk && !ok { + t.Fatalf("expected parsing to succeed and produce %q at %#v", + tc.expectedKey, tc.expectedRange) + } + if tc.expectedKey != rawKey { + t.Fatalf("extracted key mismatch\nexpected: %q\ngiven: %q", tc.expectedKey, rawKey) + } + if diff := cmp.Diff(tc.expectedRange, rng); diff != "" { + t.Fatalf("unexpected range: %s", diff) + } + }) + } +} + +func TestRawObjectKey_json(t *testing.T) { + testCases := []struct { + cfg string + expectedKey string + expectedRange *hcl.Range + expectedOk bool + }{ + { + `{"attr": { "foo": "bar" }}`, + "foo", + &hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + End: hcl.Pos{Line: 1, Column: 17, Byte: 16}, + }, + true, + }, + { + `{"attr": { "42": "bar" }}`, + "42", + &hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + true, + }, + { + `{"attr": { "foo-bar": "bar" }}`, + "foo-bar", + &hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + End: hcl.Pos{Line: 1, Column: 21, Byte: 20}, + }, + true, + }, + { + `{"attr": { "${foo.x}": "bar" }}`, + "", + nil, + false, + }, + { + `{"attr": { "${(foo)}": "bar" }}`, + "", + nil, + false, + }, + { + `{"attr": { "${(var.foo)}": "bar" }}`, + "", + nil, + false, + }, + { + `{"attr": { "${foo}": "bar" }}`, + "", + nil, + false, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + f, diags := json.ParseWithStartPos([]byte(tc.cfg), "test.hcl.json", hcl.InitialPos) + if len(diags) > 0 { + t.Fatal(diags) + } + + attrs, diags := f.Body.JustAttributes() + if len(diags) > 0 { + t.Fatal(diags) + } + + if len(attrs) != 1 { + t.Fatalf("expected exactly 1 attribute, %d given", len(attrs)) + } + + attr, ok := attrs["attr"] if !ok { - t.Fatalf("expected expression to be ObjectConsExpr, %T given", attr.Expr) + t.Fatalf("expected to find attribute %q", "attr") + } + + items, diags := hcl.ExprMap(attr.Expr) + if diags.HasErrors() { + t.Fatal(diags) } - if len(objConsExpr.Items) != 1 { - t.Fatalf("expected exactly 1 object item, %d given", len(objConsExpr.Items)) + if len(items) != 1 { + t.Fatalf("expected exactly 1 object item, %d given", len(items)) } - rawKey, rng, ok := rawObjectKey(objConsExpr.Items[0].KeyExpr) + rawKey, rng, ok := rawObjectKey(items[0].Key) if !tc.expectedOk && ok { - t.Fatal("expected parsing to fail") + t.Fatalf("expected parsing to fail, produced: %q at %#v", rawKey, rng) } if tc.expectedOk && !ok { - t.Fatal("expected parsing to succeed") + t.Fatalf("expected parsing to succeed and produce %q at %#v", + tc.expectedKey, tc.expectedRange) } if tc.expectedKey != rawKey { t.Fatalf("extracted key mismatch\nexpected: %q\ngiven: %q", tc.expectedKey, rawKey) From 08f676263eac4e46c7054aa2d77230d8fd019344 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 27 Mar 2023 18:23:57 +0100 Subject: [PATCH 06/13] decoder: Account for JSON when collecting origins in Map/Object --- decoder/expr_map_ref_origins.go | 12 ++++++------ decoder/expr_object_ref_origins.go | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/decoder/expr_map_ref_origins.go b/decoder/expr_map_ref_origins.go index 00d6abe7..262bb595 100644 --- a/decoder/expr_map_ref_origins.go +++ b/decoder/expr_map_ref_origins.go @@ -4,23 +4,23 @@ import ( "context" "github.com/hashicorp/hcl-lang/reference" - "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2" ) func (m Map) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { - eType, ok := m.expr.(*hclsyntax.ObjectConsExpr) - if !ok { + items, diags := hcl.ExprMap(m.expr) + if diags.HasErrors() { return reference.Origins{} } - if len(eType.Items) == 0 || m.cons.Elem == nil { + if len(items) == 0 || m.cons.Elem == nil { return reference.Origins{} } origins := make(reference.Origins, 0) - for _, item := range eType.Items { - expr := newExpression(m.pathCtx, item.ValueExpr, m.cons.Elem) + for _, item := range items { + expr := newExpression(m.pathCtx, item.Value, m.cons.Elem) if elemExpr, ok := expr.(ReferenceOriginsExpression); ok { origins = append(origins, elemExpr.ReferenceOrigins(ctx, allowSelfRefs)...) diff --git a/decoder/expr_object_ref_origins.go b/decoder/expr_object_ref_origins.go index 05ee7a61..439cddfe 100644 --- a/decoder/expr_object_ref_origins.go +++ b/decoder/expr_object_ref_origins.go @@ -4,23 +4,23 @@ import ( "context" "github.com/hashicorp/hcl-lang/reference" - "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2" ) func (obj Object) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { - eType, ok := obj.expr.(*hclsyntax.ObjectConsExpr) - if !ok { + items, diags := hcl.ExprMap(obj.expr) + if diags.HasErrors() { return reference.Origins{} } - if len(eType.Items) == 0 || len(obj.cons.Attributes) == 0 { + if len(items) == 0 || len(obj.cons.Attributes) == 0 { return reference.Origins{} } origins := make(reference.Origins, 0) - for _, item := range eType.Items { - attrName, _, ok := rawObjectKey(item.KeyExpr) + for _, item := range items { + attrName, _, ok := rawObjectKey(item.Key) if !ok { continue } @@ -31,7 +31,7 @@ func (obj Object) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) refe continue } - expr := newExpression(obj.pathCtx, item.ValueExpr, aSchema.Constraint) + expr := newExpression(obj.pathCtx, item.Value, aSchema.Constraint) if elemExpr, ok := expr.(ReferenceOriginsExpression); ok { origins = append(origins, elemExpr.ReferenceOrigins(ctx, allowSelfRefs)...) From f2bce7809e2dd3e26d07bb762ff4151f086724e6 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Fri, 24 Mar 2023 08:21:02 +0000 Subject: [PATCH 07/13] decoder: Add tests for reference origins (OneOf) --- decoder/expr_one_of_ref_origins_test.go | 231 +++++++++++++++++++++++- 1 file changed, 228 insertions(+), 3 deletions(-) diff --git a/decoder/expr_one_of_ref_origins_test.go b/decoder/expr_one_of_ref_origins_test.go index 0f734140..72d8b5c5 100644 --- a/decoder/expr_one_of_ref_origins_test.go +++ b/decoder/expr_one_of_ref_origins_test.go @@ -1,7 +1,232 @@ package decoder -import "testing" +import ( + "fmt" + "testing" -func TestCollectRefOrigins_exprOneOf(t *testing.T) { - // TODO! test when reference expr is available + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" +) + +func TestCollectRefOrigins_exprOneOf_hcl(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.OneOf{ + schema.Reference{OfType: cty.String}, + schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = "noot" +`, + reference.Origins{}, + }, + { + "one matching origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.OneOf{ + schema.Reference{OfType: cty.String}, + schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = foo.bar +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + {OfType: cty.Number}, + }, + }, + }, + }, + { + "multiple origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.OneOf{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.Bool}, + }, + }, + }, + `attr = "${foo}-${bar}" +`, + reference.Origins{}, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.hcl", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} + +func TestCollectRefOrigins_exprOneOf_json(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.OneOf{ + schema.Reference{OfType: cty.String}, + schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr": "42"}`, + reference.Origins{}, + }, + { + "one matching origin interpolated", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.OneOf{ + schema.Reference{OfType: cty.String}, + schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr": "${foo.bar}"}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 13, Byte: 12}, + End: hcl.Pos{Line: 1, Column: 20, Byte: 19}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + {OfType: cty.Number}, + }, + }, + }, + }, + { + "one matching origin plaintext", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.OneOf{ + schema.Reference{OfType: cty.String}, + schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr": "foo.bar"}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + {OfType: cty.Number}, + }, + }, + }, + }, + { + "multiple origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.OneOf{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.Bool}, + }, + }, + }, + `{"attr": "${foo}-${bar}"}`, + reference.Origins{}, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := json.ParseWithStartPos([]byte(tc.cfg), "test.hcl.json", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl.json": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } } From f4328834490f31e28697a4af12270f57a1251264 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 27 Mar 2023 17:31:47 +0100 Subject: [PATCH 08/13] decoder: Add tests for reference origins (List) --- decoder/expr_list_ref_origins_test.go | 353 ++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 decoder/expr_list_ref_origins_test.go diff --git a/decoder/expr_list_ref_origins_test.go b/decoder/expr_list_ref_origins_test.go new file mode 100644 index 00000000..15225caa --- /dev/null +++ b/decoder/expr_list_ref_origins_test.go @@ -0,0 +1,353 @@ +package decoder + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" +) + +func TestCollectRefOrigins_exprList_hcl(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `attr = foo.bar +`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `attr = ["noot"] +`, + reference.Origins{}, + }, + { + "one origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `attr = [foo.bar] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + }, + { + "multiple origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = [foo, bar] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + End: hcl.Pos{Line: 1, Column: 17, Byte: 16}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "multiple origins with skipped invalid expression", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = [foo, "noot", bar] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 22, Byte: 21}, + End: hcl.Pos{Line: 1, Column: 25, Byte: 24}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.hcl", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} + +func TestCollectRefOrigins_exprList_json(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `{"attr": "foo.bar"}`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `{"attr":[42]}`, + reference.Origins{}, + }, + { + "one origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `{"attr":["foo.bar"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + }, + { + "multiple origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr":["${foo}", "${bar}"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 13, Byte: 12}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 23, Byte: 22}, + End: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "multiple origins with skipped invalid expression", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.List{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr":["${foo}", 42, "${bar}"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 13, Byte: 12}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 27, Byte: 26}, + End: hcl.Pos{Line: 1, Column: 30, Byte: 29}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := json.ParseWithStartPos([]byte(tc.cfg), "test.hcl.json", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl.json": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} From 542c005ca21f67db79954295ecb188dae54921d0 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 27 Mar 2023 17:31:57 +0100 Subject: [PATCH 09/13] decoder: Add tests for reference origins (Set) --- decoder/expr_set_ref_origins_test.go | 353 +++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 decoder/expr_set_ref_origins_test.go diff --git a/decoder/expr_set_ref_origins_test.go b/decoder/expr_set_ref_origins_test.go new file mode 100644 index 00000000..58f25448 --- /dev/null +++ b/decoder/expr_set_ref_origins_test.go @@ -0,0 +1,353 @@ +package decoder + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" +) + +func TestCollectRefOrigins_exprSet_hcl(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `attr = foo.bar +`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `attr = ["noot"] +`, + reference.Origins{}, + }, + { + "one origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `attr = [foo.bar] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + }, + { + "multiple origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = [foo, bar] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + End: hcl.Pos{Line: 1, Column: 17, Byte: 16}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "multiple origins with skipped invalid expression", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = [foo, "noot", bar] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 22, Byte: 21}, + End: hcl.Pos{Line: 1, Column: 25, Byte: 24}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.hcl", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} + +func TestCollectRefOrigins_exprSet_json(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `{"attr": "foo.bar"}`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `{"attr":[42]}`, + reference.Origins{}, + }, + { + "one origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.String}, + }, + }, + }, + `{"attr":["foo.bar"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + }, + { + "multiple origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr":["${foo}", "${bar}"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 13, Byte: 12}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 23, Byte: 22}, + End: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "multiple origins with skipped invalid expression", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Set{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr":["${foo}", 42, "${bar}"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 13, Byte: 12}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 27, Byte: 26}, + End: hcl.Pos{Line: 1, Column: 30, Byte: 29}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := json.ParseWithStartPos([]byte(tc.cfg), "test.hcl.json", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl.json": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} From e663011c87f38ad04ee75128c2f7841127b5aa31 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 27 Mar 2023 17:32:07 +0100 Subject: [PATCH 10/13] decoder: Add tests for reference origins (Tuple) --- decoder/expr_tuple_ref_origins_test.go | 385 +++++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 decoder/expr_tuple_ref_origins_test.go diff --git a/decoder/expr_tuple_ref_origins_test.go b/decoder/expr_tuple_ref_origins_test.go new file mode 100644 index 00000000..e899b909 --- /dev/null +++ b/decoder/expr_tuple_ref_origins_test.go @@ -0,0 +1,385 @@ +package decoder + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" +) + +func TestCollectRefOrigins_exprTuple_hcl(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + }, + }, + }, + }, + `attr = foo.bar +`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + }, + }, + }, + }, + `attr = ["noot"] +`, + reference.Origins{}, + }, + { + "first origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + }, + }, + }, + }, + `attr = [foo.bar] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 16, Byte: 15}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "extra origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + }, + }, + }, + }, + `attr = [foo, bar, baz] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 14, Byte: 13}, + End: hcl.Pos{Line: 1, Column: 17, Byte: 16}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + }, + { + "multiple origins with skipped invalid expression", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + schema.Reference{OfType: cty.Number}, + }, + }, + }, + }, + `attr = [foo, "noot", bar] +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 1, Column: 22, Byte: 21}, + End: hcl.Pos{Line: 1, Column: 25, Byte: 24}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.hcl", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} + +func TestCollectRefOrigins_exprTuple_json(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + }, + }, + }, + }, + `{"attr": "foo.bar"}`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + }, + }, + }, + }, + `{"attr": [42]}`, + reference.Origins{}, + }, + { + "first origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + }, + }, + }, + }, + `{"attr": ["foo.bar"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + End: hcl.Pos{Line: 1, Column: 19, Byte: 18}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "extra origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + }, + }, + }, + }, + `{"attr": ["foo", "bar", "baz"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 19, Byte: 18}, + End: hcl.Pos{Line: 1, Column: 22, Byte: 21}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + }, + { + "multiple origins with skipped invalid expression", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Tuple{ + Elems: []schema.Constraint{ + schema.Reference{OfType: cty.Number}, + schema.Reference{OfType: cty.String}, + schema.Reference{OfType: cty.Number}, + }, + }, + }, + }, + `{"attr": ["foo", 42224, "bar"]}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 1, Column: 26, Byte: 25}, + End: hcl.Pos{Line: 1, Column: 29, Byte: 28}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := json.ParseWithStartPos([]byte(tc.cfg), "test.hcl.json", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl.json": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} From d66c7d5d63a0239f5f95f4dc827bceba19be5ae2 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 27 Mar 2023 17:32:20 +0100 Subject: [PATCH 11/13] decoder: Add tests for reference origins (Object) --- decoder/expr_object_ref_origins_test.go | 407 ++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 decoder/expr_object_ref_origins_test.go diff --git a/decoder/expr_object_ref_origins_test.go b/decoder/expr_object_ref_origins_test.go new file mode 100644 index 00000000..f51ad5ba --- /dev/null +++ b/decoder/expr_object_ref_origins_test.go @@ -0,0 +1,407 @@ +package decoder + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" +) + +func TestCollectRefOrigins_exprObject_hcl(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `attr = [foobar] +`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `attr = { + foo = "noot" + bar = "noot" +} +`, + reference.Origins{}, + }, + { + "one origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `attr = { + foo = foo.bar + bar = "noot" +} +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 2, Column: 9, Byte: 17}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 24}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "one origin with invalid key expression", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `attr = { + 422 = bar + foo = foo.bar + bar = "noot" +} +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 3, Column: 9, Byte: 29}, + End: hcl.Pos{Line: 3, Column: 16, Byte: 36}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "unknown attribute", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `attr = { + foo = foo + baz = baz + bar = bar +} +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 2, Column: 9, Byte: 17}, + End: hcl.Pos{Line: 2, Column: 12, Byte: 20}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 4, Column: 9, Byte: 41}, + End: hcl.Pos{Line: 4, Column: 12, Byte: 44}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.hcl", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} + +func TestCollectRefOrigins_exprObject_json(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `{"attr": ["foobar"]}`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `{"attr": { + "foo": 42, + "bar": true +}}`, + reference.Origins{}, + }, + { + "one origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `{"attr": { + "foo": "foo.bar", + "bar": 42 +}}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 2, Column: 11, Byte: 21}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 28}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "unknown attribute", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Object{ + Attributes: schema.ObjectAttributes{ + "foo": { + Constraint: schema.Reference{OfType: cty.Number}, + IsOptional: true, + }, + "bar": { + Constraint: schema.Reference{OfType: cty.String}, + IsOptional: true, + }, + }, + }, + }, + }, + `{"attr": { + "foo": "foo", + "baz": "baz", + "bar": "bar" +}}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 2, Column: 11, Byte: 21}, + End: hcl.Pos{Line: 2, Column: 14, Byte: 24}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 4, Column: 11, Byte: 53}, + End: hcl.Pos{Line: 4, Column: 14, Byte: 56}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.String}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := json.ParseWithStartPos([]byte(tc.cfg), "test.hcl.json", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl.json": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} From f6222e160a6cd574740e42c72b23d2aa464d9e5f Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 27 Mar 2023 18:38:20 +0100 Subject: [PATCH 12/13] decoder: Add tests for reference origins (Map) --- decoder/expr_map_ref_origins_test.go | 252 +++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 decoder/expr_map_ref_origins_test.go diff --git a/decoder/expr_map_ref_origins_test.go b/decoder/expr_map_ref_origins_test.go new file mode 100644 index 00000000..d657c946 --- /dev/null +++ b/decoder/expr_map_ref_origins_test.go @@ -0,0 +1,252 @@ +package decoder + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/reference" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/hcl/v2/json" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" +) + +func TestCollectRefOrigins_exprMap_hcl(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = [foobar] +`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = { + foo = "noot" + bar = "noot" +} +`, + reference.Origins{}, + }, + { + "one origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = { + foo = foo.bar + bar = "noot" +} +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 2, Column: 9, Byte: 17}, + End: hcl.Pos{Line: 2, Column: 16, Byte: 24}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + { + "one origin with invalid key expression", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `attr = { + 422 = bar + foo = foo.bar + bar = "noot" +} +`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 2, Column: 9, Byte: 17}, + End: hcl.Pos{Line: 2, Column: 12, Byte: 20}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl", + Start: hcl.Pos{Line: 3, Column: 9, Byte: 29}, + End: hcl.Pos{Line: 3, Column: 16, Byte: 36}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.hcl", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} + +func TestCollectRefOrigins_exprMap_json(t *testing.T) { + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + expectedRefOrigins reference.Origins + }{ + { + "expression mismatch", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr": ["foobar"]}`, + reference.Origins{}, + }, + { + "no origins", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr": { + "foo": 42, + "bar": true +}}`, + reference.Origins{}, + }, + { + "one origin", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.Map{ + Elem: schema.Reference{OfType: cty.Number}, + }, + }, + }, + `{"attr": { + "foo": "foo.bar", + "bar": 42 +}}`, + reference.Origins{ + reference.LocalOrigin{ + Addr: lang.Address{ + lang.RootStep{Name: "foo"}, + lang.AttrStep{Name: "bar"}, + }, + Range: hcl.Range{ + Filename: "test.hcl.json", + Start: hcl.Pos{Line: 2, Column: 11, Byte: 21}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 28}, + }, + Constraints: reference.OriginConstraints{ + {OfType: cty.Number}, + }, + }, + }, + }, + } + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + f, diags := json.ParseWithStartPos([]byte(tc.cfg), "test.hcl.json", hcl.InitialPos) + if len(diags) > 0 { + t.Error(diags) + } + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.hcl.json": f, + }, + }) + + origins, err := d.CollectReferenceOrigins() + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedRefOrigins, origins, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected origins: %s", diff) + } + }) + } +} From da7b4b772be29398f84133efa17091fca54cdd85 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 30 Mar 2023 18:12:48 +0100 Subject: [PATCH 13/13] decoder(style): avoid else block to aid readability --- decoder/expr_one_of_ref_origins.go | 33 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/decoder/expr_one_of_ref_origins.go b/decoder/expr_one_of_ref_origins.go index 85ea65d6..c8d99697 100644 --- a/decoder/expr_one_of_ref_origins.go +++ b/decoder/expr_one_of_ref_origins.go @@ -30,23 +30,24 @@ func appendOrigins(origins, newOrigins reference.Origins) reference.Origins { // and maintains all possible ScopeIds & Types as a *single* slice. for _, newOrigin := range newOrigins { newMatchableOrigin, ok := newOrigin.(reference.MatchableOrigin) - if ok { - foundMatch := false - for i, origin := range origins { - existingOrigin, ok := origin.(reference.MatchableOrigin) - if ok && - existingOrigin.Address().Equals(newMatchableOrigin.Address()) && - rangesEqual(existingOrigin.OriginRange(), newMatchableOrigin.OriginRange()) { - - origins[i] = existingOrigin.AppendConstraints(newMatchableOrigin.OriginConstraints()) - foundMatch = true - break - } - } - if !foundMatch { - origins = append(origins, newOrigin) + if !ok { + origins = append(origins, newOrigin) + continue + } + + foundMatch := false + for i, origin := range origins { + existingOrigin, ok := origin.(reference.MatchableOrigin) + if ok && + existingOrigin.Address().Equals(newMatchableOrigin.Address()) && + rangesEqual(existingOrigin.OriginRange(), newMatchableOrigin.OriginRange()) { + + origins[i] = existingOrigin.AppendConstraints(newMatchableOrigin.OriginConstraints()) + foundMatch = true + break } - } else { + } + if !foundMatch { origins = append(origins, newOrigin) } }