feat(arch-rules): JS-016 blocks hardcoded 'en-US' in JS at error severity
Some checks failed
Some checks failed
Architecture rule that fails CI on any new toLocaleDateString / toLocaleString / toLocaleTimeString / new Intl.* call that hardcodes 'en-US' instead of using I18n.locale. The whole codebase was cleaned in the preceding commits (06e59f73,bb4c4004,dd1f9af8) so the rule ships at error severity from day one. - Rule definition in .architecture-rules/frontend.yaml under javascript_rules; exceptions: i18n.js (defines the helper), vendor/. - _check_hardcoded_locale in scripts/validate/validate_architecture.py wired into both JS validation sites (full scan + per-file -f mode). - Suppressible per-line with `// noqa: JS-016` for the rare case where a specific locale is genuinely required (e.g., a US-only invoice formatter that must use en-US regardless of UI language). Validator output: 0 JS-016 hits across the codebase. Negative-tested with a planted violation — rule fires correctly and clears on removal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -391,6 +391,41 @@ javascript_rules:
|
|||||||
exceptions:
|
exceptions:
|
||||||
- "init-alpine.js"
|
- "init-alpine.js"
|
||||||
|
|
||||||
|
- id: "JS-016"
|
||||||
|
name: "Do not hardcode 'en-US' (or any locale) in Intl/toLocale calls"
|
||||||
|
severity: "error"
|
||||||
|
description: |
|
||||||
|
Locale-aware APIs (toLocaleDateString, toLocaleString, toLocaleTimeString,
|
||||||
|
new Intl.NumberFormat, new Intl.DateTimeFormat, etc.) must NOT receive a
|
||||||
|
hardcoded locale tag like 'en-US'. The user's dashboard language won't be
|
||||||
|
respected and dates/numbers will render in English even when FR/DE/LB is
|
||||||
|
selected.
|
||||||
|
|
||||||
|
Use the `I18n.locale` getter from static/shared/js/i18n.js, which returns
|
||||||
|
the current dashboard language (falls back to 'en' if I18n hasn't loaded).
|
||||||
|
|
||||||
|
WRONG (always renders dates in English):
|
||||||
|
date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||||
|
new Intl.NumberFormat('en-US').format(num);
|
||||||
|
|
||||||
|
RIGHT:
|
||||||
|
date.toLocaleDateString(I18n.locale, { year: 'numeric', month: 'short', day: 'numeric' });
|
||||||
|
new Intl.NumberFormat(I18n.locale).format(num);
|
||||||
|
|
||||||
|
Suppress with `// noqa: JS-016` on the line for the rare case where a
|
||||||
|
specific locale is genuinely required (e.g., a US-only invoice number
|
||||||
|
formatter that must use en-US regardless of UI language).
|
||||||
|
pattern:
|
||||||
|
file_pattern: "static/**/js/**/*.js"
|
||||||
|
anti_patterns:
|
||||||
|
- "toLocaleDateString\\(\\s*['\"]en-US['\"]"
|
||||||
|
- "toLocaleString\\(\\s*['\"]en-US['\"]"
|
||||||
|
- "toLocaleTimeString\\(\\s*['\"]en-US['\"]"
|
||||||
|
- "new\\s+Intl\\.\\w+\\(\\s*['\"]en-US['\"]"
|
||||||
|
exceptions:
|
||||||
|
- "i18n.js"
|
||||||
|
- "vendor/"
|
||||||
|
|
||||||
- id: "JS-012"
|
- id: "JS-012"
|
||||||
name: "Do not include /api/v1 prefix in API endpoints"
|
name: "Do not include /api/v1 prefix in API endpoints"
|
||||||
severity: "error"
|
severity: "error"
|
||||||
|
|||||||
@@ -485,6 +485,45 @@ class ArchitectureValidator:
|
|||||||
# JS-015: Check for native confirm() instead of confirm_modal macros
|
# JS-015: Check for native confirm() instead of confirm_modal macros
|
||||||
self._check_confirm_usage(file_path, content, lines)
|
self._check_confirm_usage(file_path, content, lines)
|
||||||
|
|
||||||
|
# JS-016: Check for hardcoded 'en-US' in locale-aware APIs
|
||||||
|
self._check_hardcoded_locale(file_path, content, lines)
|
||||||
|
|
||||||
|
_HARDCODED_LOCALE_RE = re.compile(
|
||||||
|
r"(toLocaleDateString|toLocaleString|toLocaleTimeString|new\s+Intl\.\w+)\(\s*['\"]en-US['\"]"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _check_hardcoded_locale(
|
||||||
|
self, file_path: Path, content: str, lines: list[str]
|
||||||
|
):
|
||||||
|
"""JS-016: Reject hardcoded 'en-US' in toLocale* / Intl.* calls.
|
||||||
|
|
||||||
|
Dates and numbers must respect the user's dashboard language. Use
|
||||||
|
`I18n.locale` from static/shared/js/i18n.js instead of a hardcoded tag.
|
||||||
|
|
||||||
|
Exceptions: i18n.js itself (defines the helper) and vendor/.
|
||||||
|
Suppressible per-line with `// noqa: JS-016`.
|
||||||
|
"""
|
||||||
|
name = file_path.name
|
||||||
|
path_str = str(file_path).replace("\\", "/")
|
||||||
|
if name == "i18n.js" or "/vendor/" in path_str:
|
||||||
|
return
|
||||||
|
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
if not self._HARDCODED_LOCALE_RE.search(line):
|
||||||
|
continue
|
||||||
|
if "noqa: js-016" in line.lower() or "noqa: js016" in line.lower():
|
||||||
|
continue
|
||||||
|
self._add_violation(
|
||||||
|
rule_id="JS-016",
|
||||||
|
rule_name="Do not hardcode 'en-US' (or any locale) in Intl/toLocale calls",
|
||||||
|
severity=Severity.ERROR,
|
||||||
|
file_path=file_path,
|
||||||
|
line_number=i,
|
||||||
|
message="Hardcoded 'en-US' ignores the user's dashboard language",
|
||||||
|
context=line.strip()[:80],
|
||||||
|
suggestion="Replace 'en-US' with I18n.locale (from static/shared/js/i18n.js)",
|
||||||
|
)
|
||||||
|
|
||||||
def _check_toast_usage(self, file_path: Path, content: str, lines: list[str]):
|
def _check_toast_usage(self, file_path: Path, content: str, lines: list[str]):
|
||||||
"""JS-009: Check for alert() or window.showToast instead of Utils.showToast()"""
|
"""JS-009: Check for alert() or window.showToast instead of Utils.showToast()"""
|
||||||
# Skip utils.js (where showToast is defined)
|
# Skip utils.js (where showToast is defined)
|
||||||
@@ -3287,6 +3326,9 @@ class ArchitectureValidator:
|
|||||||
# JS-014: Check that store API calls don't include storeCode in path
|
# JS-014: Check that store API calls don't include storeCode in path
|
||||||
self._check_store_api_paths(file_path, content, lines)
|
self._check_store_api_paths(file_path, content, lines)
|
||||||
|
|
||||||
|
# JS-016: Check for hardcoded 'en-US' in locale-aware APIs
|
||||||
|
self._check_hardcoded_locale(file_path, content, lines)
|
||||||
|
|
||||||
def _check_platform_settings_usage(
|
def _check_platform_settings_usage(
|
||||||
self, file_path: Path, content: str, lines: list[str]
|
self, file_path: Path, content: str, lines: list[str]
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user