7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
517 lines
20 KiB
Python
Executable File
517 lines
20 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Pre-commit hook to ensure all LDR-specific CSS class names are prefixed with 'ldr-'.
|
|
This prevents CSS class name conflicts when Vite bundles CSS from dependencies.
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List, Tuple
|
|
|
|
# Allowed patterns - ONLY third-party framework classes
|
|
# Everything else MUST have ldr- prefix
|
|
ALLOWED_PATTERNS = {
|
|
# Already prefixed with ldr-
|
|
r"^ldr-",
|
|
# Bootstrap 5 specific classes (no wildcards unless necessary)
|
|
# Layout
|
|
r"^(container|container-fluid|container-sm|container-md|container-lg|container-xl|container-xxl)$",
|
|
r"^row$",
|
|
r"^col$",
|
|
r"^col-(1|2|3|4|5|6|7|8|9|10|11|12)$",
|
|
r"^col-(sm|md|lg|xl|xxl)-(1|2|3|4|5|6|7|8|9|10|11|12)$",
|
|
# Bootstrap components (with specific prefixes)
|
|
r"^btn(-primary|-secondary|-success|-danger|-warning|-info|-light|-dark|-link|-outline-primary|-outline-secondary|-outline-success|-outline-danger|-outline-warning|-outline-info|-outline-light|-outline-dark|-sm|-lg|-block|-close|-close-white)?$",
|
|
r"^alert(-primary|-secondary|-success|-danger|-warning|-info|-light|-dark|-dismissible)?$",
|
|
r"^badge(-primary|-secondary|-success|-danger|-warning|-info|-light|-dark|-pill)?$",
|
|
r"^card(-body|-header|-footer|-title|-subtitle|-text|-link|-img-top|-img-bottom)?$",
|
|
r"^navbar(-brand|-nav|-toggler|-collapse|-expand|-expand-sm|-expand-md|-expand-lg|-expand-xl|-dark|-light)?$",
|
|
r"^nav(-link|-item|-pills|-tabs|-fill|-justified)?$",
|
|
r"^dropdown(-toggle|-menu|-menu-end|-menu-start|-item|-divider|-header)?$",
|
|
r"^modal(-dialog|-dialog-centered|-dialog-scrollable|-content|-header|-body|-footer|-title|-backdrop|-static|-sm|-lg|-xl|-fullscreen)?$",
|
|
r"^form(-control|-label|-text|-check|-check-input|-check-label|-check-inline|-switch|-select|-range|-floating|-group|-row)?$",
|
|
r"^input-group(-text|-prepend|-append)?$",
|
|
r"^list-group(-item|-item-action|-flush)?$",
|
|
r"^table(-dark|-striped|-bordered|-borderless|-hover|-sm|-responsive)?$",
|
|
# Bootstrap utilities with exact values
|
|
r"^text-(start|end|center|primary|secondary|success|danger|warning|info|light|dark|body|muted|white|black-50|white-50)$",
|
|
r"^text-(lowercase|uppercase|capitalize|nowrap|truncate|break|monospace)$",
|
|
r"^bg-(primary|secondary|success|danger|warning|info|light|dark|body|white|transparent)$",
|
|
r"^border(-top|-end|-bottom|-start|-primary|-secondary|-success|-danger|-warning|-info|-light|-dark|-white)?$",
|
|
r"^border-(0|1|2|3|4|5)$",
|
|
r"^rounded(-top|-end|-bottom|-start|-circle|-pill|-0|1|2|3)?$",
|
|
r"^shadow(-sm|-lg|-none)?$",
|
|
# Display utilities
|
|
r"^d-(none|inline|inline-block|block|table|table-row|table-cell|flex|inline-flex|grid)$",
|
|
r"^d-(sm|md|lg|xl|xxl)-(none|inline|inline-block|block|table|table-row|table-cell|flex|inline-flex|grid)$",
|
|
# Flexbox utilities
|
|
r"^flex-(row|row-reverse|column|column-reverse|wrap|nowrap|wrap-reverse)$",
|
|
r"^justify-content-(start|end|center|between|around|evenly)$",
|
|
r"^align-items-(start|end|center|baseline|stretch)$",
|
|
r"^align-self-(start|end|center|baseline|stretch)$",
|
|
r"^flex-(fill|grow-0|grow-1|shrink-0|shrink-1)$",
|
|
# Spacing utilities (margins and padding)
|
|
r"^m-(0|1|2|3|4|5|auto)$",
|
|
r"^mt-(0|1|2|3|4|5|auto)$",
|
|
r"^mb-(0|1|2|3|4|5|auto)$",
|
|
r"^ms-(0|1|2|3|4|5|auto)$",
|
|
r"^me-(0|1|2|3|4|5|auto)$",
|
|
r"^mx-(0|1|2|3|4|5|auto)$",
|
|
r"^my-(0|1|2|3|4|5|auto)$",
|
|
r"^p-(0|1|2|3|4|5)$",
|
|
r"^pt-(0|1|2|3|4|5)$",
|
|
r"^pb-(0|1|2|3|4|5)$",
|
|
r"^ps-(0|1|2|3|4|5)$",
|
|
r"^pe-(0|1|2|3|4|5)$",
|
|
r"^px-(0|1|2|3|4|5)$",
|
|
r"^py-(0|1|2|3|4|5)$",
|
|
# Sizing utilities
|
|
r"^w-(25|50|75|100|auto)$",
|
|
r"^h-(25|50|75|100|auto)$",
|
|
r"^mw-100$",
|
|
r"^mh-100$",
|
|
# Position utilities
|
|
r"^position-(static|relative|absolute|fixed|sticky)$",
|
|
r"^top-(0|50|100)$",
|
|
r"^bottom-(0|50|100)$",
|
|
r"^start-(0|50|100)$",
|
|
r"^end-(0|50|100)$",
|
|
# Typography utilities
|
|
r"^fs-(1|2|3|4|5|6)$",
|
|
r"^fw-(light|lighter|normal|bold|bolder)$",
|
|
r"^fst-(normal|italic)$",
|
|
r"^lh-(1|sm|base|lg)$",
|
|
r"^font-monospace$",
|
|
r"^text-decoration-(none|underline|line-through)$",
|
|
# Visibility utilities
|
|
r"^(visible|invisible)$",
|
|
r"^visually-hidden$",
|
|
r"^visually-hidden-focusable$",
|
|
r"^overflow-(auto|hidden|visible|scroll)$",
|
|
r"^user-select-(all|auto|none)$",
|
|
# Interactive utilities
|
|
r"^pe-(none|auto)$",
|
|
r"^(active|disabled|show|hide|collapse|collapsed|fade|collapsing|close)$",
|
|
# Form validation states (Bootstrap 5)
|
|
r"^(is-valid|is-invalid|valid-feedback|invalid-feedback|valid-tooltip|invalid-tooltip)$",
|
|
# Toast component (Bootstrap 5)
|
|
r"^toast(-header|-body)?$",
|
|
# Progress bars (Bootstrap 5)
|
|
r"^progress(-bar|-bar-striped|-bar-animated|-stacked)?$",
|
|
r"^btn-group(-vertical|-sm|-lg)?$",
|
|
# Image utilities (Bootstrap 5)
|
|
r"^img-(fluid|thumbnail)$",
|
|
# Other Bootstrap utilities
|
|
r"^(clearfix|sr-only|sr-only-focusable|small)$",
|
|
r"^spinner-(border|border-sm|grow|grow-sm)$",
|
|
r"^placeholder(-glow|-wave)?$",
|
|
# Bootstrap Icons (bi-*)
|
|
r"^bi(-[a-z0-9-]+)?$",
|
|
# Font Awesome icons
|
|
r"^(fa|fas|far|fab|fal|fad)$",
|
|
r"^fa-[a-z0-9-]+$",
|
|
# KaTeX math rendering
|
|
r"^katex(-[a-z0-9-]+)?$",
|
|
}
|
|
|
|
|
|
def is_allowed_class(class_name: str) -> bool:
|
|
"""Check if a class name is allowed without ldr- prefix."""
|
|
# Remove any leading/trailing whitespace
|
|
class_name = class_name.strip()
|
|
|
|
# Empty class names are invalid
|
|
if not class_name:
|
|
return False
|
|
|
|
# Check against allowed patterns
|
|
for pattern in ALLOWED_PATTERNS:
|
|
if re.match(pattern, class_name, re.IGNORECASE):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def check_css_file(file_path: Path) -> List[Tuple[int, str, str]]:
|
|
"""Check CSS file for non-prefixed class definitions."""
|
|
errors = []
|
|
|
|
try:
|
|
content = file_path.read_text(encoding="utf-8")
|
|
lines = content.split("\n")
|
|
|
|
# Pattern to match CSS class selectors, including compound
|
|
# selectors like .class1.class2
|
|
# First match the leading dot (not preceded by a word char or /)
|
|
# then capture everything including subsequent .class segments
|
|
class_pattern = re.compile(
|
|
r"(?<![\w/])\.([a-zA-Z][a-zA-Z0-9\-_]*(?:\.[a-zA-Z][a-zA-Z0-9\-_]*)*)"
|
|
)
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
# Skip comments
|
|
if "/*" in line or "*/" in line or line.strip().startswith("//"):
|
|
continue
|
|
|
|
# Skip @import statements
|
|
if "@import" in line:
|
|
continue
|
|
|
|
matches = class_pattern.findall(line)
|
|
for match in matches:
|
|
# Split compound selectors like "class1.class2" into
|
|
# individual class names for separate validation
|
|
for class_name in match.split("."):
|
|
if not class_name:
|
|
continue
|
|
if not is_allowed_class(class_name):
|
|
errors.append(
|
|
(
|
|
line_num,
|
|
class_name,
|
|
f"CSS class '.{class_name}' should be prefixed with 'ldr-'",
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"Error reading {file_path}: {e}", file=sys.stderr)
|
|
|
|
return errors
|
|
|
|
|
|
def check_html_file(file_path: Path) -> List[Tuple[int, str, str]]:
|
|
"""Check HTML file for non-prefixed class usage and CSS definitions in style tags."""
|
|
errors = []
|
|
|
|
try:
|
|
content = file_path.read_text(encoding="utf-8")
|
|
lines = content.split("\n")
|
|
|
|
# Pattern to match class attributes in HTML
|
|
# Simplified pattern to avoid exponential backtracking
|
|
# Matches: class="..." or class='...'
|
|
# Uses two separate patterns to handle each quote type correctly
|
|
class_attr_patterns = [
|
|
re.compile(r'class\s*=\s*"([^"]*)"', re.IGNORECASE),
|
|
re.compile(r"class\s*=\s*'([^']*)'", re.IGNORECASE),
|
|
]
|
|
|
|
# Pattern to match CSS class definitions (same as in check_css_file)
|
|
css_class_pattern = re.compile(
|
|
r"(?<![\w/])\.([a-zA-Z][a-zA-Z0-9\-_]*(?:\.[a-zA-Z][a-zA-Z0-9\-_]*)*)"
|
|
)
|
|
|
|
in_style_tag = False
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
# Skip HTML comments
|
|
if "<!--" in line or "-->" in line:
|
|
continue
|
|
|
|
# Skip Jinja2 template comments
|
|
if "{#" in line or "#}" in line:
|
|
continue
|
|
|
|
# Check for style tag boundaries
|
|
if "<style" in line.lower():
|
|
in_style_tag = True
|
|
if "</style>" in line.lower():
|
|
in_style_tag = False
|
|
|
|
# If we're inside a style tag, check for CSS class definitions
|
|
if in_style_tag:
|
|
# Skip CSS comments
|
|
if (
|
|
"/*" in line
|
|
or "*/" in line
|
|
or line.strip().startswith("//")
|
|
):
|
|
continue
|
|
|
|
# Check for CSS class definitions
|
|
css_matches = css_class_pattern.findall(line)
|
|
for match in css_matches:
|
|
# Split compound selectors like "class1.class2"
|
|
for class_name in match.split("."):
|
|
if not class_name:
|
|
continue
|
|
if not is_allowed_class(class_name):
|
|
errors.append(
|
|
(
|
|
line_num,
|
|
class_name,
|
|
f"CSS class '.{class_name}' in style tag should be prefixed with 'ldr-'",
|
|
)
|
|
)
|
|
continue # Skip HTML class checking when in style tag
|
|
|
|
# Try both patterns to handle different quote types
|
|
matches = []
|
|
for pattern in class_attr_patterns:
|
|
matches.extend(pattern.findall(line))
|
|
|
|
for class_attr in matches:
|
|
# Skip if this is a JavaScript template literal (contains ${...})
|
|
if "${" in class_attr:
|
|
continue
|
|
|
|
# Skip if the entire class attribute contains Jinja2 template variables
|
|
# This handles cases like class="alert alert-{{ category }}"
|
|
if "{{" in class_attr or "{%" in class_attr:
|
|
# Extract only the static class names (those not part of Jinja2 expressions)
|
|
# Remove the Jinja2 parts and check remaining static classes
|
|
# Remove Jinja2 expressions but keep the rest
|
|
cleaned_attr = re.sub(
|
|
r"\{\{[^}]*\}\}|\{%[^%]*%\}", "", class_attr
|
|
)
|
|
classes = cleaned_attr.split()
|
|
else:
|
|
# No Jinja2 in this attribute, check all classes
|
|
classes = class_attr.split()
|
|
|
|
for class_name in classes:
|
|
# Additional safety check for individual class names
|
|
if (
|
|
"{{" in class_name
|
|
or "{%" in class_name
|
|
or "{" in class_name
|
|
or "}" in class_name
|
|
):
|
|
continue
|
|
|
|
# Skip JavaScript template literals and variables
|
|
if any(
|
|
char in class_name
|
|
for char in [
|
|
"$",
|
|
"=",
|
|
"!",
|
|
"<",
|
|
">",
|
|
"(",
|
|
")",
|
|
"[",
|
|
"]",
|
|
"||",
|
|
"&&",
|
|
"?",
|
|
":",
|
|
]
|
|
):
|
|
continue
|
|
|
|
# Skip common Jinja2 patterns
|
|
if class_name in [
|
|
"if",
|
|
"else",
|
|
"endif",
|
|
"for",
|
|
"endfor",
|
|
"block",
|
|
"endblock",
|
|
"include",
|
|
"extends",
|
|
]:
|
|
continue
|
|
|
|
# Skip Bootstrap icon classes
|
|
if class_name.startswith("bi-") or class_name == "bi":
|
|
continue
|
|
|
|
if not is_allowed_class(class_name):
|
|
errors.append(
|
|
(
|
|
line_num,
|
|
class_name,
|
|
f"HTML class '{class_name}' should be prefixed with 'ldr-'",
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"Error reading {file_path}: {e}", file=sys.stderr)
|
|
|
|
return errors
|
|
|
|
|
|
def check_js_file(file_path: Path) -> List[Tuple[int, str, str]]:
|
|
"""Check JavaScript file for non-prefixed class usage."""
|
|
errors = []
|
|
|
|
try:
|
|
content = file_path.read_text(encoding="utf-8")
|
|
lines = content.split("\n")
|
|
|
|
# Patterns to match class usage in JavaScript (excluding querySelector/jQuery)
|
|
patterns = [
|
|
# classList.add('classname')
|
|
re.compile(
|
|
r'classList\.(add|remove|toggle|contains)\s*\(\s*["\']([^"\']+)["\']'
|
|
),
|
|
# className = 'classname' or className: 'classname'
|
|
re.compile(r'className\s*[:=]\s*["\']([^"\']+)["\']'),
|
|
# getElementsByClassName('classname')
|
|
re.compile(r'getElementsByClassName\s*\(\s*["\']([^"\']+)["\']'),
|
|
# hasClass('classname'), addClass('classname'), removeClass('classname')
|
|
re.compile(
|
|
r'\.(hasClass|addClass|removeClass|toggleClass)\s*\(\s*["\']([^"\']+)["\']'
|
|
),
|
|
]
|
|
|
|
# Pattern for template literal className assignments:
|
|
# className = `some-class ${dynamic}` or className: `some-class`
|
|
template_literal_pattern = re.compile(r"className\s*[:=]\s*`([^`]+)`")
|
|
|
|
# Special patterns for querySelector and jQuery that need different handling
|
|
querySelector_pattern = re.compile(
|
|
r'querySelector(?:All)?\s*\(\s*["\']([^"\']+)["\']'
|
|
)
|
|
jquery_pattern = re.compile(r'\$\s*\(\s*["\']([^"\']+)["\']')
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
# Skip comments
|
|
if "//" in line or "/*" in line or "*/" in line:
|
|
# Simple comment detection (not perfect but good enough)
|
|
comment_start = line.find("//")
|
|
if comment_start >= 0:
|
|
line = line[:comment_start]
|
|
|
|
# Handle querySelector and jQuery selectors specially
|
|
for selector_match in querySelector_pattern.findall(line):
|
|
# Extract class names from CSS selectors (e.g., '.class1 .class2', '.class1.class2')
|
|
# Only match class selectors (starting with .)
|
|
class_matches = re.findall(
|
|
r"\.([a-zA-Z0-9_-]+)", selector_match
|
|
)
|
|
for cls in class_matches:
|
|
if not is_allowed_class(cls):
|
|
errors.append(
|
|
(
|
|
line_num,
|
|
cls,
|
|
f"JavaScript class '.{cls}' should be prefixed with 'ldr-'",
|
|
)
|
|
)
|
|
|
|
for jquery_match in jquery_pattern.findall(line):
|
|
# Extract class names from jQuery selectors
|
|
class_matches = re.findall(r"\.([a-zA-Z0-9_-]+)", jquery_match)
|
|
for cls in class_matches:
|
|
if not is_allowed_class(cls):
|
|
errors.append(
|
|
(
|
|
line_num,
|
|
cls,
|
|
f"JavaScript class '.{cls}' should be prefixed with 'ldr-'",
|
|
)
|
|
)
|
|
|
|
# Handle template literal className assignments
|
|
for tl_match in template_literal_pattern.findall(line):
|
|
# Extract static class tokens by removing ${...} expressions
|
|
static_part = re.sub(r"\$\{[^}]*\}", " ", tl_match)
|
|
for cls in static_part.split():
|
|
# Skip partial tokens that are fragments of dynamic
|
|
# class names (e.g. "alert-" from "alert-${type}")
|
|
if cls.endswith("-") or cls.startswith("-"):
|
|
continue
|
|
if not is_allowed_class(cls):
|
|
errors.append(
|
|
(
|
|
line_num,
|
|
cls,
|
|
f"JavaScript class '{cls}' should be prefixed with 'ldr-'",
|
|
)
|
|
)
|
|
|
|
# Handle other patterns
|
|
for pattern in patterns:
|
|
matches = pattern.findall(line)
|
|
for match in matches:
|
|
# Handle different capture groups
|
|
if isinstance(match, tuple):
|
|
# For patterns with multiple groups, get the class name
|
|
class_name = match[-1] if len(match) > 1 else match[0]
|
|
else:
|
|
class_name = match
|
|
|
|
# Split multiple classes if present
|
|
classes = class_name.split()
|
|
for cls in classes:
|
|
if not is_allowed_class(cls):
|
|
errors.append(
|
|
(
|
|
line_num,
|
|
cls,
|
|
f"JavaScript class '{cls}' should be prefixed with 'ldr-'",
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"Error reading {file_path}: {e}", file=sys.stderr)
|
|
|
|
return errors
|
|
|
|
|
|
def main():
|
|
"""Main function to check files passed as arguments."""
|
|
if len(sys.argv) < 2:
|
|
print("No files to check")
|
|
return 0
|
|
|
|
has_errors = False
|
|
|
|
for file_arg in sys.argv[1:]:
|
|
file_path = Path(file_arg)
|
|
|
|
if not file_path.exists():
|
|
continue
|
|
|
|
# Match path components, not substrings: "lib" must be an actual
|
|
# directory segment, not a fragment of "library.html" etc.
|
|
if any(
|
|
vendor in file_path.parts
|
|
for vendor in [
|
|
"vendor",
|
|
"dist",
|
|
"build",
|
|
"lib",
|
|
"libs",
|
|
"node_modules",
|
|
]
|
|
):
|
|
continue
|
|
|
|
errors = []
|
|
|
|
# Check based on file extension
|
|
if file_path.suffix == ".css":
|
|
errors = check_css_file(file_path)
|
|
elif file_path.suffix in [".html", ".htm"]:
|
|
errors = check_html_file(file_path)
|
|
elif file_path.suffix in [".js", ".jsx", ".ts", ".tsx", ".mjs"]:
|
|
errors = check_js_file(file_path)
|
|
|
|
if errors:
|
|
has_errors = True
|
|
print(f"\n❌ CSS class prefix errors in {file_path}:")
|
|
for line_num, class_name, message in errors:
|
|
print(f" Line {line_num}: {message}")
|
|
|
|
if has_errors:
|
|
print("\n" + "=" * 60)
|
|
print("CSS Class Naming Convention:")
|
|
print(" All LDR-specific CSS classes must be prefixed with 'ldr-'")
|
|
print(
|
|
" This prevents conflicts when Vite bundles CSS from dependencies"
|
|
)
|
|
print(" Example: .custom-button → .ldr-custom-button")
|
|
print("=" * 60)
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|