Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reference: refactor & fix matching of cyclical refs #159

Merged
merged 2 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions decoder/expression_candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ func (d *PathDecoder) constraintToCandidates(ctx context.Context, constraint sch
candidates = append(candidates, foreachEachCandidate(editRng)...)
}

candidates = append(candidates, d.candidatesForTraversalConstraint(c, outerBodyRng, prefixRng, editRng)...)
candidates = append(candidates, d.candidatesForTraversalConstraint(ctx, c, outerBodyRng, prefixRng, editRng)...)
case schema.TupleConsExpr:
candidates = append(candidates, lang.Candidate{
Label: fmt.Sprintf(`[%s]`, labelForConstraints(c.AnyElem)),
Expand Down Expand Up @@ -464,7 +464,7 @@ func (d *PathDecoder) constraintToCandidates(ctx context.Context, constraint sch
return candidates
}

func (d *PathDecoder) candidatesForTraversalConstraint(tc schema.TraversalExpr, outerBodyRng, prefixRng, editRng hcl.Range) []lang.Candidate {
func (d *PathDecoder) candidatesForTraversalConstraint(ctx context.Context, tc schema.TraversalExpr, outermostBodyRng, prefixRng, editRng hcl.Range) []lang.Candidate {
candidates := make([]lang.Candidate, 0)

if d.pathCtx.ReferenceTargets == nil {
Expand All @@ -478,14 +478,8 @@ func (d *PathDecoder) candidatesForTraversalConstraint(tc schema.TraversalExpr,

prefix, _ := d.bytesFromRange(prefixRng)

d.pathCtx.ReferenceTargets.MatchWalk(tc, string(prefix), editRng, func(target reference.Target) error {
// avoid suggesting references to block's own fields from within (for now)
// TODO: Reflect LocalAddr here
if referenceTargetIsInRange(target, outerBodyRng) {
return nil
}

address := target.Address().String()
d.pathCtx.ReferenceTargets.MatchWalk(ctx, tc, string(prefix), outermostBodyRng, editRng, func(target reference.Target) error {
address := target.Address(ctx).String()

candidates = append(candidates, lang.Candidate{
Label: address,
Expand Down
10 changes: 5 additions & 5 deletions decoder/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ func (d *PathDecoder) hoverDataForExpr(ctx context.Context, expr hcl.Expression,

tes, ok := constraints.TraversalExprs()
if ok {
content, err := d.hoverContentForTraversalExpr(e.AsTraversal(), tes, pos)
content, err := d.hoverContentForTraversalExpr(ctx, e.AsTraversal(), tes, pos)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -642,7 +642,7 @@ func stringValFromTemplateExpr(tplExpr *hclsyntax.TemplateExpr) (cty.Value, bool
return cty.StringVal(value), true
}

func (d *PathDecoder) hoverContentForTraversalExpr(traversal hcl.Traversal, tes []schema.TraversalExpr, pos hcl.Pos) (string, error) {
func (d *PathDecoder) hoverContentForTraversalExpr(ctx context.Context, traversal hcl.Traversal, tes []schema.TraversalExpr, pos hcl.Pos) (string, error) {
origins, ok := d.pathCtx.ReferenceOrigins.AtPos(traversal.SourceRange().Filename, pos)
if !ok {
return "", &reference.NoOriginFound{}
Expand All @@ -660,14 +660,14 @@ func (d *PathDecoder) hoverContentForTraversalExpr(traversal hcl.Traversal, tes
}

// TODO: Reflect additional found targets here?
return hoverContentForReferenceTarget(targets[0])
return hoverContentForReferenceTarget(ctx, targets[0])
}

return "", &reference.NoTargetFound{}
}

func hoverContentForReferenceTarget(ref reference.Target) (string, error) {
content := fmt.Sprintf("`%s`", ref.Address())
func hoverContentForReferenceTarget(ctx context.Context, ref reference.Target) (string, error) {
content := fmt.Sprintf("`%s`", ref.Address(ctx))

var friendlyName string
if ref.Type != cty.NilType {
Expand Down
7 changes: 4 additions & 3 deletions reference/target.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package reference

import (
"context"

"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/hcl/v2"
Expand Down Expand Up @@ -95,9 +97,8 @@ func copyHclRangePtr(rng *hcl.Range) *hcl.Range {
}

// Address returns any of the two non-empty addresses
//
// TODO: Return address based on context when we have both
func (r Target) Address() lang.Address {
// depending on the provided context
func (r Target) Address(ctx context.Context) lang.Address {
addr := r.Addr
if len(r.LocalAddr) > 0 {
addr = r.LocalAddr
Expand Down
94 changes: 73 additions & 21 deletions reference/targets.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package reference

import (
"context"
"errors"
"strings"

Expand Down Expand Up @@ -72,41 +73,92 @@ func (w refTargetDeepWalker) walk(refTargets Targets) {
}
}

func (refs Targets) MatchWalk(te schema.TraversalExpr, prefix string, originRng hcl.Range, f TargetWalkFunc) {
func (refs Targets) MatchWalk(ctx context.Context, te schema.TraversalExpr, prefix string, outermostBodyRng, originRng hcl.Range, f TargetWalkFunc) {
for _, ref := range refs {
if len(ref.LocalAddr) > 0 && strings.HasPrefix(ref.LocalAddr.String(), prefix) {
// Check if origin is inside the targetable range
if ref.TargetableFromRangePtr == nil || rangeOverlaps(*ref.TargetableFromRangePtr, originRng) {
nestedMatches := ref.NestedTargets.containsMatch(te, prefix)
if ref.MatchesConstraint(te) || nestedMatches {
f(ref)
}
}
if localTargetMatches(ctx, ref, te, prefix, outermostBodyRng, originRng) ||
absTargetMatches(ctx, ref, te, prefix, outermostBodyRng, originRng) {
f(ref)
continue
}
if len(ref.Addr) > 0 && strings.HasPrefix(ref.Addr.String(), prefix) {
nestedMatches := ref.NestedTargets.containsMatch(te, prefix)
if ref.MatchesConstraint(te) || nestedMatches {
f(ref)
continue

ref.NestedTargets.MatchWalk(ctx, te, prefix, outermostBodyRng, originRng, f)
}
}

func localTargetMatches(ctx context.Context, target Target, te schema.TraversalExpr, prefix string, outermostBodyRng, originRng hcl.Range) bool {
if len(target.LocalAddr) > 0 && strings.HasPrefix(target.LocalAddr.String(), prefix) {
hasNestedMatches := target.NestedTargets.containsMatch(ctx, te, prefix, outermostBodyRng, originRng)

// Avoid suggesting cyclical reference to the same attribute
// unless it has nested matches - i.e. still consider reference
// to the outside block/body as valid.
//
// For example, block { foo = self } where "self" refers to the "block"
// is considered valid. The use case this is important for is
// Terraform's self references inside nested block such as "connection".
if target.RangePtr != nil && !hasNestedMatches {
if rangeOverlaps(*target.RangePtr, originRng) {
return false
}
// We compare line in case the (incomplete) attribute
// ends w/ whitespace which wouldn't be included in the range
if target.RangePtr.Filename == originRng.Filename &&
target.RangePtr.End.Line == originRng.Start.Line {
return false
}
}

// Reject origins which are outside the targetable range
if target.TargetableFromRangePtr != nil && !rangeOverlaps(*target.TargetableFromRangePtr, originRng) {
return false
}

ref.NestedTargets.MatchWalk(te, prefix, originRng, f)
if target.MatchesConstraint(te) || hasNestedMatches {
return true
}
}

return false
}

func (refs Targets) containsMatch(te schema.TraversalExpr, prefix string) bool {
func absTargetMatches(ctx context.Context, target Target, te schema.TraversalExpr, prefix string, outermostBodyRng, originRng hcl.Range) bool {
if len(target.Addr) > 0 && strings.HasPrefix(target.Addr.String(), prefix) {
// Reject references to block's own fields from within the body
if referenceTargetIsInRange(target, outermostBodyRng) {
return false
}

if target.MatchesConstraint(te) || target.NestedTargets.containsMatch(ctx, te, prefix, outermostBodyRng, originRng) {
return true
}
}
return false
}

func referenceTargetIsInRange(target Target, bodyRange hcl.Range) bool {
return target.RangePtr != nil &&
bodyRange.Filename == target.RangePtr.Filename &&
(bodyRange.ContainsPos(target.RangePtr.Start) ||
posEqual(bodyRange.End, target.RangePtr.End))
}

func posEqual(pos, other hcl.Pos) bool {
return pos.Line == other.Line &&
pos.Column == other.Column &&
pos.Byte == other.Byte
}

func (refs Targets) containsMatch(ctx context.Context, te schema.TraversalExpr, prefix string, outermostBodyRng, originRng hcl.Range) bool {
for _, ref := range refs {
if strings.HasPrefix(ref.LocalAddr.String(), prefix) &&
ref.MatchesConstraint(te) {
if localTargetMatches(ctx, ref, te, prefix, outermostBodyRng, originRng) {
return true
}
if strings.HasPrefix(ref.Addr.String(), prefix) &&
ref.MatchesConstraint(te) {
if absTargetMatches(ctx, ref, te, prefix, outermostBodyRng, originRng) {
return true
}

if len(ref.NestedTargets) > 0 {
if match := ref.NestedTargets.containsMatch(te, prefix); match {
if match := ref.NestedTargets.containsMatch(ctx, te, prefix, outermostBodyRng, originRng); match {
return true
}
}
Expand Down
Loading