jinja2.idtracking

  1import typing as t
  2
  3from . import nodes
  4from .visitor import NodeVisitor
  5
  6VAR_LOAD_PARAMETER = "param"
  7VAR_LOAD_RESOLVE = "resolve"
  8VAR_LOAD_ALIAS = "alias"
  9VAR_LOAD_UNDEFINED = "undefined"
 10
 11
 12def find_symbols(
 13    nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None
 14) -> "Symbols":
 15    sym = Symbols(parent=parent_symbols)
 16    visitor = FrameSymbolVisitor(sym)
 17    for node in nodes:
 18        visitor.visit(node)
 19    return sym
 20
 21
 22def symbols_for_node(
 23    node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None
 24) -> "Symbols":
 25    sym = Symbols(parent=parent_symbols)
 26    sym.analyze_node(node)
 27    return sym
 28
 29
 30class Symbols:
 31    def __init__(
 32        self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
 33    ) -> None:
 34        if level is None:
 35            if parent is None:
 36                level = 0
 37            else:
 38                level = parent.level + 1
 39
 40        self.level: int = level
 41        self.parent = parent
 42        self.refs: t.Dict[str, str] = {}
 43        self.loads: t.Dict[str, t.Any] = {}
 44        self.stores: t.Set[str] = set()
 45
 46    def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None:
 47        visitor = RootVisitor(self)
 48        visitor.visit(node, **kwargs)
 49
 50    def _define_ref(
 51        self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None
 52    ) -> str:
 53        ident = f"l_{self.level}_{name}"
 54        self.refs[name] = ident
 55        if load is not None:
 56            self.loads[ident] = load
 57        return ident
 58
 59    def find_load(self, target: str) -> t.Optional[t.Any]:
 60        if target in self.loads:
 61            return self.loads[target]
 62
 63        if self.parent is not None:
 64            return self.parent.find_load(target)
 65
 66        return None
 67
 68    def find_ref(self, name: str) -> t.Optional[str]:
 69        if name in self.refs:
 70            return self.refs[name]
 71
 72        if self.parent is not None:
 73            return self.parent.find_ref(name)
 74
 75        return None
 76
 77    def ref(self, name: str) -> str:
 78        rv = self.find_ref(name)
 79        if rv is None:
 80            raise AssertionError(
 81                "Tried to resolve a name to a reference that was"
 82                f" unknown to the frame ({name!r})"
 83            )
 84        return rv
 85
 86    def copy(self) -> "Symbols":
 87        rv = object.__new__(self.__class__)
 88        rv.__dict__.update(self.__dict__)
 89        rv.refs = self.refs.copy()
 90        rv.loads = self.loads.copy()
 91        rv.stores = self.stores.copy()
 92        return rv
 93
 94    def store(self, name: str) -> None:
 95        self.stores.add(name)
 96
 97        # If we have not see the name referenced yet, we need to figure
 98        # out what to set it to.
 99        if name not in self.refs:
100            # If there is a parent scope we check if the name has a
101            # reference there.  If it does it means we might have to alias
102            # to a variable there.
103            if self.parent is not None:
104                outer_ref = self.parent.find_ref(name)
105                if outer_ref is not None:
106                    self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
107                    return
108
109            # Otherwise we can just set it to undefined.
110            self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
111
112    def declare_parameter(self, name: str) -> str:
113        self.stores.add(name)
114        return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
115
116    def load(self, name: str) -> None:
117        if self.find_ref(name) is None:
118            self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
119
120    def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
121        stores: t.Dict[str, int] = {}
122        for branch in branch_symbols:
123            for target in branch.stores:
124                if target in self.stores:
125                    continue
126                stores[target] = stores.get(target, 0) + 1
127
128        for sym in branch_symbols:
129            self.refs.update(sym.refs)
130            self.loads.update(sym.loads)
131            self.stores.update(sym.stores)
132
133        for name, branch_count in stores.items():
134            if branch_count == len(branch_symbols):
135                continue
136
137            target = self.find_ref(name)  # type: ignore
138            assert target is not None, "should not happen"
139
140            if self.parent is not None:
141                outer_target = self.parent.find_ref(name)
142                if outer_target is not None:
143                    self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
144                    continue
145            self.loads[target] = (VAR_LOAD_RESOLVE, name)
146
147    def dump_stores(self) -> t.Dict[str, str]:
148        rv: t.Dict[str, str] = {}
149        node: t.Optional["Symbols"] = self
150
151        while node is not None:
152            for name in sorted(node.stores):
153                if name not in rv:
154                    rv[name] = self.find_ref(name)  # type: ignore
155
156            node = node.parent
157
158        return rv
159
160    def dump_param_targets(self) -> t.Set[str]:
161        rv = set()
162        node: t.Optional["Symbols"] = self
163
164        while node is not None:
165            for target, (instr, _) in self.loads.items():
166                if instr == VAR_LOAD_PARAMETER:
167                    rv.add(target)
168
169            node = node.parent
170
171        return rv
172
173
174class RootVisitor(NodeVisitor):
175    def __init__(self, symbols: "Symbols") -> None:
176        self.sym_visitor = FrameSymbolVisitor(symbols)
177
178    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
179        for child in node.iter_child_nodes():
180            self.sym_visitor.visit(child)
181
182    visit_Template = _simple_visit
183    visit_Block = _simple_visit
184    visit_Macro = _simple_visit
185    visit_FilterBlock = _simple_visit
186    visit_Scope = _simple_visit
187    visit_If = _simple_visit
188    visit_ScopedEvalContextModifier = _simple_visit
189
190    def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
191        for child in node.body:
192            self.sym_visitor.visit(child)
193
194    def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
195        for child in node.iter_child_nodes(exclude=("call",)):
196            self.sym_visitor.visit(child)
197
198    def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
199        for child in node.body:
200            self.sym_visitor.visit(child)
201
202    def visit_For(
203        self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any
204    ) -> None:
205        if for_branch == "body":
206            self.sym_visitor.visit(node.target, store_as_param=True)
207            branch = node.body
208        elif for_branch == "else":
209            branch = node.else_
210        elif for_branch == "test":
211            self.sym_visitor.visit(node.target, store_as_param=True)
212            if node.test is not None:
213                self.sym_visitor.visit(node.test)
214            return
215        else:
216            raise RuntimeError("Unknown for branch")
217
218        if branch:
219            for item in branch:
220                self.sym_visitor.visit(item)
221
222    def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
223        for target in node.targets:
224            self.sym_visitor.visit(target)
225        for child in node.body:
226            self.sym_visitor.visit(child)
227
228    def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None:
229        raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}")
230
231
232class FrameSymbolVisitor(NodeVisitor):
233    """A visitor for `Frame.inspect`."""
234
235    def __init__(self, symbols: "Symbols") -> None:
236        self.symbols = symbols
237
238    def visit_Name(
239        self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any
240    ) -> None:
241        """All assignments to names go through this function."""
242        if store_as_param or node.ctx == "param":
243            self.symbols.declare_parameter(node.name)
244        elif node.ctx == "store":
245            self.symbols.store(node.name)
246        elif node.ctx == "load":
247            self.symbols.load(node.name)
248
249    def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None:
250        self.symbols.load(node.name)
251
252    def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None:
253        self.visit(node.test, **kwargs)
254        original_symbols = self.symbols
255
256        def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols":
257            self.symbols = rv = original_symbols.copy()
258
259            for subnode in nodes:
260                self.visit(subnode, **kwargs)
261
262            self.symbols = original_symbols
263            return rv
264
265        body_symbols = inner_visit(node.body)
266        elif_symbols = inner_visit(node.elif_)
267        else_symbols = inner_visit(node.else_ or ())
268        self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
269
270    def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None:
271        self.symbols.store(node.name)
272
273    def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None:
274        self.generic_visit(node, **kwargs)
275        self.symbols.store(node.target)
276
277    def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None:
278        self.generic_visit(node, **kwargs)
279
280        for name in node.names:
281            if isinstance(name, tuple):
282                self.symbols.store(name[1])
283            else:
284                self.symbols.store(name)
285
286    def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None:
287        """Visit assignments in the correct order."""
288        self.visit(node.node, **kwargs)
289        self.visit(node.target, **kwargs)
290
291    def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None:
292        """Visiting stops at for blocks.  However the block sequence
293        is visited as part of the outer scope.
294        """
295        self.visit(node.iter, **kwargs)
296
297    def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
298        self.visit(node.call, **kwargs)
299
300    def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None:
301        self.visit(node.filter, **kwargs)
302
303    def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
304        for target in node.values:
305            self.visit(target)
306
307    def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
308        """Stop visiting at block assigns."""
309        self.visit(node.target, **kwargs)
310
311    def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None:
312        """Stop visiting at scopes."""
313
314    def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None:
315        """Stop visiting at blocks."""
316
317    def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
318        """Do not visit into overlay scopes."""
VAR_LOAD_PARAMETER = 'param'
VAR_LOAD_RESOLVE = 'resolve'
VAR_LOAD_ALIAS = 'alias'
VAR_LOAD_UNDEFINED = 'undefined'
def find_symbols( nodes: Iterable[jinja2.nodes.Node], parent_symbols: Optional[Symbols] = None) -> Symbols:
13def find_symbols(
14    nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None
15) -> "Symbols":
16    sym = Symbols(parent=parent_symbols)
17    visitor = FrameSymbolVisitor(sym)
18    for node in nodes:
19        visitor.visit(node)
20    return sym
def symbols_for_node( node: jinja2.nodes.Node, parent_symbols: Optional[Symbols] = None) -> Symbols:
23def symbols_for_node(
24    node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None
25) -> "Symbols":
26    sym = Symbols(parent=parent_symbols)
27    sym.analyze_node(node)
28    return sym
class Symbols:
 31class Symbols:
 32    def __init__(
 33        self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
 34    ) -> None:
 35        if level is None:
 36            if parent is None:
 37                level = 0
 38            else:
 39                level = parent.level + 1
 40
 41        self.level: int = level
 42        self.parent = parent
 43        self.refs: t.Dict[str, str] = {}
 44        self.loads: t.Dict[str, t.Any] = {}
 45        self.stores: t.Set[str] = set()
 46
 47    def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None:
 48        visitor = RootVisitor(self)
 49        visitor.visit(node, **kwargs)
 50
 51    def _define_ref(
 52        self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None
 53    ) -> str:
 54        ident = f"l_{self.level}_{name}"
 55        self.refs[name] = ident
 56        if load is not None:
 57            self.loads[ident] = load
 58        return ident
 59
 60    def find_load(self, target: str) -> t.Optional[t.Any]:
 61        if target in self.loads:
 62            return self.loads[target]
 63
 64        if self.parent is not None:
 65            return self.parent.find_load(target)
 66
 67        return None
 68
 69    def find_ref(self, name: str) -> t.Optional[str]:
 70        if name in self.refs:
 71            return self.refs[name]
 72
 73        if self.parent is not None:
 74            return self.parent.find_ref(name)
 75
 76        return None
 77
 78    def ref(self, name: str) -> str:
 79        rv = self.find_ref(name)
 80        if rv is None:
 81            raise AssertionError(
 82                "Tried to resolve a name to a reference that was"
 83                f" unknown to the frame ({name!r})"
 84            )
 85        return rv
 86
 87    def copy(self) -> "Symbols":
 88        rv = object.__new__(self.__class__)
 89        rv.__dict__.update(self.__dict__)
 90        rv.refs = self.refs.copy()
 91        rv.loads = self.loads.copy()
 92        rv.stores = self.stores.copy()
 93        return rv
 94
 95    def store(self, name: str) -> None:
 96        self.stores.add(name)
 97
 98        # If we have not see the name referenced yet, we need to figure
 99        # out what to set it to.
100        if name not in self.refs:
101            # If there is a parent scope we check if the name has a
102            # reference there.  If it does it means we might have to alias
103            # to a variable there.
104            if self.parent is not None:
105                outer_ref = self.parent.find_ref(name)
106                if outer_ref is not None:
107                    self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
108                    return
109
110            # Otherwise we can just set it to undefined.
111            self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
112
113    def declare_parameter(self, name: str) -> str:
114        self.stores.add(name)
115        return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
116
117    def load(self, name: str) -> None:
118        if self.find_ref(name) is None:
119            self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
120
121    def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
122        stores: t.Dict[str, int] = {}
123        for branch in branch_symbols:
124            for target in branch.stores:
125                if target in self.stores:
126                    continue
127                stores[target] = stores.get(target, 0) + 1
128
129        for sym in branch_symbols:
130            self.refs.update(sym.refs)
131            self.loads.update(sym.loads)
132            self.stores.update(sym.stores)
133
134        for name, branch_count in stores.items():
135            if branch_count == len(branch_symbols):
136                continue
137
138            target = self.find_ref(name)  # type: ignore
139            assert target is not None, "should not happen"
140
141            if self.parent is not None:
142                outer_target = self.parent.find_ref(name)
143                if outer_target is not None:
144                    self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
145                    continue
146            self.loads[target] = (VAR_LOAD_RESOLVE, name)
147
148    def dump_stores(self) -> t.Dict[str, str]:
149        rv: t.Dict[str, str] = {}
150        node: t.Optional["Symbols"] = self
151
152        while node is not None:
153            for name in sorted(node.stores):
154                if name not in rv:
155                    rv[name] = self.find_ref(name)  # type: ignore
156
157            node = node.parent
158
159        return rv
160
161    def dump_param_targets(self) -> t.Set[str]:
162        rv = set()
163        node: t.Optional["Symbols"] = self
164
165        while node is not None:
166            for target, (instr, _) in self.loads.items():
167                if instr == VAR_LOAD_PARAMETER:
168                    rv.add(target)
169
170            node = node.parent
171
172        return rv
Symbols( parent: Optional[Symbols] = None, level: Optional[int] = None)
32    def __init__(
33        self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
34    ) -> None:
35        if level is None:
36            if parent is None:
37                level = 0
38            else:
39                level = parent.level + 1
40
41        self.level: int = level
42        self.parent = parent
43        self.refs: t.Dict[str, str] = {}
44        self.loads: t.Dict[str, t.Any] = {}
45        self.stores: t.Set[str] = set()
level: int
parent
refs: Dict[str, str]
loads: Dict[str, Any]
stores: Set[str]
def analyze_node(self, node: jinja2.nodes.Node, **kwargs: Any) -> None:
47    def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None:
48        visitor = RootVisitor(self)
49        visitor.visit(node, **kwargs)
def find_load(self, target: str) -> Optional[Any]:
60    def find_load(self, target: str) -> t.Optional[t.Any]:
61        if target in self.loads:
62            return self.loads[target]
63
64        if self.parent is not None:
65            return self.parent.find_load(target)
66
67        return None
def find_ref(self, name: str) -> Optional[str]:
69    def find_ref(self, name: str) -> t.Optional[str]:
70        if name in self.refs:
71            return self.refs[name]
72
73        if self.parent is not None:
74            return self.parent.find_ref(name)
75
76        return None
def ref(self, name: str) -> str:
78    def ref(self, name: str) -> str:
79        rv = self.find_ref(name)
80        if rv is None:
81            raise AssertionError(
82                "Tried to resolve a name to a reference that was"
83                f" unknown to the frame ({name!r})"
84            )
85        return rv
def copy(self) -> Symbols:
87    def copy(self) -> "Symbols":
88        rv = object.__new__(self.__class__)
89        rv.__dict__.update(self.__dict__)
90        rv.refs = self.refs.copy()
91        rv.loads = self.loads.copy()
92        rv.stores = self.stores.copy()
93        return rv
def store(self, name: str) -> None:
 95    def store(self, name: str) -> None:
 96        self.stores.add(name)
 97
 98        # If we have not see the name referenced yet, we need to figure
 99        # out what to set it to.
100        if name not in self.refs:
101            # If there is a parent scope we check if the name has a
102            # reference there.  If it does it means we might have to alias
103            # to a variable there.
104            if self.parent is not None:
105                outer_ref = self.parent.find_ref(name)
106                if outer_ref is not None:
107                    self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
108                    return
109
110            # Otherwise we can just set it to undefined.
111            self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
def declare_parameter(self, name: str) -> str:
113    def declare_parameter(self, name: str) -> str:
114        self.stores.add(name)
115        return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
def load(self, name: str) -> None:
117    def load(self, name: str) -> None:
118        if self.find_ref(name) is None:
119            self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
def branch_update(self, branch_symbols: Sequence[Symbols]) -> None:
121    def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
122        stores: t.Dict[str, int] = {}
123        for branch in branch_symbols:
124            for target in branch.stores:
125                if target in self.stores:
126                    continue
127                stores[target] = stores.get(target, 0) + 1
128
129        for sym in branch_symbols:
130            self.refs.update(sym.refs)
131            self.loads.update(sym.loads)
132            self.stores.update(sym.stores)
133
134        for name, branch_count in stores.items():
135            if branch_count == len(branch_symbols):
136                continue
137
138            target = self.find_ref(name)  # type: ignore
139            assert target is not None, "should not happen"
140
141            if self.parent is not None:
142                outer_target = self.parent.find_ref(name)
143                if outer_target is not None:
144                    self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
145                    continue
146            self.loads[target] = (VAR_LOAD_RESOLVE, name)
def dump_stores(self) -> Dict[str, str]:
148    def dump_stores(self) -> t.Dict[str, str]:
149        rv: t.Dict[str, str] = {}
150        node: t.Optional["Symbols"] = self
151
152        while node is not None:
153            for name in sorted(node.stores):
154                if name not in rv:
155                    rv[name] = self.find_ref(name)  # type: ignore
156
157            node = node.parent
158
159        return rv
def dump_param_targets(self) -> Set[str]:
161    def dump_param_targets(self) -> t.Set[str]:
162        rv = set()
163        node: t.Optional["Symbols"] = self
164
165        while node is not None:
166            for target, (instr, _) in self.loads.items():
167                if instr == VAR_LOAD_PARAMETER:
168                    rv.add(target)
169
170            node = node.parent
171
172        return rv
class RootVisitor(jinja2.visitor.NodeVisitor):
175class RootVisitor(NodeVisitor):
176    def __init__(self, symbols: "Symbols") -> None:
177        self.sym_visitor = FrameSymbolVisitor(symbols)
178
179    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
180        for child in node.iter_child_nodes():
181            self.sym_visitor.visit(child)
182
183    visit_Template = _simple_visit
184    visit_Block = _simple_visit
185    visit_Macro = _simple_visit
186    visit_FilterBlock = _simple_visit
187    visit_Scope = _simple_visit
188    visit_If = _simple_visit
189    visit_ScopedEvalContextModifier = _simple_visit
190
191    def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
192        for child in node.body:
193            self.sym_visitor.visit(child)
194
195    def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
196        for child in node.iter_child_nodes(exclude=("call",)):
197            self.sym_visitor.visit(child)
198
199    def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
200        for child in node.body:
201            self.sym_visitor.visit(child)
202
203    def visit_For(
204        self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any
205    ) -> None:
206        if for_branch == "body":
207            self.sym_visitor.visit(node.target, store_as_param=True)
208            branch = node.body
209        elif for_branch == "else":
210            branch = node.else_
211        elif for_branch == "test":
212            self.sym_visitor.visit(node.target, store_as_param=True)
213            if node.test is not None:
214                self.sym_visitor.visit(node.test)
215            return
216        else:
217            raise RuntimeError("Unknown for branch")
218
219        if branch:
220            for item in branch:
221                self.sym_visitor.visit(item)
222
223    def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
224        for target in node.targets:
225            self.sym_visitor.visit(target)
226        for child in node.body:
227            self.sym_visitor.visit(child)
228
229    def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None:
230        raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}")

Walks the abstract syntax tree and call visitor functions for every node found. The visitor functions may return values which will be forwarded by the visit method.

Per default the visitor functions for the nodes are 'visit_' + class name of the node. So a TryFinally node visit function would be visit_TryFinally. This behavior can be changed by overriding the get_visitor function. If no visitor function exists for a node (return value None) the generic_visit visitor is used instead.

RootVisitor(symbols: Symbols)
176    def __init__(self, symbols: "Symbols") -> None:
177        self.sym_visitor = FrameSymbolVisitor(symbols)
sym_visitor
def visit_Template(self, node: jinja2.nodes.Node, **kwargs: Any) -> None:
179    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
180        for child in node.iter_child_nodes():
181            self.sym_visitor.visit(child)
def visit_Block(self, node: jinja2.nodes.Node, **kwargs: Any) -> None:
179    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
180        for child in node.iter_child_nodes():
181            self.sym_visitor.visit(child)
def visit_Macro(self, node: jinja2.nodes.Node, **kwargs: Any) -> None:
179    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
180        for child in node.iter_child_nodes():
181            self.sym_visitor.visit(child)
def visit_FilterBlock(self, node: jinja2.nodes.Node, **kwargs: Any) -> None:
179    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
180        for child in node.iter_child_nodes():
181            self.sym_visitor.visit(child)
def visit_Scope(self, node: jinja2.nodes.Node, **kwargs: Any) -> None:
179    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
180        for child in node.iter_child_nodes():
181            self.sym_visitor.visit(child)
def visit_If(self, node: jinja2.nodes.Node, **kwargs: Any) -> None:
179    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
180        for child in node.iter_child_nodes():
181            self.sym_visitor.visit(child)
def visit_ScopedEvalContextModifier(self, node: jinja2.nodes.Node, **kwargs: Any) -> None:
179    def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
180        for child in node.iter_child_nodes():
181            self.sym_visitor.visit(child)
def visit_AssignBlock(self, node: jinja2.nodes.AssignBlock, **kwargs: Any) -> None:
191    def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
192        for child in node.body:
193            self.sym_visitor.visit(child)
def visit_CallBlock(self, node: jinja2.nodes.CallBlock, **kwargs: Any) -> None:
195    def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
196        for child in node.iter_child_nodes(exclude=("call",)):
197            self.sym_visitor.visit(child)
def visit_OverlayScope(self, node: jinja2.nodes.OverlayScope, **kwargs: Any) -> None:
199    def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
200        for child in node.body:
201            self.sym_visitor.visit(child)
def visit_For( self, node: jinja2.nodes.For, for_branch: str = 'body', **kwargs: Any) -> None:
203    def visit_For(
204        self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any
205    ) -> None:
206        if for_branch == "body":
207            self.sym_visitor.visit(node.target, store_as_param=True)
208            branch = node.body
209        elif for_branch == "else":
210            branch = node.else_
211        elif for_branch == "test":
212            self.sym_visitor.visit(node.target, store_as_param=True)
213            if node.test is not None:
214                self.sym_visitor.visit(node.test)
215            return
216        else:
217            raise RuntimeError("Unknown for branch")
218
219        if branch:
220            for item in branch:
221                self.sym_visitor.visit(item)
def visit_With(self, node: jinja2.nodes.With, **kwargs: Any) -> None:
223    def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
224        for target in node.targets:
225            self.sym_visitor.visit(target)
226        for child in node.body:
227            self.sym_visitor.visit(child)
def generic_visit(self, node: jinja2.nodes.Node, *args: Any, **kwargs: Any) -> None:
229    def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None:
230        raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}")

Called if no explicit visitor function exists for a node.

class FrameSymbolVisitor(jinja2.visitor.NodeVisitor):
233class FrameSymbolVisitor(NodeVisitor):
234    """A visitor for `Frame.inspect`."""
235
236    def __init__(self, symbols: "Symbols") -> None:
237        self.symbols = symbols
238
239    def visit_Name(
240        self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any
241    ) -> None:
242        """All assignments to names go through this function."""
243        if store_as_param or node.ctx == "param":
244            self.symbols.declare_parameter(node.name)
245        elif node.ctx == "store":
246            self.symbols.store(node.name)
247        elif node.ctx == "load":
248            self.symbols.load(node.name)
249
250    def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None:
251        self.symbols.load(node.name)
252
253    def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None:
254        self.visit(node.test, **kwargs)
255        original_symbols = self.symbols
256
257        def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols":
258            self.symbols = rv = original_symbols.copy()
259
260            for subnode in nodes:
261                self.visit(subnode, **kwargs)
262
263            self.symbols = original_symbols
264            return rv
265
266        body_symbols = inner_visit(node.body)
267        elif_symbols = inner_visit(node.elif_)
268        else_symbols = inner_visit(node.else_ or ())
269        self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
270
271    def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None:
272        self.symbols.store(node.name)
273
274    def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None:
275        self.generic_visit(node, **kwargs)
276        self.symbols.store(node.target)
277
278    def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None:
279        self.generic_visit(node, **kwargs)
280
281        for name in node.names:
282            if isinstance(name, tuple):
283                self.symbols.store(name[1])
284            else:
285                self.symbols.store(name)
286
287    def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None:
288        """Visit assignments in the correct order."""
289        self.visit(node.node, **kwargs)
290        self.visit(node.target, **kwargs)
291
292    def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None:
293        """Visiting stops at for blocks.  However the block sequence
294        is visited as part of the outer scope.
295        """
296        self.visit(node.iter, **kwargs)
297
298    def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
299        self.visit(node.call, **kwargs)
300
301    def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None:
302        self.visit(node.filter, **kwargs)
303
304    def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
305        for target in node.values:
306            self.visit(target)
307
308    def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
309        """Stop visiting at block assigns."""
310        self.visit(node.target, **kwargs)
311
312    def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None:
313        """Stop visiting at scopes."""
314
315    def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None:
316        """Stop visiting at blocks."""
317
318    def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
319        """Do not visit into overlay scopes."""

A visitor for Frame.inspect.

FrameSymbolVisitor(symbols: Symbols)
236    def __init__(self, symbols: "Symbols") -> None:
237        self.symbols = symbols
symbols
def visit_Name( self, node: jinja2.nodes.Name, store_as_param: bool = False, **kwargs: Any) -> None:
239    def visit_Name(
240        self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any
241    ) -> None:
242        """All assignments to names go through this function."""
243        if store_as_param or node.ctx == "param":
244            self.symbols.declare_parameter(node.name)
245        elif node.ctx == "store":
246            self.symbols.store(node.name)
247        elif node.ctx == "load":
248            self.symbols.load(node.name)

All assignments to names go through this function.

def visit_NSRef(self, node: jinja2.nodes.NSRef, **kwargs: Any) -> None:
250    def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None:
251        self.symbols.load(node.name)
def visit_If(self, node: jinja2.nodes.If, **kwargs: Any) -> None:
253    def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None:
254        self.visit(node.test, **kwargs)
255        original_symbols = self.symbols
256
257        def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols":
258            self.symbols = rv = original_symbols.copy()
259
260            for subnode in nodes:
261                self.visit(subnode, **kwargs)
262
263            self.symbols = original_symbols
264            return rv
265
266        body_symbols = inner_visit(node.body)
267        elif_symbols = inner_visit(node.elif_)
268        else_symbols = inner_visit(node.else_ or ())
269        self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
def visit_Macro(self, node: jinja2.nodes.Macro, **kwargs: Any) -> None:
271    def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None:
272        self.symbols.store(node.name)
def visit_Import(self, node: jinja2.nodes.Import, **kwargs: Any) -> None:
274    def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None:
275        self.generic_visit(node, **kwargs)
276        self.symbols.store(node.target)
def visit_FromImport(self, node: jinja2.nodes.FromImport, **kwargs: Any) -> None:
278    def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None:
279        self.generic_visit(node, **kwargs)
280
281        for name in node.names:
282            if isinstance(name, tuple):
283                self.symbols.store(name[1])
284            else:
285                self.symbols.store(name)
def visit_Assign(self, node: jinja2.nodes.Assign, **kwargs: Any) -> None:
287    def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None:
288        """Visit assignments in the correct order."""
289        self.visit(node.node, **kwargs)
290        self.visit(node.target, **kwargs)

Visit assignments in the correct order.

def visit_For(self, node: jinja2.nodes.For, **kwargs: Any) -> None:
292    def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None:
293        """Visiting stops at for blocks.  However the block sequence
294        is visited as part of the outer scope.
295        """
296        self.visit(node.iter, **kwargs)

Visiting stops at for blocks. However the block sequence is visited as part of the outer scope.

def visit_CallBlock(self, node: jinja2.nodes.CallBlock, **kwargs: Any) -> None:
298    def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
299        self.visit(node.call, **kwargs)
def visit_FilterBlock(self, node: jinja2.nodes.FilterBlock, **kwargs: Any) -> None:
301    def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None:
302        self.visit(node.filter, **kwargs)
def visit_With(self, node: jinja2.nodes.With, **kwargs: Any) -> None:
304    def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
305        for target in node.values:
306            self.visit(target)
def visit_AssignBlock(self, node: jinja2.nodes.AssignBlock, **kwargs: Any) -> None:
308    def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
309        """Stop visiting at block assigns."""
310        self.visit(node.target, **kwargs)

Stop visiting at block assigns.

def visit_Scope(self, node: jinja2.nodes.Scope, **kwargs: Any) -> None:
312    def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None:
313        """Stop visiting at scopes."""

Stop visiting at scopes.

def visit_Block(self, node: jinja2.nodes.Block, **kwargs: Any) -> None:
315    def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None:
316        """Stop visiting at blocks."""

Stop visiting at blocks.

def visit_OverlayScope(self, node: jinja2.nodes.OverlayScope, **kwargs: Any) -> None:
318    def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
319        """Do not visit into overlay scopes."""

Do not visit into overlay scopes.