Css - biały

This commit is contained in:
dm
2025-12-19 14:56:02 +01:00
parent 8d984c7a9c
commit 1ec16fb089
7 changed files with 171 additions and 42 deletions

120
find_unused_css_classes.py Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python3
import argparse
import os
import re
from pathlib import Path
from typing import Dict, List, Set, Tuple
# --- Regexy do zbierania klas z CSS ---
# Łapie ".class-name" w selektorach (pomija np. ".5" i rzeczy z escapami prosto, ale działa w praktyce)
CSS_CLASS_RE = re.compile(r'(?<![\\\w-])\.([a-zA-Z_-][\w-]*)')
# --- Regexy do zbierania stringów klas z Astro/JSX/TSX ---
# 1) class="..."
CLASS_ATTR_RE = re.compile(r'\bclass\s*=\s*("([^"]*)"|\'([^\']*)\')', re.IGNORECASE)
# 2) className="..."
CLASSNAME_ATTR_RE = re.compile(r'\bclassName\s*=\s*("([^"]*)"|\'([^\']*)\')', re.IGNORECASE)
# Astro: class:list={{ a: cond, "b c": cond2 }} / class:list={[...]} tu łapiemy stringi w środku
STRING_LIT_RE = re.compile(r'("([^"]+)"|\'([^\']+)\')')
# Dla wyszukiwania tokenów klas (żeby "f-card" nie matchowało jako fragment "f-card-x")
def token_pattern(cls: str) -> re.Pattern:
return re.compile(r'(?<![\w-])' + re.escape(cls) + r'(?![\w-])')
def read_text(path: Path) -> str:
try:
return path.read_text(encoding="utf-8", errors="ignore")
except Exception:
return ""
def collect_css_classes(css_dir: Path) -> Tuple[Set[str], Dict[str, Set[Path]]]:
classes: Set[str] = set()
where: Dict[str, Set[Path]] = {}
for p in css_dir.rglob("*.css"):
txt = read_text(p)
for m in CSS_CLASS_RE.finditer(txt):
c = m.group(1)
classes.add(c)
where.setdefault(c, set()).add(p)
return classes, where
def collect_used_classes(code_dir: Path, candidates: Set[str]) -> Tuple[Set[str], Dict[str, Set[Path]]]:
used: Set[str] = set()
used_where: Dict[str, Set[Path]] = {c: set() for c in candidates}
exts = {".astro", ".jsx", ".tsx", ".js", ".ts"}
# prekompilacja patternów dla szybkości
patterns = {c: token_pattern(c) for c in candidates}
for p in code_dir.rglob("*"):
if not p.is_file():
continue
if p.suffix.lower() not in exts:
continue
txt = read_text(p)
if not txt:
continue
# szybki filtr: jeśli żaden kandydat nie ma nawet prefiksu "f-" / "jmb-" itd,
# to i tak skan tokenowy jest ok, ale tu robimy prosty scan wszystkich.
for c, pat in patterns.items():
if pat.search(txt):
used.add(c)
used_where[c].add(p)
# usuń puste wpisy
used_where = {k: v for k, v in used_where.items() if v}
return used, used_where
def main():
ap = argparse.ArgumentParser(description="Znajdź potencjalnie nieużywane klasy CSS w projekcie Astro/JSX.")
ap.add_argument("--css", required=True, help="Katalog z plikami CSS (np. src/styles)")
ap.add_argument("--code", required=True, help="Katalog z kodem (np. src)")
ap.add_argument("--min-len", type=int, default=3, help="Minimalna długość nazwy klasy (domyślnie 3)")
ap.add_argument("--prefix", action="append", default=[], help="Filtruj klasy po prefiksie (np. --prefix f- --prefix jmb-)")
ap.add_argument("--show-where", action="store_true", help="Pokaż gdzie zdefiniowano klasę w CSS")
args = ap.parse_args()
css_dir = Path(args.css).resolve()
code_dir = Path(args.code).resolve()
if not css_dir.exists():
raise SystemExit(f"Brak katalogu CSS: {css_dir}")
if not code_dir.exists():
raise SystemExit(f"Brak katalogu code: {code_dir}")
all_classes, defined_where = collect_css_classes(css_dir)
# filtr długości + prefiksów
classes = {c for c in all_classes if len(c) >= args.min_len}
if args.prefix:
classes = {c for c in classes if any(c.startswith(px) for px in args.prefix)}
used, used_where = collect_used_classes(code_dir, classes)
unused = sorted(classes - used)
print(f"CSS katalog: {css_dir}")
print(f"CODE katalog: {code_dir}")
print(f"Klasy w CSS: {len(classes)} (po filtrach)")
print(f"Użyte w kodzie:{len(used)}")
print(f"NIEUŻYTE: {len(unused)}")
print("-" * 60)
for c in unused:
print(c)
if args.show_where:
files = sorted(defined_where.get(c, []))
for f in files:
rel = f.relative_to(css_dir.parent) if css_dir.parent in f.parents else f
print(f" defined in: {rel}")
print("-" * 60)
# opcjonalnie: pokaż top kilka użyć
# (jak chcesz, dopiszę flagę na raport "gdzie użyte")
if __name__ == "__main__":
main()