simplified
This commit is contained in:
143
test_jinja2_analysis.py
Normal file
143
test_jinja2_analysis.py
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Explore Jinja2's AST to understand variable detection in conditionals"""
|
||||
|
||||
from jinja2 import Environment, meta, nodes
|
||||
|
||||
# Test different template patterns
|
||||
templates = [
|
||||
# Simple required variable
|
||||
("simple", "Hello {{ name }}!"),
|
||||
|
||||
# Variable in conditional (optional)
|
||||
("conditional", """{% if error %}
|
||||
Error: {{ error }}
|
||||
{% endif %}"""),
|
||||
|
||||
# Mixed required and optional
|
||||
("mixed", """Status: {{ status }}
|
||||
{% if error %}
|
||||
Error: {{ error }}
|
||||
{% endif %}"""),
|
||||
|
||||
# Nested conditionals
|
||||
("nested", """{% if user %}
|
||||
Name: {{ user.name }}
|
||||
{% if user.email %}
|
||||
Email: {{ user.email }}
|
||||
{% endif %}
|
||||
{% endif %}"""),
|
||||
|
||||
# Variable with default filter (makes it optional)
|
||||
("default_filter", "Hello {{ name | default('Guest') }}!"),
|
||||
|
||||
# Variable in for loop
|
||||
("for_loop", """{% for item in items %}
|
||||
- {{ item }}
|
||||
{% endfor %}"""),
|
||||
|
||||
# Complex with multiple contexts
|
||||
("complex", """Required: {{ required }}
|
||||
{% if optional1 %}
|
||||
Optional1: {{ optional1 }}
|
||||
{% endif %}
|
||||
Default: {{ optional2 | default('N/A') }}
|
||||
{% for item in items %}
|
||||
- {{ item }}
|
||||
{% endfor %}""")
|
||||
]
|
||||
|
||||
env = Environment()
|
||||
|
||||
print("=" * 70)
|
||||
print("Analyzing Jinja2 AST for Variable Detection")
|
||||
print("=" * 70)
|
||||
|
||||
for name, template in templates:
|
||||
print(f"\n{name.upper()} TEMPLATE:")
|
||||
print("-" * 40)
|
||||
print(template)
|
||||
print("-" * 40)
|
||||
|
||||
# Parse the template
|
||||
ast = env.parse(template)
|
||||
|
||||
# Get undeclared variables (what we currently use)
|
||||
undeclared = meta.find_undeclared_variables(ast)
|
||||
print(f"Undeclared variables: {undeclared}")
|
||||
|
||||
# Try to analyze the AST more deeply
|
||||
print("\nAST Analysis:")
|
||||
|
||||
def analyze_node(node, indent=0, context="root"):
|
||||
"""Recursively analyze AST nodes"""
|
||||
prefix = " " * indent
|
||||
|
||||
if isinstance(node, nodes.Name):
|
||||
print(f"{prefix}Variable '{node.name}' in context: {context}")
|
||||
|
||||
elif isinstance(node, nodes.If):
|
||||
print(f"{prefix}If block:")
|
||||
print(f"{prefix} Test expression:")
|
||||
analyze_node(node.test, indent + 2, "if_test")
|
||||
print(f"{prefix} Body:")
|
||||
for child in node.body:
|
||||
analyze_node(child, indent + 2, "if_body")
|
||||
if node.elif_:
|
||||
print(f"{prefix} Elif:")
|
||||
for child in node.elif_:
|
||||
analyze_node(child, indent + 2, "elif")
|
||||
if node.else_:
|
||||
print(f"{prefix} Else:")
|
||||
for child in node.else_:
|
||||
analyze_node(child, indent + 2, "else")
|
||||
|
||||
elif isinstance(node, nodes.For):
|
||||
print(f"{prefix}For loop:")
|
||||
print(f"{prefix} Target: {node.target}")
|
||||
print(f"{prefix} Iter:")
|
||||
analyze_node(node.iter, indent + 2, "for_iter")
|
||||
print(f"{prefix} Body:")
|
||||
for child in node.body:
|
||||
analyze_node(child, indent + 2, "for_body")
|
||||
|
||||
elif isinstance(node, nodes.Filter):
|
||||
print(f"{prefix}Filter: {node.name}")
|
||||
if node.name == "default":
|
||||
print(f"{prefix} (Makes variable optional!)")
|
||||
analyze_node(node.node, indent + 1, f"filter_{node.name}")
|
||||
|
||||
elif isinstance(node, nodes.Getattr):
|
||||
print(f"{prefix}Attribute access: .{node.attr}")
|
||||
analyze_node(node.node, indent + 1, "getattr")
|
||||
|
||||
elif isinstance(node, nodes.Output):
|
||||
print(f"{prefix}Output:")
|
||||
for child in node.nodes:
|
||||
analyze_node(child, indent + 1, "output")
|
||||
|
||||
elif isinstance(node, nodes.TemplateData):
|
||||
# Skip raw text
|
||||
pass
|
||||
|
||||
else:
|
||||
# Recurse into child nodes
|
||||
for child in node.iter_child_nodes():
|
||||
analyze_node(child, indent, context)
|
||||
|
||||
analyze_node(ast)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("INSIGHTS:")
|
||||
print("-" * 70)
|
||||
print("""
|
||||
1. Jinja2's `find_undeclared_variables()` returns ALL variables, regardless of context
|
||||
2. Variables in {% if %} conditions could be considered optional
|
||||
3. Variables with | default() filter are definitely optional
|
||||
4. We can walk the AST to identify context for each variable
|
||||
|
||||
Possible solution:
|
||||
- Walk the AST to categorize variables by context
|
||||
- Mark variables in if_test contexts as optional (unless also used elsewhere)
|
||||
- Mark variables with default filter as optional
|
||||
- Consider all others as required
|
||||
""")
|
||||
Reference in New Issue
Block a user