simplified

This commit is contained in:
2025-09-16 16:51:50 +02:00
parent 6772c1561c
commit 045a9f669a
9 changed files with 732 additions and 276 deletions

143
test_jinja2_analysis.py Normal file
View 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
""")