From 54eff83566450573e577e46311838592731966a2 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Mon, 4 Apr 2022 20:02:25 +0200 Subject: [PATCH 1/5] add new targets field to body schema --- schema/body_schema.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/schema/body_schema.go b/schema/body_schema.go index 82e11427..7d5d714f 100644 --- a/schema/body_schema.go +++ b/schema/body_schema.go @@ -31,6 +31,10 @@ type BodySchema struct { // TargetableAs represents how else the body may be targeted // if not by its declarable attributes or blocks. TargetableAs Targetables + + // Targets indicates whether the something inside the body is + // targeting another location + Targets *Target } type DocsLink struct { @@ -38,6 +42,11 @@ type DocsLink struct { Tooltip string } +type Target struct { + Path lang.Path + Range hcl.Range +} + func (*BodySchema) isSchemaImpl() schemaImplSigil { return schemaImplSigil{} } @@ -137,6 +146,7 @@ func (bs *BodySchema) Copy() *BodySchema { AnyAttribute: bs.AnyAttribute.Copy(), HoverURL: bs.HoverURL, DocsLink: bs.DocsLink.Copy(), + Targets: bs.Targets.Copy(), } if bs.TargetableAs != nil { @@ -173,3 +183,14 @@ func (dl *DocsLink) Copy() *DocsLink { Tooltip: dl.Tooltip, } } + +func (t *Target) Copy() *Target { + if t == nil { + return nil + } + + return &Target{ + Path: t.Path, + Range: t.Range, + } +} From c486e1e6d7b1a25de4530703286f97b7039fa677 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Mon, 4 Apr 2022 20:04:24 +0200 Subject: [PATCH 2/5] expose dependency key attributes as direct origins When the body schema has a valid `Targets` set, we expose all dependecy keys as DirectOrigin for go to definition. --- decoder/reference_origins.go | 8 ++++++ decoder/reference_targets.go | 15 ++++++++++- decoder/reference_targets_test.go | 43 +++++++++++++++++++++++++++++++ reference/origin.go | 4 +++ reference/origin_direct.go | 35 +++++++++++++++++++++++++ reference/origins.go | 2 ++ reference/origins_test.go | 27 +++++++++++++++++++ reference/targets_test.go | 2 +- 8 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 reference/origin_direct.go diff --git a/decoder/reference_origins.go b/decoder/reference_origins.go index 5851cd5d..6aa5f565 100644 --- a/decoder/reference_origins.go +++ b/decoder/reference_origins.go @@ -111,6 +111,14 @@ func (d *PathDecoder) referenceOriginsInBody(body hcl.Body, bodySchema *schema.B } } + if aSchema.IsDepKey && bodySchema.Targets != nil { + origins = append(origins, reference.DirectOrigin{ + Range: attr.Expr.Range(), + TargetPath: bodySchema.Targets.Path, + TargetRange: bodySchema.Targets.Range, + }) + } + origins = append(origins, d.findOriginsInExpression(attr.Expr, aSchema.Expr)...) } diff --git a/decoder/reference_targets.go b/decoder/reference_targets.go index 14426920..e87862d0 100644 --- a/decoder/reference_targets.go +++ b/decoder/reference_targets.go @@ -33,6 +33,15 @@ func (d *Decoder) ReferenceTargetsForOriginAtPos(path lang.Path, file string, po targetCtx := pathCtx targetPath := path + if directOrigin, ok := origin.(reference.DirectOrigin); ok { + matchingTargets = append(matchingTargets, &ReferenceTarget{ + OriginRange: origin.OriginRange(), + Path: directOrigin.TargetPath, + Range: directOrigin.TargetRange, + DefRangePtr: nil, + }) + continue + } if pathOrigin, ok := origin.(reference.PathOrigin); ok { ctx, err := d.pathReader.PathContext(pathOrigin.TargetPath) if err != nil { @@ -42,7 +51,11 @@ func (d *Decoder) ReferenceTargetsForOriginAtPos(path lang.Path, file string, po targetPath = pathOrigin.TargetPath } - targets, ok := targetCtx.ReferenceTargets.Match(origin.Address(), origin.OriginConstraints()) + matchableOrigin, ok := origin.(reference.MatchableOrigin) + if !ok { + continue + } + targets, ok := targetCtx.ReferenceTargets.Match(matchableOrigin.Address(), matchableOrigin.OriginConstraints()) if !ok { // target not found continue diff --git a/decoder/reference_targets_test.go b/decoder/reference_targets_test.go index 8964bee9..119d2074 100644 --- a/decoder/reference_targets_test.go +++ b/decoder/reference_targets_test.go @@ -280,6 +280,49 @@ func TestReferenceTargetForOriginAtPos(t *testing.T) { }, nil, }, + { + "direct origin target", + map[string]*PathContext{ + dirPath: { + ReferenceTargets: reference.Targets{}, + ReferenceOrigins: reference.Origins{ + reference.DirectOrigin{ + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + TargetPath: lang.Path{Path: dirPath, LanguageID: "terraform"}, + TargetRange: hcl.Range{ + Filename: "target.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 2, Byte: 15}, + }, + }, + }, + }, + }, + lang.Path{Path: dirPath}, + "test.tf", + hcl.InitialPos, + ReferenceTargets{ + { + OriginRange: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, + }, + Path: lang.Path{Path: dirPath, LanguageID: "terraform"}, + Range: hcl.Range{ + Filename: "target.tf", + Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, + End: hcl.Pos{Line: 3, Column: 2, Byte: 15}, + }, + DefRangePtr: nil, + }, + }, + nil, + }, } for i, tc := range testCases { diff --git a/reference/origin.go b/reference/origin.go index 3ebcd400..f3608beb 100644 --- a/reference/origin.go +++ b/reference/origin.go @@ -11,6 +11,10 @@ type Origin interface { isOriginImpl() originSigil Copy() Origin OriginRange() hcl.Range +} + +type MatchableOrigin interface { + Origin OriginConstraints() OriginConstraints Address() lang.Address } diff --git a/reference/origin_direct.go b/reference/origin_direct.go new file mode 100644 index 00000000..051fdff5 --- /dev/null +++ b/reference/origin_direct.go @@ -0,0 +1,35 @@ +package reference + +import ( + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl/v2" +) + +// DirectOrigin represents an origin which directly targets a file +// and doesn't need a matching target +type DirectOrigin struct { + // Range represents a range of a local traversal or an attribute + Range hcl.Range + + // TargetPath represents what Path does the origin targets + TargetPath lang.Path + + // TargetRange represents which file and line the origin targets + TargetRange hcl.Range +} + +func (do DirectOrigin) Copy() Origin { + return DirectOrigin{ + Range: do.Range, + TargetPath: do.TargetPath, + TargetRange: do.TargetRange, + } +} + +func (DirectOrigin) isOriginImpl() originSigil { + return originSigil{} +} + +func (do DirectOrigin) OriginRange() hcl.Range { + return do.Range +} diff --git a/reference/origins.go b/reference/origins.go index 21de9559..690873a6 100644 --- a/reference/origins.go +++ b/reference/origins.go @@ -44,6 +44,8 @@ func (ro Origins) Match(localPath lang.Path, target Target, targetPath lang.Path if origin.TargetPath.Equals(targetPath) && target.Matches(origin.Address(), origin.OriginConstraints()) { origins = append(origins, refOrigin) } + case DirectOrigin: + continue } } diff --git a/reference/origins_test.go b/reference/origins_test.go index 0270d8b8..a810b954 100644 --- a/reference/origins_test.go +++ b/reference/origins_test.go @@ -501,6 +501,33 @@ func TestOrigins_Match(t *testing.T) { }, }, }, + { + "direct origin cant be matched", + alphaPath, + Origins{ + DirectOrigin{ + Range: hcl.Range{ + Filename: "origin.tf", + Start: hcl.InitialPos, + End: hcl.InitialPos, + }, + TargetPath: betaPath, + TargetRange: hcl.Range{ + Filename: "target.tf", + Start: hcl.InitialPos, + End: hcl.InitialPos, + }, + }, + }, + alphaPath, + Target{ + Addr: lang.Address{ + lang.RootStep{Name: "test"}, + }, + Type: cty.String, + }, + Origins{}, + }, } for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { diff --git a/reference/targets_test.go b/reference/targets_test.go index 54b776b8..62f878dc 100644 --- a/reference/targets_test.go +++ b/reference/targets_test.go @@ -16,7 +16,7 @@ func TestTargets_Match(t *testing.T) { testCases := []struct { name string targets Targets - origin Origin + origin MatchableOrigin expectedTargets Targets expectedFound bool }{ From 96aabd86bc0236836637dceedbb87e938556823a Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Mon, 4 Apr 2022 20:04:50 +0200 Subject: [PATCH 3/5] merge Targets as part of the dependent body schema merger --- decoder/decoder.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/decoder/decoder.go b/decoder/decoder.go index 9a59641c..69b7ff51 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -69,6 +69,10 @@ func mergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (* for _, tBody := range depSchema.TargetableAs { mergedSchema.TargetableAs = append(mergedSchema.TargetableAs, tBody) } + + if depSchema.Targets != nil { + mergedSchema.Targets = depSchema.Targets.Copy() + } } return mergedSchema, nil From 089c2bc703dfe99136d88e25f1a5b831bbc0d708 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 6 Apr 2022 13:56:38 +0200 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Radek Simko --- reference/origin_direct.go | 4 ++-- reference/origins.go | 2 -- reference/origins_test.go | 2 +- schema/body_schema.go | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/reference/origin_direct.go b/reference/origin_direct.go index 051fdff5..6113d99d 100644 --- a/reference/origin_direct.go +++ b/reference/origin_direct.go @@ -8,10 +8,10 @@ import ( // DirectOrigin represents an origin which directly targets a file // and doesn't need a matching target type DirectOrigin struct { - // Range represents a range of a local traversal or an attribute + // Range represents a range of a local traversal, attribute, or an expression Range hcl.Range - // TargetPath represents what Path does the origin targets + // TargetPath represents what (directory) Path does the origin targets TargetPath lang.Path // TargetRange represents which file and line the origin targets diff --git a/reference/origins.go b/reference/origins.go index 690873a6..21de9559 100644 --- a/reference/origins.go +++ b/reference/origins.go @@ -44,8 +44,6 @@ func (ro Origins) Match(localPath lang.Path, target Target, targetPath lang.Path if origin.TargetPath.Equals(targetPath) && target.Matches(origin.Address(), origin.OriginConstraints()) { origins = append(origins, refOrigin) } - case DirectOrigin: - continue } } diff --git a/reference/origins_test.go b/reference/origins_test.go index a810b954..2a95c76a 100644 --- a/reference/origins_test.go +++ b/reference/origins_test.go @@ -502,7 +502,7 @@ func TestOrigins_Match(t *testing.T) { }, }, { - "direct origin cant be matched", + "direct origin cannot be matched", alphaPath, Origins{ DirectOrigin{ diff --git a/schema/body_schema.go b/schema/body_schema.go index 7d5d714f..1e54e131 100644 --- a/schema/body_schema.go +++ b/schema/body_schema.go @@ -32,8 +32,8 @@ type BodySchema struct { // if not by its declarable attributes or blocks. TargetableAs Targetables - // Targets indicates whether the something inside the body is - // targeting another location + // Targets represents a location targeted by the body, when used as a body + // dependent on an attribute (e.g. Terraform module source) Targets *Target } From 784b768c095f94f4dc5b6cc0bf4d6fb9f6a0044a Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 6 Apr 2022 14:06:37 +0200 Subject: [PATCH 5/5] remove conditional --- decoder/decoder.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/decoder/decoder.go b/decoder/decoder.go index 69b7ff51..95c20ce5 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -70,9 +70,7 @@ func mergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (* mergedSchema.TargetableAs = append(mergedSchema.TargetableAs, tBody) } - if depSchema.Targets != nil { - mergedSchema.Targets = depSchema.Targets.Copy() - } + mergedSchema.Targets = depSchema.Targets.Copy() } return mergedSchema, nil