|
1 | 1 | package reference
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "context" |
4 | 5 | "errors"
|
5 | 6 | "strings"
|
6 | 7 |
|
@@ -72,41 +73,92 @@ func (w refTargetDeepWalker) walk(refTargets Targets) {
|
72 | 73 | }
|
73 | 74 | }
|
74 | 75 |
|
75 |
| -func (refs Targets) MatchWalk(te schema.TraversalExpr, prefix string, originRng hcl.Range, f TargetWalkFunc) { |
| 76 | +func (refs Targets) MatchWalk(ctx context.Context, te schema.TraversalExpr, prefix string, outermostBodyRng, originRng hcl.Range, f TargetWalkFunc) { |
76 | 77 | for _, ref := range refs {
|
77 |
| - if len(ref.LocalAddr) > 0 && strings.HasPrefix(ref.LocalAddr.String(), prefix) { |
78 |
| - // Check if origin is inside the targetable range |
79 |
| - if ref.TargetableFromRangePtr == nil || rangeOverlaps(*ref.TargetableFromRangePtr, originRng) { |
80 |
| - nestedMatches := ref.NestedTargets.containsMatch(te, prefix) |
81 |
| - if ref.MatchesConstraint(te) || nestedMatches { |
82 |
| - f(ref) |
83 |
| - } |
84 |
| - } |
| 78 | + if localTargetMatches(ctx, ref, te, prefix, outermostBodyRng, originRng) || |
| 79 | + absTargetMatches(ctx, ref, te, prefix, outermostBodyRng, originRng) { |
| 80 | + f(ref) |
| 81 | + continue |
85 | 82 | }
|
86 |
| - if len(ref.Addr) > 0 && strings.HasPrefix(ref.Addr.String(), prefix) { |
87 |
| - nestedMatches := ref.NestedTargets.containsMatch(te, prefix) |
88 |
| - if ref.MatchesConstraint(te) || nestedMatches { |
89 |
| - f(ref) |
90 |
| - continue |
| 83 | + |
| 84 | + ref.NestedTargets.MatchWalk(ctx, te, prefix, outermostBodyRng, originRng, f) |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +func localTargetMatches(ctx context.Context, target Target, te schema.TraversalExpr, prefix string, outermostBodyRng, originRng hcl.Range) bool { |
| 89 | + if len(target.LocalAddr) > 0 && strings.HasPrefix(target.LocalAddr.String(), prefix) { |
| 90 | + hasNestedMatches := target.NestedTargets.containsMatch(ctx, te, prefix, outermostBodyRng, originRng) |
| 91 | + |
| 92 | + // Avoid suggesting cyclical reference to the same attribute |
| 93 | + // unless it has nested matches - i.e. still consider reference |
| 94 | + // to the outside block/body as valid. |
| 95 | + // |
| 96 | + // For example, block { foo = self } where "self" refers to the "block" |
| 97 | + // is considered valid. The use case this is important for is |
| 98 | + // Terraform's self references inside nested block such as "connection". |
| 99 | + if target.RangePtr != nil && !hasNestedMatches { |
| 100 | + if rangeOverlaps(*target.RangePtr, originRng) { |
| 101 | + return false |
91 | 102 | }
|
| 103 | + // We compare line in case the (incomplete) attribute |
| 104 | + // ends w/ whitespace which wouldn't be included in the range |
| 105 | + if target.RangePtr.Filename == originRng.Filename && |
| 106 | + target.RangePtr.End.Line == originRng.Start.Line { |
| 107 | + return false |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + // Reject origins which are outside the targetable range |
| 112 | + if target.TargetableFromRangePtr != nil && !rangeOverlaps(*target.TargetableFromRangePtr, originRng) { |
| 113 | + return false |
92 | 114 | }
|
93 | 115 |
|
94 |
| - ref.NestedTargets.MatchWalk(te, prefix, originRng, f) |
| 116 | + if target.MatchesConstraint(te) || hasNestedMatches { |
| 117 | + return true |
| 118 | + } |
95 | 119 | }
|
| 120 | + |
| 121 | + return false |
96 | 122 | }
|
97 | 123 |
|
98 |
| -func (refs Targets) containsMatch(te schema.TraversalExpr, prefix string) bool { |
| 124 | +func absTargetMatches(ctx context.Context, target Target, te schema.TraversalExpr, prefix string, outermostBodyRng, originRng hcl.Range) bool { |
| 125 | + if len(target.Addr) > 0 && strings.HasPrefix(target.Addr.String(), prefix) { |
| 126 | + // Reject references to block's own fields from within the body |
| 127 | + if referenceTargetIsInRange(target, outermostBodyRng) { |
| 128 | + return false |
| 129 | + } |
| 130 | + |
| 131 | + if target.MatchesConstraint(te) || target.NestedTargets.containsMatch(ctx, te, prefix, outermostBodyRng, originRng) { |
| 132 | + return true |
| 133 | + } |
| 134 | + } |
| 135 | + return false |
| 136 | +} |
| 137 | + |
| 138 | +func referenceTargetIsInRange(target Target, bodyRange hcl.Range) bool { |
| 139 | + return target.RangePtr != nil && |
| 140 | + bodyRange.Filename == target.RangePtr.Filename && |
| 141 | + (bodyRange.ContainsPos(target.RangePtr.Start) || |
| 142 | + posEqual(bodyRange.End, target.RangePtr.End)) |
| 143 | +} |
| 144 | + |
| 145 | +func posEqual(pos, other hcl.Pos) bool { |
| 146 | + return pos.Line == other.Line && |
| 147 | + pos.Column == other.Column && |
| 148 | + pos.Byte == other.Byte |
| 149 | +} |
| 150 | + |
| 151 | +func (refs Targets) containsMatch(ctx context.Context, te schema.TraversalExpr, prefix string, outermostBodyRng, originRng hcl.Range) bool { |
99 | 152 | for _, ref := range refs {
|
100 |
| - if strings.HasPrefix(ref.LocalAddr.String(), prefix) && |
101 |
| - ref.MatchesConstraint(te) { |
| 153 | + if localTargetMatches(ctx, ref, te, prefix, outermostBodyRng, originRng) { |
102 | 154 | return true
|
103 | 155 | }
|
104 |
| - if strings.HasPrefix(ref.Addr.String(), prefix) && |
105 |
| - ref.MatchesConstraint(te) { |
| 156 | + if absTargetMatches(ctx, ref, te, prefix, outermostBodyRng, originRng) { |
106 | 157 | return true
|
107 | 158 | }
|
| 159 | + |
108 | 160 | if len(ref.NestedTargets) > 0 {
|
109 |
| - if match := ref.NestedTargets.containsMatch(te, prefix); match { |
| 161 | + if match := ref.NestedTargets.containsMatch(ctx, te, prefix, outermostBodyRng, originRng); match { |
110 | 162 | return true
|
111 | 163 | }
|
112 | 164 | }
|
|
0 commit comments