"""Render a design system's resolved tokens as its master CSS sheet. Pure, like `design_cascade` and for the same reasons: no database import, so the rendering rule is testable without one and the preview surface can reuse it. WHAT THIS SHEET IS, AND DELIBERATELY IS NOT ------------------------------------------- It declares **purpose tokens only** — custom properties, grouped by what they mean. It contains no rules for elements or classes: no `.btn-primary`, no `table`, no `input`. That is the point rather than a limitation. A sheet that styled every element would restate the same handful of values once per element and grow with the UI; a sheet of purpose-named values states each once and is reused. Components — buttons, tables, input schemes — live as SNIPPETS that reference these names and carry prose about the idea, which is a surface that already exists and already has recall, locations, drift checks and merge. So the division is: this sheet says what the values MEAN; snippets say what things LOOK LIKE, in terms of those values. A token named after an element (`--fs-button-bg`) is the smell that the two have been mixed — it multiplies with every new element, where a purpose name (`--fs-action-primary`) is reused. SAFETY ------ Values are interpolated into a stylesheet, and design systems are shareable records (rule #47). A value of `red; } body { display: none` in a system someone shared with you would otherwise inject CSS into your page. Everything rendered here is validated or dropped — never escaped-and-hoped. """ from __future__ import annotations import re from collections.abc import Sequence BASE_MODE = "base" # A custom property name, strictly. Anything else is dropped rather than # sanitised: a name is an identifier, and a "cleaned up" identifier is a # different token than the one the operator recorded. _VALID_NAME = re.compile(r"^--[A-Za-z0-9_-]+$") # Characters that would end the declaration, open a block, start an at-rule, or # begin a tag. A value containing any of them is not a value. _UNSAFE_VALUE = re.compile(r"[{};@<>]|\*/|/\*|\n|\r") def is_valid_token_name(name: str) -> bool: return bool(_VALID_NAME.match((name or "").strip())) def safe_value(value: str) -> str | None: """A CSS value that cannot escape its declaration, or None. Rejects rather than strips. A partially-sanitised value is a value the operator did not write, and silently rendering a different colour than the record holds is worse than rendering none — the sheet's whole claim is that it IS the record. """ candidate = (value or "").strip() if not candidate or _UNSAFE_VALUE.search(candidate): return None return candidate def safe_comment(text: str) -> str: """Comment text that cannot close its comment or break the line.""" return re.sub(r"\*/|/\*|[\n\r]", " ", (text or "")).strip() def selector_for_mode(mode: str, root_selector: str = ":root") -> str: """Which selector a mode's declarations belong under. `base` gets the caller's root selector; every other mode gets a bare attribute selector, matching the convention already in the codebase. The root selector is a PARAMETER because a container-scoped preview cannot use `:root` — #251 recorded that mode scoping is one-way (light lives on `:root`, dark layers over it), so a generator that hardcoded `:root` could not serve a preview at all. """ if mode == BASE_MODE: return root_selector safe_mode = re.sub(r"[^A-Za-z0-9_-]", "", mode) return f'[data-theme="{safe_mode}"]' if safe_mode else root_selector def _grouped(tokens: Sequence) -> list[tuple[str | None, list]]: """Tokens by group, preserving the order they arrive in (already sorted).""" groups: dict[str | None, list] = {} for token in tokens: groups.setdefault(getattr(token, "group_name", None), []).append(token) return list(groups.items()) def _modes_present(tokens: Sequence) -> list[str]: """Every mode any token declares, base first then the rest alphabetically.""" modes = { mode for token in tokens for mode in (getattr(token, "value_by_mode", None) or {}) } rest = sorted(modes - {BASE_MODE}) return ([BASE_MODE] if BASE_MODE in modes else []) + rest def render_stylesheet( tokens: Sequence, *, root_selector: str = ":root", title: str = "", design_system_id: int | None = None, ) -> str: """The master sheet for a resolved token set. One block per mode. Within a block, only the tokens that declare a value for that mode — so a mode block is an override layer, exactly as the storage model has it. Tokens with no value at all are emitted as COMMENTED-OUT declarations in their group, not dropped. The rulebook named them, so their absence is a finding, and a commented line puts that finding where the reader is already looking. """ header = [ "/*", f" * {safe_comment(title) or 'Design system'} — generated stylesheet", ] if design_system_id is not None: header.append(f" * Source: design system {int(design_system_id)}.") header += [ " *", " * Purpose tokens only. This sheet declares what values MEAN; it styles", " * no elements. Buttons, tables and input schemes live as snippets that", " * reference these names, so each value is stated once and reused rather", " * than restated per element.", " *", " * Generated — edit the design system, not this file.", " */", "", ] lines = list(header) valueless = [ t for t in tokens if is_valid_token_name(getattr(t, "name", "")) and not (getattr(t, "value_by_mode", None) or {}) ] for mode in _modes_present(tokens): block: list[str] = [] for group, group_tokens in _grouped(tokens): entries: list[str] = [] for token in group_tokens: name = (getattr(token, "name", "") or "").strip() if not is_valid_token_name(name): continue raw = (getattr(token, "value_by_mode", None) or {}).get(mode) if raw is None: continue value = safe_value(str(raw)) if value is None: entries.append( f" /* {name}: value rejected — not a safe CSS value */" ) continue # Purpose first — what the token is FOR is what a reader of the # stylesheet needs. Rationale is the fallback so a token that # only carries the why still says something. purpose = safe_comment( getattr(token, "purpose", "") or getattr(token, "rationale", "") or "" ) comment = f" /* {purpose} */" if purpose and mode == BASE_MODE else "" entries.append(f" {name}: {value};{comment}") if entries: if group: block.append(f" /* {safe_comment(group)} */") block.extend(entries) block.append("") # Declared-but-valueless tokens belong to the base layer: they have no # mode to sit under, and repeating them per mode would triple the noise. if mode == BASE_MODE and valueless: block.append(" /* Declared by the design system, no value set yet: */") block.extend(f" /* {t.name}: ; */" for t in valueless) block.append("") if not block: continue lines.append(f"{selector_for_mode(mode, root_selector)} {{") lines.extend(block[:-1] if block[-1] == "" else block) lines.append("}") lines.append("") return "\n".join(lines).rstrip() + "\n" def duplicate_values(tokens: Sequence) -> dict[str, list[str]]: """Values declared by more than one token, mapped to the names declaring them. Two names for one value are either a deliberate alias or the same idea recorded twice — the token-level form of the duplicated-definition shape. Reported rather than refused: a design system legitimately aligns colours on purpose ("Success = Moss, by design"), and a generator that rejected that would be wrong about the operator's intent. Compares the BASE value only. Two tokens agreeing in one mode and diverging in another are not duplicates of each other — they are a near-miss, which is a different and less interesting finding. """ by_value: dict[str, list[str]] = {} for token in tokens: name = getattr(token, "name", "") base = (getattr(token, "value_by_mode", None) or {}).get(BASE_MODE) if not name or not base: continue by_value.setdefault(str(base).strip().lower(), []).append(name) return {value: names for value, names in by_value.items() if len(names) > 1} # --------------------------------------------------------------------------- # Reading a sheet from the other side: does this code use it correctly? # --------------------------------------------------------------------------- # # "The snippets use the tags from the sheet" is a verifiable relation, and # nothing checked it before. Three questions, each a different failure: # # var(--x) where no --x exists -> renders as NOTHING. No error, no test # failure, no visual clue beyond the thing # silently not being styled. # a superseded literal in code -> the value the sheet said to stop writing, # and the sheet knows what to write instead. # --x: declared inside a snippet -> a component minting its own token is the # bloat a shared sheet exists to prevent. # # The first is not hypothetical: `--color-accent` was used throughout a new view # in this codebase and does not exist. _VAR_REFERENCE = re.compile(r"var\(\s*(--[A-Za-z0-9_-]+)") _LOCAL_DEFINITION = re.compile(r"(? set[str]: """Every custom property the code reads through `var()`.""" return set(_VAR_REFERENCE.findall(code or "")) def defined_tokens(code: str) -> set[str]: """Every custom property the code declares itself. Excludes names it also reads: `--x: var(--x, fallback)` is a redeclaration of something the sheet owns, which the unknown-reference check already covers more precisely. """ return set(_LOCAL_DEFINITION.findall(code or "")) - referenced_tokens(code or "") def _literal_pattern(literal: str) -> re.Pattern: """Match a literal value without matching a longer one that contains it. `#fff` must not match inside `#ffffff` — they are different colours, and a finding that fired on the wrong one would send someone to change code that was already correct. """ escaped = re.escape(literal) lead = r"(? dict: """What this code gets wrong about that token set. `tokens` is any sequence with `.name`, `.value_by_mode` and `.supersedes` — resolved tokens, or stored ones. Reports rather than scores. Every finding here has a legitimate exception: a snippet may target a system it isn't being checked against, and a literal may be deliberate in a context the token doesn't cover. What it removes is the SILENCE — all three currently fail with no signal at all. """ known = {getattr(t, "name", "") for t in tokens} referenced = referenced_tokens(code) superseded: list[dict] = [] for token in tokens: for literal in getattr(token, "supersedes", None) or (): if _literal_pattern(str(literal)).search(code or ""): superseded.append({ "literal": literal, "use_instead": getattr(token, "name", ""), }) return { "used": sorted(referenced & known), "unknown": sorted(referenced - known), "superseded_literals": superseded, "local_definitions": sorted(defined_tokens(code)), }