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:
2025-12-21 22:18:04 +01:00
parent f9db85a33b
commit db6a76667a

View File

@@ -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