feat(validator): add TPL-009 rule for Alpine variable validation
Add new rule to detect when templates use Alpine variables (e.g., from error_state or action_dropdown macros) that are not defined in the corresponding JavaScript component. The rule: - Checks for error_state macro usage (requires 'error' variable) - Checks for action_dropdown macro with custom open_var/loading_var - Cross-references with the matching JS file - Reports errors when variables are missing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1102,6 +1102,56 @@ class ArchitectureValidator:
|
||||
suggestion=f"Use '{{% block {invalid_blocks[block_name]} %}}' instead",
|
||||
)
|
||||
|
||||
def _check_alpine_template_vars(
|
||||
self, file_path: Path, content: str, lines: list[str], js_content: str
|
||||
):
|
||||
"""TPL-009: Check that Alpine variables used in templates are defined in JS"""
|
||||
if "noqa: tpl-009" in content.lower():
|
||||
return
|
||||
|
||||
import re
|
||||
|
||||
# Common Alpine variable patterns that MUST be defined in the component
|
||||
# These are variables that are directly referenced in templates
|
||||
required_vars = set()
|
||||
|
||||
# Check for error_state macro usage (requires 'error' var)
|
||||
if "error_state(" in content and "error_var=" not in content:
|
||||
required_vars.add("error")
|
||||
|
||||
# Check for action_dropdown macro with custom vars
|
||||
dropdown_pattern = re.compile(
|
||||
r"action_dropdown\([^)]*open_var=['\"](\w+)['\"]"
|
||||
)
|
||||
for match in dropdown_pattern.finditer(content):
|
||||
required_vars.add(match.group(1))
|
||||
|
||||
dropdown_pattern2 = re.compile(
|
||||
r"action_dropdown\([^)]*loading_var=['\"](\w+)['\"]"
|
||||
)
|
||||
for match in dropdown_pattern2.finditer(content):
|
||||
required_vars.add(match.group(1))
|
||||
|
||||
# Check if variables are defined in JS
|
||||
for var in required_vars:
|
||||
# Check if variable is defined in JS (look for "varname:" pattern)
|
||||
var_pattern = re.compile(rf"\b{var}\s*:")
|
||||
if not var_pattern.search(js_content):
|
||||
# Find the line where it's used in template
|
||||
for i, line in enumerate(lines, 1):
|
||||
if var in line and ("error_state" in line or "action_dropdown" in line):
|
||||
self._add_violation(
|
||||
rule_id="TPL-009",
|
||||
rule_name="Alpine variables must be defined in JS component",
|
||||
severity=Severity.ERROR,
|
||||
file_path=file_path,
|
||||
line_number=i,
|
||||
message=f"Template uses Alpine variable '{var}' but it's not defined in the JS component",
|
||||
context=line.strip()[:80],
|
||||
suggestion=f"Add '{var}: null,' or '{var}: false,' to the Alpine component's return object",
|
||||
)
|
||||
break
|
||||
|
||||
def _check_pagination_macro_usage(
|
||||
self, file_path: Path, content: str, lines: list[str]
|
||||
):
|
||||
@@ -2717,6 +2767,16 @@ class ArchitectureValidator:
|
||||
if not is_base_or_partial:
|
||||
self._check_valid_block_names(file_path, content, lines)
|
||||
|
||||
# TPL-009: Check Alpine variables are defined in JS
|
||||
if not is_base_or_partial and not is_macro and not is_components_page:
|
||||
# Try to find corresponding JS file
|
||||
# Template: app/templates/admin/messages.html -> JS: static/admin/js/messages.js
|
||||
template_name = file_path.stem # e.g., "messages"
|
||||
js_file = target_path / f"static/admin/js/{template_name}.js"
|
||||
if js_file.exists():
|
||||
js_content = js_file.read_text()
|
||||
self._check_alpine_template_vars(file_path, content, lines, js_content)
|
||||
|
||||
# Skip base/partials for TPL-001 check
|
||||
if is_base_or_partial:
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user