1package hclsyntax
2
3import (
4	"github.com/hashicorp/hcl2/hcl"
5)
6
7// -----------------------------------------------------------------------------
8// The methods in this file are all optional extension methods that serve to
9// implement the methods of the same name on *hcl.File when its root body
10// is provided by this package.
11// -----------------------------------------------------------------------------
12
13// BlocksAtPos implements the method of the same name for an *hcl.File that
14// is backed by a *Body.
15func (b *Body) BlocksAtPos(pos hcl.Pos) []*hcl.Block {
16	list, _ := b.blocksAtPos(pos, true)
17	return list
18}
19
20// InnermostBlockAtPos implements the method of the same name for an *hcl.File
21// that is backed by a *Body.
22func (b *Body) InnermostBlockAtPos(pos hcl.Pos) *hcl.Block {
23	_, innermost := b.blocksAtPos(pos, false)
24	return innermost.AsHCLBlock()
25}
26
27// OutermostBlockAtPos implements the method of the same name for an *hcl.File
28// that is backed by a *Body.
29func (b *Body) OutermostBlockAtPos(pos hcl.Pos) *hcl.Block {
30	return b.outermostBlockAtPos(pos).AsHCLBlock()
31}
32
33// blocksAtPos is the internal engine of both BlocksAtPos and
34// InnermostBlockAtPos, which both need to do the same logic but return a
35// differently-shaped result.
36//
37// list is nil if makeList is false, avoiding an allocation. Innermost is
38// always set, and if the returned list is non-nil it will always match the
39// final element from that list.
40func (b *Body) blocksAtPos(pos hcl.Pos, makeList bool) (list []*hcl.Block, innermost *Block) {
41	current := b
42
43Blocks:
44	for current != nil {
45		for _, block := range current.Blocks {
46			wholeRange := hcl.RangeBetween(block.TypeRange, block.CloseBraceRange)
47			if wholeRange.ContainsPos(pos) {
48				innermost = block
49				if makeList {
50					list = append(list, innermost.AsHCLBlock())
51				}
52				current = block.Body
53				continue Blocks
54			}
55		}
56
57		// If we fall out here then none of the current body's nested blocks
58		// contain the position we are looking for, and so we're done.
59		break
60	}
61
62	return
63}
64
65// outermostBlockAtPos is the internal version of OutermostBlockAtPos that
66// returns a hclsyntax.Block rather than an hcl.Block, allowing for further
67// analysis if necessary.
68func (b *Body) outermostBlockAtPos(pos hcl.Pos) *Block {
69	// This is similar to blocksAtPos, but simpler because we know it only
70	// ever needs to search the first level of nested blocks.
71
72	for _, block := range b.Blocks {
73		wholeRange := hcl.RangeBetween(block.TypeRange, block.CloseBraceRange)
74		if wholeRange.ContainsPos(pos) {
75			return block
76		}
77	}
78
79	return nil
80}
81
82// AttributeAtPos implements the method of the same name for an *hcl.File
83// that is backed by a *Body.
84func (b *Body) AttributeAtPos(pos hcl.Pos) *hcl.Attribute {
85	return b.attributeAtPos(pos).AsHCLAttribute()
86}
87
88// attributeAtPos is the internal version of AttributeAtPos that returns a
89// hclsyntax.Block rather than an hcl.Block, allowing for further analysis if
90// necessary.
91func (b *Body) attributeAtPos(pos hcl.Pos) *Attribute {
92	searchBody := b
93	_, block := b.blocksAtPos(pos, false)
94	if block != nil {
95		searchBody = block.Body
96	}
97
98	for _, attr := range searchBody.Attributes {
99		if attr.SrcRange.ContainsPos(pos) {
100			return attr
101		}
102	}
103
104	return nil
105}
106
107// OutermostExprAtPos implements the method of the same name for an *hcl.File
108// that is backed by a *Body.
109func (b *Body) OutermostExprAtPos(pos hcl.Pos) hcl.Expression {
110	attr := b.attributeAtPos(pos)
111	if attr == nil {
112		return nil
113	}
114	if !attr.Expr.Range().ContainsPos(pos) {
115		return nil
116	}
117	return attr.Expr
118}
119