diff --git a/README.md b/README.md index 8985c8d7..cc30bcac 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ it('uses only what compiles', function () { }); ``` -The guard reads every path it is given and fails, with `path:line` and the 2.0.0 replacement, on any Tailwind utility or variant still in a view, PHP or JS file — none compiles, since the application carries no Tailwind — and on 1.x's own utilities (`type-body-md`, `text-meta`, `rounded-corner-lg`, `focus-ring`): a layout component and prop (`flex gap-4` → ``), an `md-*` class (`truncate` → `md-truncate`), or a token for the application's own CSS (`rounded-lg` → `var(--md-sys-shape-corner-lg)`). A class the application's own stylesheets declare is exempt. In the `.css` files it is given (`material-scheme.css` skipped) it fails on a literal colour, radius, shadow, font, easing or duration, and on a media query off M3's 600/840/1200/1600px; a `var()`, or a `calc()`/`min()`/`max()`/`clamp()` built on one, is fine. It also fails on unknown Material Symbol names and Blade directives written inside component tags. +The guard reads every path it is given and fails, with `path:line` and the 2.0.0 replacement, on any Tailwind utility or variant still in a view, PHP or JS file, a colour of the application's own Tailwind theme (`bg-brand`) included — none compiles, since the application carries no Tailwind — and on 1.x's own utilities (`type-body-md`, `text-meta`, `rounded-corner-lg`, `focus-ring`): a layout component and prop (`flex gap-4` → ``), an `md-*` class (`truncate` → `md-truncate`), or a token for the application's own CSS (`rounded-lg` → `var(--md-sys-shape-corner-lg)`). A class the application's own stylesheets declare is exempt. In the `.css` files it is given (`material-scheme.css` skipped) it fails on a literal colour, radius, shadow, font, easing or duration, and on a media query off M3's 600/840/1200/1600px; a `var()`, or a `calc()`/`min()`/`max()`/`clamp()` built on one, is fine. It also fails on unknown Material Symbol names and Blade directives written inside component tags. `missingStylesheets($cssEntry)` checks the entry's relative `@import` graph, followed through every package file's own imports, against the package tags the views render (unprefixed, under the configured prefix, or ``) and `->links()`, and names each missing `@import` line once; a tag the application shadows with its own component is reported instead. It reads imports only, so leave `resource_path('css')` out of `scan()` while the stylesheets still hold literals. `forbidColours([...])` names roles the application leaves out, and fails wherever one (with its `on-` and container roles) is still written: `var(--md-sys-color-…)` in CSS or an inline `style`, its `md-ink-*` class, or a component's `color`/`tone` prop. `forbid($pattern, $reason)` adds any pattern of your own, line by line. diff --git a/UPGRADE.md b/UPGRADE.md index 1cf581b8..2c94c9dd 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -9,6 +9,14 @@ it in the layout, `toggle()` opens and closes the modal rail (`show()`, `hide()`) instead of changing a choice nothing draws there. A shortcut that pressed the rail's menu button to get this can call `$store.rail.toggle()` again. +- **`DesignGuard`** reports a colour utility on a colour the application's own Tailwind theme + named (`bg-brand`, `text-sport-run`, `from-brand`, `border-l-zone-4`, `bg-brand/8`) in a class + list, with `var(--…)` of the application's own as its replacement, unless the application's + CSS declares the class. None of them has compiled since 2.0.0, and they were the leftovers its + count never showed. Tailwind's other utilities on the same prefixes are named by their own + family now too: `border-collapse`, `border-spacing-*`, `from-10%`, `via-none`, `fill-none`, + `stroke-*`, `text-shadow-*`, `text-inherit`, and Tailwind 4.2's `mauve`, `olive`, `mist` and + `taupe` palettes. A test that expects no findings may fail on them. ## From 2.0.0 to 2.1.0 diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md index ddda3735..3d1807b1 100644 --- a/resources/boost/skills/livewire-material-development/SKILL.md +++ b/resources/boost/skills/livewire-material-development/SKILL.md @@ -1196,6 +1196,7 @@ It reads every path it is given and fails with `path:line` on unknown symbol nam | `border-outline-variant`, `border-divider` | `` or `` | | `bg-surface-container` | `` | | `bg-primary`, `border-error`, `text-red-500` … | `var(--md-sys-color-*)` | +| `bg-brand`, `text-sport-run`, `border-l-zone-4/40` … (a colour your own Tailwind theme named) | your own `var(--…)`, or `color-mix(in srgb, var(--…) 40%, transparent)` | | `bg-white`, `text-black` | a surface or `on-` role — M3 has no white or black | | `text-on-surface/60`, `bg-on-surface/8` | `md-ink-variant`/`md-ink-quiet`, or `md-state-layer` for an overlay | | `rounded-lg`, `rounded-corner-lg` (1.x) | `var(--md-sys-shape-corner-lg)`, or `` | diff --git a/src/Testing/DesignGuard.php b/src/Testing/DesignGuard.php index fac3d32a..c26f2e2a 100644 --- a/src/Testing/DesignGuard.php +++ b/src/Testing/DesignGuard.php @@ -19,7 +19,8 @@ use Symfony\Component\Finder\Finder; * cleared scale (radius, shadow, type size/weight/leading/tracking, easing, duration), a * layout, spacing, sizing, position, border, effect, interactivity, text or display * utility, a colour utility on a role, a 1.x ink, white or black (`text-meta`, `bg-white`, - * `text-on-surface/60`), a 1.x package utility (`type-body-md`, `focus-ring`, + * `text-on-surface/60`) or on a colour the application's own Tailwind theme named + * (`bg-brand`, `text-sport-run/60`), a 1.x package utility (`type-body-md`, `focus-ring`, * `rounded-corner-lg`), a variant, or an arbitrary `[…]` value or property — each with its * 2.0.0 replacement: a layout component and prop (`gap-4` → `gap="space200"`), an `md-*` * class, or a token for the application's own CSS. A class the application's own @@ -60,14 +61,14 @@ use Symfony\Component\Finder\Finder; * colour-value families, which also have to see a class assembled as a plain PHP or JS string, as * an enum's own literal colour string does) requires the utility's actual shape — a digit, a * known scale step or a colour function — never a bare word. The trade-off: a bare-word utility - * (`flex`, `hidden`) anywhere but inside a class list, and an application's own Tailwind theme - * names (`bg-brand`), are invisible to this guard. + * (`flex`, `hidden`) and a colour of the application's own Tailwind theme (`bg-brand`) are + * invisible to this guard anywhere but inside a class list. */ class DesignGuard { - protected const string UTILITY = '(?:bg|text|border(?:-[trblxyse])?|ring|ring-offset|fill|stroke|from|via|to|outline|divide|decoration|caret|accent|shadow|placeholder)'; + protected const string UTILITY = '(?:bg|text|border(?:-(?:[trblxyse]|bs|be))?|ring|ring-offset|fill|stroke|from|via|to|outline|divide|decoration|caret|accent|shadow|placeholder)'; - protected const string PALETTE = '(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone)-(?:50|[1-9]00|950)'; + protected const string PALETTE = '(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone|mauve|olive|mist|taupe)-(?:50|[1-9]00|950)'; /** A colour written as a value: an arbitrary hex, function or mix instead of a role. */ protected const string ARBITRARY_COLOUR = '/(?` sets a content column\'s measure, `` an icon\'s; anything else is a length in your own CSS'], ['/^container$/', 'Tailwind\'s `container` compiles to nothing — use ``, which sets M3\'s margins and a measure'], ['/^(?:table(?:-[a-z-]+)?|flow-root|list-item|inline-table)$/', 'Tailwind display utility `%s` compiles to nothing — write the `display` rule in your own CSS'], - ['/^(?:border(?:-[trblxyse])?(?:-\d+)?|border-(?:solid|dashed|dotted|double|hidden|none)|divide-[xy](?:-\d+|-reverse)?|divide-(?:solid|dashed|dotted|double|none))$/', 'Tailwind border utility `%s` compiles to nothing — a line is `` or ``; any other border is your own CSS, in `var(--md-sys-color-outline-variant)`'], + ['/^(?:border(?:-(?:[trblxyse]|bs|be))?(?:-\d+)?|border-(?:solid|dashed|dotted|double|hidden|none)|divide-[xy](?:-\d+|-reverse)?|divide-(?:solid|dashed|dotted|double|none))$/', 'Tailwind border utility `%s` compiles to nothing — a line is `` or ``; any other border is your own CSS, in `var(--md-sys-color-outline-variant)`'], ['/^rounded(?:-(?:ss|se|ee|es|tl|tr|br|bl|t|r|b|l|s|e))?$/', 'Tailwind radius utility `%s` compiles to nothing — use `var(--md-sys-shape-corner-xs)` in your own CSS, or ``'], - ['/^(?:shadow|shadow-none|inset-shadow(?:-[\w]+)?|drop-shadow(?:-[\w]+)?)$/', 'Tailwind shadow utility `%s` compiles to nothing — use `var(--md-sys-elevation-*)` in your own CSS'], + ['/^(?:shadow|shadow-none|shadow-inner|inset-shadow(?:-[\w]+)?|drop-shadow(?:-[\w]+)?)$/', 'Tailwind shadow utility `%s` compiles to nothing — use `var(--md-sys-elevation-*)` in your own CSS'], ['/^(?:outline(?:-none|-hidden|-\d+|-offset-\d+|-dashed|-dotted|-double|-solid)?|ring(?:-\d+|-inset)?|ring-offset-\d+)$/', 'Tailwind outline utility `%s` compiles to nothing — M3\'s focus indicator is `md-focus-ring` (interaction.css); any other outline is your own CSS'], ['/^(?:transition(?:-(?:all|colors|opacity|shadow|transform|none|discrete))?|animate-[\w-]+)$/', 'Tailwind motion utility `%s` compiles to nothing — pair `var(--md-sys-motion-spatial-*)`/`var(--md-sys-motion-effects-*)` with its `-duration` in your own `transition` or `animation`'], ['/^opacity-\d+$/', 'Tailwind opacity utility `%s` compiles to nothing — write `opacity` in your own CSS (M3\'s disabled content is 38 %%)'], ['/^(?:transform(?:-none|-gpu|-cpu)?|-?(?:scale|rotate|skew-[xy]|translate-[xy]|scale-[xy])-[\w.\/]+|origin-[\w-]+|will-change-[\w-]+|blur(?:-\w+)?|backdrop-[\w-]+|mix-blend-[\w-]+|isolate|isolation-auto)$/', 'Tailwind effect utility `%s` compiles to nothing — write the rule in your own CSS'], - ['/^(?:cursor-[\w-]+|pointer-events-(?:none|auto)|select-(?:none|text|all|auto)|touch-[\w-]+|resize(?:-[xy]|-none)?|appearance-(?:none|auto)|scroll-(?:smooth|auto)|-?scroll-[mp][trblxyse]?-[\w.]+|snap-[\w-]+)$/', 'Tailwind interactivity utility `%s` compiles to nothing — write the rule in your own CSS'], + ['/^(?:cursor-[\w-]+|accent-auto|pointer-events-(?:none|auto)|select-(?:none|text|all|auto)|touch-[\w-]+|resize(?:-[xy]|-none)?|appearance-(?:none|auto)|scroll-(?:smooth|auto)|-?scroll-[mp][trblxyse]?-[\w.]+|snap-[\w-]+)$/', 'Tailwind interactivity utility `%s` compiles to nothing — write the rule in your own CSS'], ['/^(?:aspect-(?:auto|square|video|\d+\/\d+)|object-(?:contain|cover|fill|none|scale-down|top|bottom|center|left|right|left-top|left-bottom|right-top|right-bottom))$/', 'Tailwind media utility `%s` compiles to nothing — write `aspect-ratio`/`object-fit` in your own CSS'], ['/^font-(?:sans|serif)$/', 'Tailwind\'s `%s` compiles to nothing — the foundation already sets the brand typeface; any other `font-family` is your own CSS'], ['/^font-mono$/', 'Tailwind\'s `%s` compiles to nothing — put the value in ``, `` or ``, or use `md-mono` (text.css)'], ['/^antialiased$/', 'Tailwind\'s `%s` compiles to nothing — the foundation already smooths text in grayscale (base.css); drop it'], ['/^(?:underline|no-underline)$/', 'Tailwind\'s `%s` compiles to nothing — `md-link` draws a link (interaction.css); any other decoration is your own CSS'], - ['/^(?:uppercase|lowercase|capitalize|normal-case|italic|not-italic|overline|line-through|underline-offset-\w+|decoration-(?:\d+|solid|double|dotted|dashed|wavy|auto|from-font|clone|slice)|subpixel-antialiased|whitespace-(?:normal|pre|pre-line|pre-wrap|break-spaces)|break-(?:words|all|keep|normal)|wrap-(?:break-word|anywhere|normal)|text-(?:wrap|balance|pretty|ellipsis|clip|justify)|line-clamp-(?:\d+|none)|not-sr-only|list-(?:disc|decimal|none|inside|outside)|align-(?:baseline|top|middle|bottom|text-top|text-bottom|sub|super)|indent-[\w.]+|hyphens-(?:none|manual|auto)|(?:normal|lining|oldstyle|proportional)-nums|ordinal|slashed-zero|(?:diagonal|stacked)-fractions)$/', 'Tailwind text utility `%s` compiles to nothing — write the rule in your own CSS'], - ['/^bg-(?:(?:gradient-to|linear-to)-[a-z]+|radial|conic|cover|contain|auto|center|top|bottom|left|right|no-repeat|repeat(?:-[xy])?|fixed|local|scroll|none|clip-[a-z]+|origin-[a-z]+)$/', 'Tailwind background utility `%s` compiles to nothing — write the rule in your own CSS'], + ['/^(?:uppercase|lowercase|capitalize|normal-case|italic|not-italic|overline|line-through|underline-offset-\w+|decoration-(?:\d+|solid|double|dotted|dashed|wavy|auto|from-font|clone|slice)|subpixel-antialiased|whitespace-(?:normal|pre|pre-line|pre-wrap|break-spaces)|break-(?:words|all|keep|normal)|wrap-(?:break-word|anywhere|normal)|text-(?:wrap|balance|pretty|ellipsis|clip|justify)|text-shadow-[\w-]+(?:\/\d+)?|line-clamp-(?:\d+|none)|not-sr-only|list-(?:disc|decimal|none|inside|outside)|align-(?:baseline|top|middle|bottom|text-top|text-bottom|sub|super)|indent-[\w.]+|hyphens-(?:none|manual|auto)|(?:normal|lining|oldstyle|proportional)-nums|ordinal|slashed-zero|(?:diagonal|stacked)-fractions)$/', 'Tailwind text utility `%s` compiles to nothing — write the rule in your own CSS'], + ['/^bg-(?:(?:gradient-to|linear-to)-[a-z]+|(?:linear|conic)-\d+|radial|conic|cover|contain|auto|center|top|bottom|left|right|(?:top|bottom)-(?:left|right)|(?:left|right)-(?:top|bottom)|no-repeat|repeat(?:-[xy]|-round|-space)?|fixed|local|scroll|none|clip-[a-z]+|origin-[a-z]+|blend-[a-z-]+)$/', 'Tailwind background utility `%s` compiles to nothing — write the rule in your own CSS'], + ['/^(?:from|via|to)-(?:\d{1,3}%|none)$/', 'Tailwind gradient utility `%s` compiles to nothing — write the gradient in your own CSS'], + ['/^(?:border-(?:collapse|separate)|border-spacing(?:-[xy])?-(?:\d+(?:\.\d+)?|px))$/', 'Tailwind table utility `%s` compiles to nothing — write the rule in your own CSS'], + ['/^(?:(?:fill|stroke)-none|stroke-\d+)$/', 'Tailwind SVG utility `%s` compiles to nothing — write `fill` or `stroke` in your own CSS'], ]; /** Bare display utilities, matched only as a whole class-list token. */ @@ -194,12 +198,23 @@ class DesignGuard * optional opacity modifier (`text-on-surface/60`, `bg-scrim/[0.32]`). The roles are every * `--md-sys-color-*` the default scheme and elevation tokens declare. */ - protected const string COLOUR_UTILITY = '/^(?bg|text|border(?:-[trblxyse])?|divide|ring(?:-offset)?|outline|fill|stroke|decoration|accent|caret|placeholder|shadow|from|via|to)-' + protected const string COLOUR_UTILITY = '/^(?bg|text|border(?:-(?:[trblxyse]|bs|be))?|divide|ring(?:-offset)?|outline|fill|stroke|decoration|accent|caret|placeholder|shadow|from|via|to)-' .'(?(?:on-)?(?:primary|secondary|tertiary|error|success|warning|info)(?:-container|-dim|-fixed(?:-dim|-variant)?)?' .'|inverse-(?:primary|surface|on-surface|error|success|warning|info)' .'|(?:on-)?background|(?:on-)?surface(?:-variant|-dim|-bright|-container(?:-lowest|-low|-high|-highest)?)?' - .'|outline(?:-variant)?|scrim|shadow|body|meta|quiet|structure|chrome|divider|white|black|current|transparent)' - .'(?:\/(?\d{1,3}|\[[^\]]*\]))?$/'; + .'|outline(?:-variant)?|scrim|shadow|body|meta|quiet|structure|chrome|divider|white|black|current|transparent|inherit|initial)' + .'(?:\/(?\d{1,3}|\[[^\]]*\]|\(--[\w-]+\)))?$/'; + + /** + * A colour utility on any other name (`bg-off-plan`, `text-sport-run`, `border-l-zone-4`, + * `bg-route-reference/8`): a colour the application's own Tailwind theme named before 2.0.0, + * which nothing declares now. Tried only after `COLOUR_UTILITY`, the palette and every family + * in `FAMILIES`, so a Tailwind utility sharing the prefix (`text-balance`, `border-collapse`, + * `bg-cover`, `shadow-none`) is reported as that family; the name starts with a letter, so a + * width or a stop (`border-2`, `from-10%`) never reads as one. + */ + protected const string THEME_COLOUR_UTILITY = '/^(?bg|text|border(?:-(?:[trblxyse]|bs|be))?|divide|ring(?:-offset)?|outline|fill|stroke|decoration|accent|caret|placeholder|shadow|from|via|to)-' + .'(?[a-z][a-z\d]*(?:-[a-z\d]+)*)(?:\/(?\d{1,3}|\[[^\]]*\]|\(--[\w-]+\)))?$/'; /** 1.x's semantic ink names, and the M3 role each resolved to (UPGRADE.md § 4). */ protected const array INK_ALIASES = [ @@ -811,7 +826,25 @@ class DesignGuard } } - return null; + return $this->themeColourHint($token); + } + + /** + * The 2.0.0 replacement for a colour utility on a name neither M3 nor Tailwind has (see + * `THEME_COLOUR_UTILITY`), or null: the application's own custom property in its own CSS, mixed + * toward transparent where the utility carried an opacity. + */ + protected function themeColourHint(string $token): ?string + { + if (preg_match(self::THEME_COLOUR_UTILITY, $token, $m, PREG_UNMATCHED_AS_NULL) !== 1) { + return null; + } + + $dead = "Tailwind colour utility `{$token}` compiles to nothing — `{$m['name']}` is neither an M3 role nor a Tailwind colour, so it named a colour of the application's own theme"; + + return $m['opacity'] !== null + ? "{$dead}: use `color-mix(in srgb, var(--…) %, transparent)` in your own CSS" + : "{$dead}: use `var(--…)` in your own CSS"; } /** @@ -930,11 +963,11 @@ class DesignGuard return match (true) { in_array($role, ['white', 'black'], true) => "{$dead} — M3 paints with roles, never white or black: `` or `var(--md-sys-color-surface-container-lowest)` for a white surface, an `on-` role (`md-ink`, `var(--md-sys-color-on-primary)`) for ink", - in_array($role, ['current', 'transparent'], true) => "{$dead} — write `".($role === 'current' ? 'currentColor' : 'transparent').'` in your own CSS', + in_array($role, ['current', 'transparent', 'inherit', 'initial'], true) => "{$dead} — write `".($role === 'current' ? 'currentColor' : $role).'` in your own CSS', $m['opacity'] !== null && $utility === 'text' => "{$dead} — M3's quieter text is a role, not a faded one: `md-ink-variant` or `md-ink-quiet` (text.css)", $m['opacity'] !== null => "{$dead} — M3's hover, focus and press overlays are `md-state-layer`; any other tint is `color-mix(in srgb, var(--md-sys-color-{$role}) %, transparent)` in your own CSS", $utility === 'text' && isset(self::INK_ROLE[$role]) => "{$dead} — use `".self::INK_ROLE[$role].'` (text.css)', - preg_match('/^(?:border(?:-[trblxyse])?|divide)$/', $utility) === 1 && in_array($role, ['outline', 'outline-variant'], true) => "{$dead} — a line is `` or ``, not a border utility", + preg_match('/^(?:border(?:-(?:[trblxyse]|bs|be))?|divide)$/', $utility) === 1 && in_array($role, ['outline', 'outline-variant'], true) => "{$dead} — a line is `` or ``, not a border utility", $utility === 'bg' && in_array($role === 'background' ? 'surface' : $role, self::SURFACE_LEVELS, true) => "{$dead} — use ``', default => "{$dead} — use `var(--md-sys-color-{$role})` in your own CSS", }; diff --git a/tests/Feature/BoostVocabularyTest.php b/tests/Feature/BoostVocabularyTest.php index bab7cd56..856c9aa6 100644 --- a/tests/Feature/BoostVocabularyTest.php +++ b/tests/Feature/BoostVocabularyTest.php @@ -1,6 +1,7 @@ implode("\n", $view), 'css' => implode("\n", $css)]; } +/** + * The hyphenated names the texts also write alone in a span for something that is not a class: + * every property the package's stylesheets declare (`border-color`), every component prop + * (`placeholder-value`) and every colour role (`outline-variant`). Each is shaped like a colour + * utility on a name the guard does not know, which is what an application's own theme colour + * (`bg-brand`) looks like. + * + * @return array + */ +function boostNamesThatAreNotClasses(): array +{ + static $names = null; + + if ($names !== null) { + return $names; + } + + $names = []; + $root = __DIR__.'/../../resources'; + + foreach (File::allFiles("{$root}/css") as $file) { + preg_match_all('/(?getContents(), $matches); + + foreach ([...$matches[1], ...$matches[2]] as $name) { + $names[$name] = true; + } + } + + foreach (File::files("{$root}/views/components") as $file) { + if (preg_match('/@props\(\[(.*?)\]\)/s', $file->getContents(), $props) === 1) { + preg_match_all("/^\s*'(\w+)'/m", $props[1], $keys); + + foreach ($keys[1] as $key) { + $names[Str::kebab($key)] = true; + } + } + } + + return $names; +} + /** * Whether an inline span reads as a class list. Not: a CSS declaration (`font: var(…)`), a - * command (`php artisan …`), or a single bare word, which is as often a role, prop or value - * (`outline`, `link`, `hidden`) as a utility. + * command (`php artisan …`), a single bare word, which is as often a role, prop or value + * (`outline`, `link`, `hidden`) as a utility, or a single property, prop or role name + * (`border-color`, `placeholder-value`, `outline-variant`). */ function boostSpanIsClassList(string $span): bool { $tokens = preg_split('/\s+/', trim($span)); - if (count($tokens) === 1 && preg_match('/[-:]/', $tokens[0]) !== 1) { + if (count($tokens) === 1 && (preg_match('/[-:]/', $tokens[0]) !== 1 || isset(boostNamesThatAreNotClasses()[$tokens[0]]))) { return false; } diff --git a/tests/Feature/DesignGuardTest.php b/tests/Feature/DesignGuardTest.php index 46f11480..2333002c 100644 --- a/tests/Feature/DesignGuardTest.php +++ b/tests/Feature/DesignGuardTest.php @@ -285,6 +285,57 @@ it('exempts a class the application\'s own CSS declares, even when it is Tailwin ]); }); +it('reports a colour utility on the application\'s own theme colour, and a Tailwind utility of the same prefix as its own family', function () { + $violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/theme-colours.blade.php'))->violations()); + $theme = fn (string $token, string $name, string $use = '`var(--…)`'): string => "Tailwind colour utility `{$token}` compiles to nothing — `{$name}` is neither an M3 role nor a Tailwind colour, so it named a colour of the application's own theme: use {$use} in your own CSS"; + $tint = '`color-mix(in srgb, var(--…) %, transparent)`'; + $line = 'a line is `` or ``; any other border is your own CSS, in `var(--md-sys-color-outline-variant)`'; + $outline = "M3's focus indicator is `md-focus-ring` (interaction.css); any other outline is your own CSS"; + + expect($violations)->toBe([ + 'tailwind/theme-colours.blade.php:1 '.$theme('bg-off-plan', 'off-plan'), + 'tailwind/theme-colours.blade.php:1 '.$theme('from-brand', 'brand'), + 'tailwind/theme-colours.blade.php:1 '.$theme('text-brand-ink', 'brand-ink'), + 'tailwind/theme-colours.blade.php:1 '.$theme('text-sport-run', 'sport-run'), + 'tailwind/theme-colours.blade.php:1 '.$theme('to-brand-end', 'brand-end'), + 'tailwind/theme-colours.blade.php:2 '.$theme('bg-route-reference/8', 'route-reference', $tint), + 'tailwind/theme-colours.blade.php:2 '.$theme('border-l-zone-4', 'zone-4'), + 'tailwind/theme-colours.blade.php:2 '.$theme('border-route-reference', 'route-reference'), + 'tailwind/theme-colours.blade.php:2 '.$theme('ring-offset-brand/[0.4]', 'brand', $tint), + 'tailwind/theme-colours.blade.php:3 Tailwind text utility `text-balance` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:3 Tailwind text utility `text-clip` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:3 Tailwind text utility `text-ellipsis` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:3 Tailwind text utility `text-pretty` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:3 Tailwind text utility `text-wrap` compiles to nothing — write the rule in your own CSS', + "tailwind/theme-colours.blade.php:3 Tailwind's `text-nowrap` compiles to nothing — use `md-nowrap` (text.css)", + "tailwind/theme-colours.blade.php:4 Tailwind border utility `border-dashed` compiles to nothing — {$line}", + "tailwind/theme-colours.blade.php:4 Tailwind border utility `border-dotted` compiles to nothing — {$line}", + "tailwind/theme-colours.blade.php:4 Tailwind border utility `border-none` compiles to nothing — {$line}", + "tailwind/theme-colours.blade.php:4 Tailwind border utility `border-solid` compiles to nothing — {$line}", + 'tailwind/theme-colours.blade.php:4 Tailwind table utility `border-collapse` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:4 Tailwind table utility `border-separate` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:5 Tailwind gradient utility `from-0%` compiles to nothing — write the gradient in your own CSS', + 'tailwind/theme-colours.blade.php:5 Tailwind gradient utility `via-none` compiles to nothing — write the gradient in your own CSS', + "tailwind/theme-colours.blade.php:5 Tailwind outline utility `outline-dashed` compiles to nothing — {$outline}", + "tailwind/theme-colours.blade.php:5 Tailwind outline utility `outline-none` compiles to nothing — {$outline}", + "tailwind/theme-colours.blade.php:5 Tailwind outline utility `ring-inset` compiles to nothing — {$outline}", + 'tailwind/theme-colours.blade.php:5 Tailwind shadow utility `shadow-none` compiles to nothing — use `var(--md-sys-elevation-*)` in your own CSS', + 'tailwind/theme-colours.blade.php:5 Tailwind text utility `decoration-wavy` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind SVG utility `fill-none` compiles to nothing — write `fill` or `stroke` in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind background utility `bg-center` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind background utility `bg-clip-text` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind background utility `bg-cover` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind background utility `bg-fixed` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind background utility `bg-no-repeat` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind background utility `bg-top-left` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind colour utility `text-inherit` compiles to nothing — write `inherit` in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind interactivity utility `accent-auto` compiles to nothing — write the rule in your own CSS', + 'tailwind/theme-colours.blade.php:6 Tailwind text utility `text-shadow-sm` compiles to nothing — write the rule in your own CSS', + "tailwind/theme-colours.blade.php:7 Tailwind border utility `border-bs-2` compiles to nothing — {$line}", + 'tailwind/theme-colours.blade.php:7 Tailwind palette colour `bg-mauve-500` compiles to nothing — M3 paints with roles: an `md-ink-*` class, or `var(--md-sys-color-*)` in your own CSS', + ]); +}); + it('reports an arbitrary [] value once, whatever utility it modifies, and strips a trailing !', function () { $violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/tailwind/arbitrary.blade.php'))->violations()); diff --git a/tests/Fixtures/design-guard/exempt/app.css b/tests/Fixtures/design-guard/exempt/app.css index c1cab08f..414b7cfa 100644 --- a/tests/Fixtures/design-guard/exempt/app.css +++ b/tests/Fixtures/design-guard/exempt/app.css @@ -1,3 +1,7 @@ .gap-4 { gap: 1rem; } + +.bg-brand { + background: var(--brand); +} diff --git a/tests/Fixtures/design-guard/exempt/view.blade.php b/tests/Fixtures/design-guard/exempt/view.blade.php index 5baf984b..269f687c 100644 --- a/tests/Fixtures/design-guard/exempt/view.blade.php +++ b/tests/Fixtures/design-guard/exempt/view.blade.php @@ -1 +1 @@ -
+
diff --git a/tests/Fixtures/design-guard/tailwind/theme-colours.blade.php b/tests/Fixtures/design-guard/tailwind/theme-colours.blade.php new file mode 100644 index 00000000..e6191d4f --- /dev/null +++ b/tests/Fixtures/design-guard/tailwind/theme-colours.blade.php @@ -0,0 +1,8 @@ +
+ +

+
+ + + +