feat: add architecture rules for macro API changes
Added TPL-013 and TPL-014 rules to detect deprecated macro usage:
TPL-013: Use new pagination macro API
- Detects old parameters: current_page, total_pages, page_numbers, etc.
- New API only accepts show_condition parameter
- Component must provide standardized Alpine.js properties
TPL-014: Use new modal_simple macro API with call block
- Detects {{ modal_simple( instead of {% call modal_simple( %}
- Detects deprecated parameters: icon, confirm_text, confirm_fn, etc.
- New API uses {% call %}...{% endcall %} with content in body
These rules will catch template issues at validation time rather than
at runtime, preventing the TypeError crashes we saw.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -530,6 +530,76 @@ template_rules:
|
|||||||
exceptions:
|
exceptions:
|
||||||
- "base.html"
|
- "base.html"
|
||||||
|
|
||||||
|
- id: "TPL-013"
|
||||||
|
name: "Use new pagination macro API"
|
||||||
|
severity: "error"
|
||||||
|
description: |
|
||||||
|
The pagination macro was simplified to only accept show_condition.
|
||||||
|
It now relies on standardized Alpine.js component properties.
|
||||||
|
|
||||||
|
OLD (deprecated - will break):
|
||||||
|
{{ pagination(
|
||||||
|
current_page='pagination.page',
|
||||||
|
total_pages='totalPages',
|
||||||
|
...
|
||||||
|
) }}
|
||||||
|
|
||||||
|
NEW (correct):
|
||||||
|
{{ pagination(show_condition="!loading && pagination.total > 0") }}
|
||||||
|
|
||||||
|
Required Alpine.js component properties:
|
||||||
|
- pagination.page, pagination.total
|
||||||
|
- totalPages, pageNumbers
|
||||||
|
- startIndex, endIndex
|
||||||
|
- previousPage(), nextPage(), goToPage()
|
||||||
|
pattern:
|
||||||
|
file_pattern: "app/templates/**/*.html"
|
||||||
|
anti_patterns:
|
||||||
|
- "pagination\\s*\\([^)]*current_page\\s*="
|
||||||
|
- "pagination\\s*\\([^)]*total_pages\\s*="
|
||||||
|
- "pagination\\s*\\([^)]*page_numbers\\s*="
|
||||||
|
exceptions:
|
||||||
|
- "shared/macros/pagination.html"
|
||||||
|
|
||||||
|
- id: "TPL-014"
|
||||||
|
name: "Use new modal_simple macro API with call block"
|
||||||
|
severity: "error"
|
||||||
|
description: |
|
||||||
|
The modal_simple macro now uses {% call %}...{% endcall %} syntax.
|
||||||
|
Content (including buttons) goes inside the call block.
|
||||||
|
|
||||||
|
OLD (deprecated - will break):
|
||||||
|
{{ modal_simple(
|
||||||
|
show_var='showModal',
|
||||||
|
title='Title',
|
||||||
|
icon='exclamation',
|
||||||
|
confirm_text='OK',
|
||||||
|
confirm_fn='doSomething()'
|
||||||
|
) }}
|
||||||
|
<template x-if="showModal">...</template>
|
||||||
|
|
||||||
|
NEW (correct):
|
||||||
|
{% call modal_simple('modalId', 'Title', show_var='showModal') %}
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p>Modal content here</p>
|
||||||
|
<div class="flex justify-end gap-3">
|
||||||
|
<button @click="showModal = false">Cancel</button>
|
||||||
|
<button @click="doSomething()">OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
Parameters: modal_simple(id, title, show_var='isModalOpen', size='md')
|
||||||
|
pattern:
|
||||||
|
file_pattern: "app/templates/**/*.html"
|
||||||
|
anti_patterns:
|
||||||
|
- "\\{\\{\\s*modal_simple\\s*\\("
|
||||||
|
- "modal_simple\\s*\\([^)]*icon\\s*="
|
||||||
|
- "modal_simple\\s*\\([^)]*confirm_text\\s*="
|
||||||
|
- "modal_simple\\s*\\([^)]*confirm_fn\\s*="
|
||||||
|
exceptions:
|
||||||
|
- "shared/macros/modals.html"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# FRONTEND COMPONENT RULES
|
# FRONTEND COMPONENT RULES
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -1217,6 +1217,95 @@ class ArchitectureValidator:
|
|||||||
suggestion="Change outer attribute to single quotes: @click='copyCode(`...`)'",
|
suggestion="Change outer attribute to single quotes: @click='copyCode(`...`)'",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _check_pagination_macro_api(
|
||||||
|
self, file_path: Path, content: str, lines: list[str]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
TPL-013: Check for old pagination macro API.
|
||||||
|
|
||||||
|
The pagination macro was simplified to only accept show_condition.
|
||||||
|
It now relies on standardized Alpine.js component properties.
|
||||||
|
|
||||||
|
OLD (deprecated): {{ pagination(current_page=..., total_pages=...) }}
|
||||||
|
NEW (correct): {{ pagination(show_condition="!loading && pagination.total > 0") }}
|
||||||
|
"""
|
||||||
|
# Skip the macro definition file
|
||||||
|
if "shared/macros/pagination.html" in str(file_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
if "noqa: tpl-013" in content.lower():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Old API patterns that indicate deprecated usage
|
||||||
|
old_patterns = [
|
||||||
|
(r"pagination\s*\([^)]*current_page\s*=", "current_page"),
|
||||||
|
(r"pagination\s*\([^)]*total_pages\s*=", "total_pages"),
|
||||||
|
(r"pagination\s*\([^)]*page_numbers\s*=", "page_numbers"),
|
||||||
|
(r"pagination\s*\([^)]*start_index\s*=", "start_index"),
|
||||||
|
(r"pagination\s*\([^)]*end_index\s*=", "end_index"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
for pattern, param_name in old_patterns:
|
||||||
|
if re.search(pattern, line):
|
||||||
|
self._add_violation(
|
||||||
|
rule_id="TPL-013",
|
||||||
|
rule_name="Use new pagination macro API",
|
||||||
|
severity=Severity.ERROR,
|
||||||
|
file_path=file_path,
|
||||||
|
line_number=i,
|
||||||
|
message=f"Old pagination API with '{param_name}' parameter",
|
||||||
|
context=line.strip()[:80],
|
||||||
|
suggestion="Use: {{ pagination(show_condition=\"!loading && pagination.total > 0\") }}",
|
||||||
|
)
|
||||||
|
break # Only report once per line
|
||||||
|
|
||||||
|
def _check_modal_simple_macro_api(
|
||||||
|
self, file_path: Path, content: str, lines: list[str]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
TPL-014: Check for old modal_simple macro API.
|
||||||
|
|
||||||
|
The modal_simple macro now uses {% call %}...{% endcall %} syntax.
|
||||||
|
Content (including buttons) goes inside the call block.
|
||||||
|
|
||||||
|
OLD (deprecated): {{ modal_simple(show_var=..., icon=..., confirm_fn=...) }}
|
||||||
|
NEW (correct): {% call modal_simple('id', 'Title', show_var='...') %}...{% endcall %}
|
||||||
|
"""
|
||||||
|
# Skip the macro definition file
|
||||||
|
if "shared/macros/modals.html" in str(file_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
if "noqa: tpl-014" in content.lower():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Old API patterns - using {{ }} instead of {% call %}
|
||||||
|
# Also checking for old parameters that don't exist anymore
|
||||||
|
old_patterns = [
|
||||||
|
(r"\{\{\s*modal_simple\s*\(", "{{ modal_simple( instead of {% call modal_simple("),
|
||||||
|
(r"modal_simple\s*\([^)]*icon\s*=", "icon parameter"),
|
||||||
|
(r"modal_simple\s*\([^)]*icon_color\s*=", "icon_color parameter"),
|
||||||
|
(r"modal_simple\s*\([^)]*confirm_text\s*=", "confirm_text parameter"),
|
||||||
|
(r"modal_simple\s*\([^)]*confirm_fn\s*=", "confirm_fn parameter"),
|
||||||
|
(r"modal_simple\s*\([^)]*confirm_class\s*=", "confirm_class parameter"),
|
||||||
|
(r"modal_simple\s*\([^)]*loading_var\s*=", "loading_var parameter"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
for pattern, issue in old_patterns:
|
||||||
|
if re.search(pattern, line):
|
||||||
|
self._add_violation(
|
||||||
|
rule_id="TPL-014",
|
||||||
|
rule_name="Use new modal_simple macro API with call block",
|
||||||
|
severity=Severity.ERROR,
|
||||||
|
file_path=file_path,
|
||||||
|
line_number=i,
|
||||||
|
message=f"Old modal_simple API: {issue}",
|
||||||
|
context=line.strip()[:80],
|
||||||
|
suggestion="Use: {{% call modal_simple('id', 'Title', show_var='...') %}}...{{% endcall %}}",
|
||||||
|
)
|
||||||
|
break # Only report once per line
|
||||||
|
|
||||||
def _check_alpine_template_vars(
|
def _check_alpine_template_vars(
|
||||||
self, file_path: Path, content: str, lines: list[str], js_content: str
|
self, file_path: Path, content: str, lines: list[str], js_content: str
|
||||||
):
|
):
|
||||||
@@ -3191,6 +3280,12 @@ class ArchitectureValidator:
|
|||||||
# TPL-012: Check for escaped quotes in Alpine template literals
|
# TPL-012: Check for escaped quotes in Alpine template literals
|
||||||
self._check_escaped_quotes_in_alpine(file_path, content, lines)
|
self._check_escaped_quotes_in_alpine(file_path, content, lines)
|
||||||
|
|
||||||
|
# TPL-013: Check for old pagination macro API
|
||||||
|
self._check_pagination_macro_api(file_path, content, lines)
|
||||||
|
|
||||||
|
# TPL-014: Check for old modal_simple macro API
|
||||||
|
self._check_modal_simple_macro_api(file_path, content, lines)
|
||||||
|
|
||||||
# Skip base/partials for TPL-001 check
|
# Skip base/partials for TPL-001 check
|
||||||
if is_base_or_partial:
|
if is_base_or_partial:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user