1e31aef6aSopenharmony_ci"""Functions that expose information about templates that might be
2e31aef6aSopenharmony_ciinteresting for introspection.
3e31aef6aSopenharmony_ci"""
4e31aef6aSopenharmony_ciimport typing as t
5e31aef6aSopenharmony_ci
6e31aef6aSopenharmony_cifrom . import nodes
7e31aef6aSopenharmony_cifrom .compiler import CodeGenerator
8e31aef6aSopenharmony_cifrom .compiler import Frame
9e31aef6aSopenharmony_ci
10e31aef6aSopenharmony_ciif t.TYPE_CHECKING:
11e31aef6aSopenharmony_ci    from .environment import Environment
12e31aef6aSopenharmony_ci
13e31aef6aSopenharmony_ci
14e31aef6aSopenharmony_ciclass TrackingCodeGenerator(CodeGenerator):
15e31aef6aSopenharmony_ci    """We abuse the code generator for introspection."""
16e31aef6aSopenharmony_ci
17e31aef6aSopenharmony_ci    def __init__(self, environment: "Environment") -> None:
18e31aef6aSopenharmony_ci        super().__init__(environment, "<introspection>", "<introspection>")
19e31aef6aSopenharmony_ci        self.undeclared_identifiers: t.Set[str] = set()
20e31aef6aSopenharmony_ci
21e31aef6aSopenharmony_ci    def write(self, x: str) -> None:
22e31aef6aSopenharmony_ci        """Don't write."""
23e31aef6aSopenharmony_ci
24e31aef6aSopenharmony_ci    def enter_frame(self, frame: Frame) -> None:
25e31aef6aSopenharmony_ci        """Remember all undeclared identifiers."""
26e31aef6aSopenharmony_ci        super().enter_frame(frame)
27e31aef6aSopenharmony_ci
28e31aef6aSopenharmony_ci        for _, (action, param) in frame.symbols.loads.items():
29e31aef6aSopenharmony_ci            if action == "resolve" and param not in self.environment.globals:
30e31aef6aSopenharmony_ci                self.undeclared_identifiers.add(param)
31e31aef6aSopenharmony_ci
32e31aef6aSopenharmony_ci
33e31aef6aSopenharmony_cidef find_undeclared_variables(ast: nodes.Template) -> t.Set[str]:
34e31aef6aSopenharmony_ci    """Returns a set of all variables in the AST that will be looked up from
35e31aef6aSopenharmony_ci    the context at runtime.  Because at compile time it's not known which
36e31aef6aSopenharmony_ci    variables will be used depending on the path the execution takes at
37e31aef6aSopenharmony_ci    runtime, all variables are returned.
38e31aef6aSopenharmony_ci
39e31aef6aSopenharmony_ci    >>> from jinja2 import Environment, meta
40e31aef6aSopenharmony_ci    >>> env = Environment()
41e31aef6aSopenharmony_ci    >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
42e31aef6aSopenharmony_ci    >>> meta.find_undeclared_variables(ast) == {'bar'}
43e31aef6aSopenharmony_ci    True
44e31aef6aSopenharmony_ci
45e31aef6aSopenharmony_ci    .. admonition:: Implementation
46e31aef6aSopenharmony_ci
47e31aef6aSopenharmony_ci       Internally the code generator is used for finding undeclared variables.
48e31aef6aSopenharmony_ci       This is good to know because the code generator might raise a
49e31aef6aSopenharmony_ci       :exc:`TemplateAssertionError` during compilation and as a matter of
50e31aef6aSopenharmony_ci       fact this function can currently raise that exception as well.
51e31aef6aSopenharmony_ci    """
52e31aef6aSopenharmony_ci    codegen = TrackingCodeGenerator(ast.environment)  # type: ignore
53e31aef6aSopenharmony_ci    codegen.visit(ast)
54e31aef6aSopenharmony_ci    return codegen.undeclared_identifiers
55e31aef6aSopenharmony_ci
56e31aef6aSopenharmony_ci
57e31aef6aSopenharmony_ci_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)
58e31aef6aSopenharmony_ci_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]
59e31aef6aSopenharmony_ci
60e31aef6aSopenharmony_ci
61e31aef6aSopenharmony_cidef find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:
62e31aef6aSopenharmony_ci    """Finds all the referenced templates from the AST.  This will return an
63e31aef6aSopenharmony_ci    iterator over all the hardcoded template extensions, inclusions and
64e31aef6aSopenharmony_ci    imports.  If dynamic inheritance or inclusion is used, `None` will be
65e31aef6aSopenharmony_ci    yielded.
66e31aef6aSopenharmony_ci
67e31aef6aSopenharmony_ci    >>> from jinja2 import Environment, meta
68e31aef6aSopenharmony_ci    >>> env = Environment()
69e31aef6aSopenharmony_ci    >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
70e31aef6aSopenharmony_ci    >>> list(meta.find_referenced_templates(ast))
71e31aef6aSopenharmony_ci    ['layout.html', None]
72e31aef6aSopenharmony_ci
73e31aef6aSopenharmony_ci    This function is useful for dependency tracking.  For example if you want
74e31aef6aSopenharmony_ci    to rebuild parts of the website after a layout template has changed.
75e31aef6aSopenharmony_ci    """
76e31aef6aSopenharmony_ci    template_name: t.Any
77e31aef6aSopenharmony_ci
78e31aef6aSopenharmony_ci    for node in ast.find_all(_ref_types):
79e31aef6aSopenharmony_ci        template: nodes.Expr = node.template  # type: ignore
80e31aef6aSopenharmony_ci
81e31aef6aSopenharmony_ci        if not isinstance(template, nodes.Const):
82e31aef6aSopenharmony_ci            # a tuple with some non consts in there
83e31aef6aSopenharmony_ci            if isinstance(template, (nodes.Tuple, nodes.List)):
84e31aef6aSopenharmony_ci                for template_name in template.items:
85e31aef6aSopenharmony_ci                    # something const, only yield the strings and ignore
86e31aef6aSopenharmony_ci                    # non-string consts that really just make no sense
87e31aef6aSopenharmony_ci                    if isinstance(template_name, nodes.Const):
88e31aef6aSopenharmony_ci                        if isinstance(template_name.value, str):
89e31aef6aSopenharmony_ci                            yield template_name.value
90e31aef6aSopenharmony_ci                    # something dynamic in there
91e31aef6aSopenharmony_ci                    else:
92e31aef6aSopenharmony_ci                        yield None
93e31aef6aSopenharmony_ci            # something dynamic we don't know about here
94e31aef6aSopenharmony_ci            else:
95e31aef6aSopenharmony_ci                yield None
96e31aef6aSopenharmony_ci            continue
97e31aef6aSopenharmony_ci        # constant is a basestring, direct template name
98e31aef6aSopenharmony_ci        if isinstance(template.value, str):
99e31aef6aSopenharmony_ci            yield template.value
100e31aef6aSopenharmony_ci        # a tuple or list (latter *should* not happen) made of consts,
101e31aef6aSopenharmony_ci        # yield the consts that are strings.  We could warn here for
102e31aef6aSopenharmony_ci        # non string values
103e31aef6aSopenharmony_ci        elif isinstance(node, nodes.Include) and isinstance(
104e31aef6aSopenharmony_ci            template.value, (tuple, list)
105e31aef6aSopenharmony_ci        ):
106e31aef6aSopenharmony_ci            for template_name in template.value:
107e31aef6aSopenharmony_ci                if isinstance(template_name, str):
108e31aef6aSopenharmony_ci                    yield template_name
109e31aef6aSopenharmony_ci        # something else we don't care about, we could warn here
110e31aef6aSopenharmony_ci        else:
111e31aef6aSopenharmony_ci            yield None
112