Skip to content

Commit

Permalink
reference: refactor & fix matching (cyclical refs)
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Nov 21, 2022
1 parent 898a478 commit f25632f
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 20 deletions.
91 changes: 71 additions & 20 deletions reference/targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,39 +75,90 @@ func (w refTargetDeepWalker) walk(refTargets Targets) {

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
}
}

ref.NestedTargets.MatchWalk(ctx, te, prefix, outermostBodyRng, originRng, f)
// Reject origins which are outside the targetable range
if target.TargetableFromRangePtr != nil && !rangeOverlaps(*target.TargetableFromRangePtr, originRng) {
return false
}

if target.MatchesConstraint(te) || hasNestedMatches {
return true
}
}

return false
}

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 (refs Targets) containsMatch(te schema.TraversalExpr, prefix string) bool {
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
1 change: 1 addition & 0 deletions reference/targets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,7 @@ func TestTargets_MatchWalk_localRefs(t *testing.T) {
},
},
},
// TODO: cyclical references
}

for i, tc := range testCases {
Expand Down

0 comments on commit f25632f

Please sign in to comment.