@@ -156,7 +156,6 @@ func (d *attributeDiagnostic) ElaborateFromConfigBody(body hcl.Body, addr string
156156 // presence of errors where performance isn't a concern.
157157
158158 traverse := d .attrPath [:]
159- final := d .attrPath [len (d .attrPath )- 1 ]
160159
161160 // Index should never be the first step
162161 // as indexing of top blocks (such as resources & data sources)
@@ -167,54 +166,19 @@ func (d *attributeDiagnostic) ElaborateFromConfigBody(body hcl.Body, addr string
167166 return & ret
168167 }
169168
170- // Process index separately
171- idxStep , hasIdx := final .(cty.IndexStep )
172- if hasIdx {
173- final = d .attrPath [len (d .attrPath )- 2 ]
174- traverse = d .attrPath [:len (d .attrPath )- 1 ]
175- }
176-
177169 // If we have more than one step after removing index
178170 // then we'll first try to traverse to a child body
179171 // corresponding to the requested path.
172+ remaining := traverse
180173 if len (traverse ) > 1 {
181- body = traversePathSteps ( traverse , body )
174+ body , remaining = getDeepestBodyFromPath ( body , traverse )
182175 }
183176
184177 // Default is to indicate a missing item in the deepest body we reached
185178 // while traversing.
186- subject := SourceRangeFromHCL (body . MissingItemRange ( ))
179+ subject := SourceRangeFromHCL (rangeOfDeepestAttributeValueFromPath ( body , remaining ))
187180 ret .subject = & subject
188181
189- // Once we get here, "final" should be a GetAttr step that maps to an
190- // attribute in our current body.
191- finalStep , isAttr := final .(cty.GetAttrStep )
192- if ! isAttr {
193- return & ret
194- }
195-
196- content , _ , contentDiags := body .PartialContent (& hcl.BodySchema {
197- Attributes : []hcl.AttributeSchema {
198- {
199- Name : finalStep .Name ,
200- Required : true ,
201- },
202- },
203- })
204- if contentDiags .HasErrors () {
205- return & ret
206- }
207-
208- if attr , ok := content .Attributes [finalStep .Name ]; ok {
209- hclRange := attr .Expr .Range ()
210- if hasIdx {
211- // Try to be more precise by finding index range
212- hclRange = hclRangeFromIndexStepAndAttribute (idxStep , attr )
213- }
214- subject = SourceRangeFromHCL (hclRange )
215- ret .subject = & subject
216- }
217-
218182 return & ret
219183}
220184
@@ -243,13 +207,15 @@ func (d *attributeDiagnostic) Equals(otherDiag ComparableDiagnostic) bool {
243207 return sourceRangeEquals (d .subject , od .subject )
244208}
245209
246- func traversePathSteps (traverse []cty.PathStep , body hcl.Body ) hcl.Body {
210+ func getDeepestBodyFromPath (body hcl.Body , traverse []cty.PathStep ) (hcl.Body , []cty.PathStep ) {
211+ lastProcessedIndex := - 1
212+
213+ LOOP:
247214 for i := 0 ; i < len (traverse ); i ++ {
248215 step := traverse [i ]
249216
250217 switch tStep := step .(type ) {
251218 case cty.GetAttrStep :
252-
253219 var next cty.PathStep
254220 if i < (len (traverse ) - 1 ) {
255221 next = traverse [i + 1 ]
@@ -281,7 +247,7 @@ func traversePathSteps(traverse []cty.PathStep, body hcl.Body) hcl.Body {
281247 },
282248 })
283249 if contentDiags .HasErrors () {
284- return body
250+ break LOOP
285251 }
286252 filtered := make ([]* hcl.Block , 0 , len (content .Blocks ))
287253 for _ , block := range content .Blocks {
@@ -291,22 +257,24 @@ func traversePathSteps(traverse []cty.PathStep, body hcl.Body) hcl.Body {
291257 }
292258 if len (filtered ) == 0 {
293259 // Step doesn't refer to a block
294- continue
260+ break LOOP
295261 }
296262
297263 switch indexType {
298264 case cty .NilType : // no index at all
299265 if len (filtered ) != 1 {
300- return body
266+ break LOOP
301267 }
302268 body = filtered [0 ].Body
269+ lastProcessedIndex = i
303270 case cty .Number :
304271 var idx int
305272 err := gocty .FromCtyValue (indexVal , & idx )
306273 if err != nil || idx >= len (filtered ) {
307- return body
274+ break LOOP
308275 }
309276 body = filtered [idx ].Body
277+ lastProcessedIndex = i
310278 case cty .String :
311279 key := indexVal .AsString ()
312280 var block * hcl.Block
@@ -319,56 +287,109 @@ func traversePathSteps(traverse []cty.PathStep, body hcl.Body) hcl.Body {
319287 if block == nil {
320288 // No block with this key, so we'll just indicate a
321289 // missing item in the containing block.
322- return body
290+ break LOOP
323291 }
324292 body = block .Body
293+ lastProcessedIndex = i
325294 default :
326295 // Should never happen, because only string and numeric indices
327296 // are supported by cty collections.
328- return body
297+ break LOOP
329298 }
330299
331300 default :
332301 // For any other kind of step, we'll just return our current body
333302 // as the subject and accept that this is a little inaccurate.
334- return body
303+ break LOOP
335304 }
336305 }
337- return body
306+ return body , traverse [ lastProcessedIndex + 1 :]
338307}
339308
340- func hclRangeFromIndexStepAndAttribute (idxStep cty.IndexStep , attr * hcl.Attribute ) hcl.Range {
341- switch idxStep .Key .Type () {
342- case cty .Number :
343- var idx int
344- err := gocty .FromCtyValue (idxStep .Key , & idx )
345- items , diags := hcl .ExprList (attr .Expr )
346- if diags .HasErrors () {
347- return attr .Expr .Range ()
309+ func rangeOfDeepestAttributeValueFromPath (body hcl.Body , traverse cty.Path ) hcl.Range {
310+ if len (traverse ) == 0 {
311+ return body .MissingItemRange ()
312+ }
313+ // First we need to use the first traverse item to get the final attribute
314+ // expression.
315+ current , rest := traverse [0 ], traverse [1 :]
316+
317+ currentGetAttr , ok := current .(cty.GetAttrStep )
318+ if ! ok {
319+ // If the remaining basis is not an attribute access something went wrong.
320+ // We can't do anything better than returning the bodies missing item range.
321+ return body .MissingItemRange ()
322+ }
323+
324+ content , _ , contentDiags := body .PartialContent (& hcl.BodySchema {
325+ Attributes : []hcl.AttributeSchema {
326+ {
327+ Name : currentGetAttr .Name ,
328+ Required : true ,
329+ },
330+ },
331+ })
332+ if contentDiags .HasErrors () {
333+ return body .MissingItemRange ()
334+ }
335+ attr , ok := content .Attributes [currentGetAttr .Name ]
336+ if ! ok {
337+ // We could not find the attribute, this should have emitted a diag above, but just in case
338+ return body .MissingItemRange ()
339+ }
340+
341+ // Now we need to loop through the rest of the path and progressively introspect
342+ // the HCL expression.
343+ currentExpr := attr .Expr
344+
345+ STEP_ITERATION:
346+ for _ , step := range rest {
347+ // We treat cty.IndexStep[type=String] and cty.GetAttrStep the same, so we just
348+ // need to deal with list indexes first
349+ if idxStep , ok := step .(cty.IndexStep ); ok && idxStep .Key .Type () == cty .Number {
350+ var idx int
351+ err := gocty .FromCtyValue (idxStep .Key , & idx )
352+ items , diags := hcl .ExprList (currentExpr )
353+ if diags .HasErrors () {
354+ return currentExpr .Range ()
355+ }
356+ if err != nil || idx >= len (items ) {
357+ return attr .NameRange
358+ }
359+ currentExpr = items [idx ]
360+ continue STEP_ITERATION
348361 }
349- if err != nil || idx >= len (items ) {
350- return attr .NameRange
362+
363+ var stepKey string
364+ switch s := step .(type ) {
365+ case cty.GetAttrStep :
366+ stepKey = s .Name
367+ case cty.IndexStep :
368+ stepKey = s .Key .AsString ()
369+ default : // should not happen
370+ return currentExpr .Range ()
351371 }
352- return items [idx ].Range ()
353- case cty .String :
354- pairs , diags := hcl .ExprMap (attr .Expr )
372+
373+ pairs , diags := hcl .ExprMap (currentExpr )
355374 if diags .HasErrors () {
356- return attr . Expr .Range ()
375+ return currentExpr .Range ()
357376 }
358- stepKey := idxStep . Key . AsString ()
377+
359378 for _ , kvPair := range pairs {
360379 key , diags := kvPair .Key .Value (nil )
361380 if diags .HasErrors () {
362- return attr . Expr .Range ()
381+ return currentExpr .Range ()
363382 }
364383 if key .AsString () == stepKey {
365- startRng : = kvPair .Value . StartRange ()
366- return startRng
384+ currentExpr = kvPair .Value
385+ continue STEP_ITERATION
367386 }
368387 }
388+ // If we could not find the item return early
369389 return attr .NameRange
370390 }
371- return attr .Expr .Range ()
391+
392+ return currentExpr .Range ()
372393}
373394
374395func (d * attributeDiagnostic ) Source () Source {
0 commit comments