From d770b425fb2288fdcbf223b6955485a989dd3221 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 14 Mar 2022 15:03:37 +0000 Subject: [PATCH] fix: filtering of self references for cross-file references (#105) * add (failing) test * fix: filtering of self references for cross-file references --- decoder/expression_candidates.go | 2 +- decoder/expression_candidates_test.go | 179 ++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 1 deletion(-) diff --git a/decoder/expression_candidates.go b/decoder/expression_candidates.go index 11641db5..16d4db09 100644 --- a/decoder/expression_candidates.go +++ b/decoder/expression_candidates.go @@ -331,7 +331,7 @@ func (d *PathDecoder) candidatesForTraversalConstraint(tc schema.TraversalExpr, d.pathCtx.ReferenceTargets.MatchWalk(tc, string(prefix), func(ref reference.Target) error { // avoid suggesting references to block's own fields from within (for now) - if ref.RangePtr != nil && + if ref.RangePtr != nil && outerBodyRng.Filename == ref.RangePtr.Filename && (outerBodyRng.ContainsPos(ref.RangePtr.Start) || posEqual(outerBodyRng.End, ref.RangePtr.End)) { return nil diff --git a/decoder/expression_candidates_test.go b/decoder/expression_candidates_test.go index c804a7df..27b44d89 100644 --- a/decoder/expression_candidates_test.go +++ b/decoder/expression_candidates_test.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty-debug/ctydebug" "github.com/zclconf/go-cty/cty" ) @@ -2566,3 +2567,181 @@ another_block "meh" { }) } } + +func TestDecoder_CandidateAtPos_expressions_crossFileTraversal(t *testing.T) { + f1, _ := hclsyntax.ParseConfig([]byte(`variable "aaa" {} +variable "bbb" {} +variable "ccc" {} +`), "test1.tf", hcl.InitialPos) + f2, _ := hclsyntax.ParseConfig([]byte(`value = +`), "test2.tf", hcl.InitialPos) + + bodySchema := &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{ + "value": { + IsOptional: true, + Expr: schema.ExprConstraints{ + schema.TraversalExpr{ + OfScopeId: lang.ScopeId("variable"), + OfType: cty.DynamicPseudoType, + }, + }, + }, + }, + Blocks: map[string]*schema.BlockSchema{ + "variable": { + Labels: []*schema.LabelSchema{ + {Name: "name"}, + }, + Address: &schema.BlockAddrSchema{ + Steps: []schema.AddrStep{ + schema.StaticStep{Name: "var"}, + schema.LabelStep{Index: 0}, + }, + FriendlyName: "variable", + ScopeId: lang.ScopeId("variable"), + AsTypeOf: &schema.BlockAsTypeOf{ + AttributeExpr: "type", + AttributeValue: "default", + }, + }, + }, + }, + } + + testDir := t.TempDir() + dirReader := &testPathReader{ + paths: map[string]*PathContext{ + testDir: { + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test1.tf": f1, + "test2.tf": f2, + }, + ReferenceTargets: reference.Targets{}, + }, + }, + } + decoder := NewDecoder(dirReader) + d, err := decoder.Path(lang.Path{Path: testDir}) + if err != nil { + t.Fatal(err) + } + refTargets, err := d.CollectReferenceTargets() + if err != nil { + t.Fatal(err) + } + + expectedTargets := reference.Targets{ + { + Addr: lang.Address{lang.RootStep{Name: "var"}, lang.AttrStep{Name: "aaa"}}, + ScopeId: lang.ScopeId("variable"), + RangePtr: &hcl.Range{ + Filename: "test1.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, + }, + DefRangePtr: &hcl.Range{ + Filename: "test1.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, + }, + Type: cty.DynamicPseudoType, + }, + { + Addr: lang.Address{lang.RootStep{Name: "var"}, lang.AttrStep{Name: "bbb"}}, + ScopeId: lang.ScopeId("variable"), + RangePtr: &hcl.Range{ + Filename: "test1.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 18, Byte: 35}, + }, + DefRangePtr: &hcl.Range{ + Filename: "test1.tf", + Start: hcl.Pos{Line: 2, Column: 1, Byte: 18}, + End: hcl.Pos{Line: 2, Column: 15, Byte: 32}, + }, + Type: cty.DynamicPseudoType, + }, + { + Addr: lang.Address{lang.RootStep{Name: "var"}, lang.AttrStep{Name: "ccc"}}, + ScopeId: lang.ScopeId("variable"), + RangePtr: &hcl.Range{ + Filename: "test1.tf", + Start: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + End: hcl.Pos{Line: 3, Column: 18, Byte: 53}, + }, + DefRangePtr: &hcl.Range{ + Filename: "test1.tf", + Start: hcl.Pos{Line: 3, Column: 1, Byte: 36}, + End: hcl.Pos{Line: 3, Column: 15, Byte: 50}, + }, + Type: cty.DynamicPseudoType, + }, + } + if diff := cmp.Diff(expectedTargets, refTargets, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected targets: %s", diff) + } + + dirReader.paths[testDir].ReferenceTargets = refTargets + + candidates, err := d.CandidatesAtPos("test2.tf", hcl.Pos{ + Line: 1, + Column: 9, + Byte: 8, + }) + if err != nil { + t.Fatal(err) + } + + expectedCandidates := lang.Candidates{ + List: []lang.Candidate{ + { + Label: "var.aaa", + Detail: "dynamic", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test2.tf", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + }, + NewText: "var.aaa", + Snippet: "var.aaa", + }, + Kind: lang.TraversalCandidateKind, + }, + { + Label: "var.bbb", + Detail: "dynamic", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test2.tf", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + }, + NewText: "var.bbb", + Snippet: "var.bbb", + }, + Kind: lang.TraversalCandidateKind, + }, + { + Label: "var.ccc", + Detail: "dynamic", + TextEdit: lang.TextEdit{ + Range: hcl.Range{ + Filename: "test2.tf", + Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, + }, + NewText: "var.ccc", + Snippet: "var.ccc", + }, + Kind: lang.TraversalCandidateKind, + }, + }, + IsComplete: true, + } + if diff := cmp.Diff(expectedCandidates, candidates); diff != "" { + t.Fatalf("unexpected candidates: %s", diff) + } +}