aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/variables.go
blob: eeee1a529a09b3110bbeca1208b22b6ebbc19dbd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package hclsyntax

import (
	"github.com/hashicorp/hcl2/hcl"
)

// Variables returns all of the variables referenced within a given experssion.
//
// This is the implementation of the "Variables" method on every native
// expression.
func Variables(expr Expression) []hcl.Traversal {
	var vars []hcl.Traversal

	walker := &variablesWalker{
		Callback: func(t hcl.Traversal) {
			vars = append(vars, t)
		},
	}

	Walk(expr, walker)

	return vars
}

// variablesWalker is a Walker implementation that calls its callback for any
// root scope traversal found while walking.
type variablesWalker struct {
	Callback    func(hcl.Traversal)
	localScopes []map[string]struct{}
}

func (w *variablesWalker) Enter(n Node) hcl.Diagnostics {
	switch tn := n.(type) {
	case *ScopeTraversalExpr:
		t := tn.Traversal

		// Check if the given root name appears in any of the active
		// local scopes. We don't want to return local variables here, since
		// the goal of walking variables is to tell the calling application
		// which names it needs to populate in the _root_ scope.
		name := t.RootName()
		for _, names := range w.localScopes {
			if _, localized := names[name]; localized {
				return nil
			}
		}

		w.Callback(t)
	case ChildScope:
		w.localScopes = append(w.localScopes, tn.LocalNames)
	}
	return nil
}

func (w *variablesWalker) Exit(n Node) hcl.Diagnostics {
	switch n.(type) {
	case ChildScope:
		// pop the latest local scope, assuming that the walker will
		// behave symmetrically as promised.
		w.localScopes = w.localScopes[:len(w.localScopes)-1]
	}
	return nil
}

// ChildScope is a synthetic AST node that is visited during a walk to
// indicate that its descendent will be evaluated in a child scope, which
// may mask certain variables from the parent scope as locals.
//
// ChildScope nodes don't really exist in the AST, but are rather synthesized
// on the fly during walk. Therefore it doesn't do any good to transform them;
// instead, transform either parent node that created a scope or the expression
// that the child scope struct wraps.
type ChildScope struct {
	LocalNames map[string]struct{}
	Expr       *Expression // pointer because it can be replaced on walk
}

func (e ChildScope) walkChildNodes(w internalWalkFunc) {
	*(e.Expr) = w(*(e.Expr)).(Expression)
}

// Range returns the range of the expression that the ChildScope is
// encapsulating. It isn't really very useful to call Range on a ChildScope.
func (e ChildScope) Range() hcl.Range {
	return (*e.Expr).Range()
}