1package hclsyntax
2
3import (
4	"github.com/hashicorp/hcl2/hcl"
5)
6
7// Variables returns all of the variables referenced within a given experssion.
8//
9// This is the implementation of the "Variables" method on every native
10// expression.
11func Variables(expr Expression) []hcl.Traversal {
12	var vars []hcl.Traversal
13
14	walker := &variablesWalker{
15		Callback: func(t hcl.Traversal) {
16			vars = append(vars, t)
17		},
18	}
19
20	Walk(expr, walker)
21
22	return vars
23}
24
25// variablesWalker is a Walker implementation that calls its callback for any
26// root scope traversal found while walking.
27type variablesWalker struct {
28	Callback    func(hcl.Traversal)
29	localScopes []map[string]struct{}
30}
31
32func (w *variablesWalker) Enter(n Node) hcl.Diagnostics {
33	switch tn := n.(type) {
34	case *ScopeTraversalExpr:
35		t := tn.Traversal
36
37		// Check if the given root name appears in any of the active
38		// local scopes. We don't want to return local variables here, since
39		// the goal of walking variables is to tell the calling application
40		// which names it needs to populate in the _root_ scope.
41		name := t.RootName()
42		for _, names := range w.localScopes {
43			if _, localized := names[name]; localized {
44				return nil
45			}
46		}
47
48		w.Callback(t)
49	case ChildScope:
50		w.localScopes = append(w.localScopes, tn.LocalNames)
51	}
52	return nil
53}
54
55func (w *variablesWalker) Exit(n Node) hcl.Diagnostics {
56	switch n.(type) {
57	case ChildScope:
58		// pop the latest local scope, assuming that the walker will
59		// behave symmetrically as promised.
60		w.localScopes = w.localScopes[:len(w.localScopes)-1]
61	}
62	return nil
63}
64
65// ChildScope is a synthetic AST node that is visited during a walk to
66// indicate that its descendent will be evaluated in a child scope, which
67// may mask certain variables from the parent scope as locals.
68//
69// ChildScope nodes don't really exist in the AST, but are rather synthesized
70// on the fly during walk. Therefore it doesn't do any good to transform them;
71// instead, transform either parent node that created a scope or the expression
72// that the child scope struct wraps.
73type ChildScope struct {
74	LocalNames map[string]struct{}
75	Expr       Expression
76}
77
78func (e ChildScope) walkChildNodes(w internalWalkFunc) {
79	w(e.Expr)
80}
81
82// Range returns the range of the expression that the ChildScope is
83// encapsulating. It isn't really very useful to call Range on a ChildScope.
84func (e ChildScope) Range() hcl.Range {
85	return e.Expr.Range()
86}
87