Retarget forbidColours() at where 2.0.0 writes a role
Plan step 41 review (the user, 2026-09-15): an application without Tailwind names a role through its --md-sys-color-* custom property (CSS, an inline style, a script), an md-ink-* class, or a component's color/tone prop, not a bg-tertiary utility. forbidColours() keeps its name and signature and reports each of those, the role's on- and container roles included; a Tailwind leftover stays family (i)'s single report. ReStride's forbidColours(['tertiary', 'primary-container']) keeps its meaning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
6dc996afdf
commit
d5972b8ddb
@@ -239,7 +239,11 @@ class DesignGuard
|
||||
|
||||
/**
|
||||
* Roles the application's own rules leave out, e.g. ['tertiary', 'primary-container'].
|
||||
* Their on-roles and containers are forbidden with them.
|
||||
* Their on-roles and containers are forbidden with them, wherever 2.0.0 lets an application
|
||||
* write one: the `--md-sys-color-*` custom property (a `var()` in its CSS, an inline `style`, a
|
||||
* script reading it), the `md-ink-*` class text.css has for it, and a component's `color` or
|
||||
* `tone` prop (`color="tertiary"`, `:tone="'tertiary'"`). A Tailwind `bg-tertiary` left behind
|
||||
* is family (i)'s to report, as every colour utility is.
|
||||
*
|
||||
* @param list<string> $roles
|
||||
*/
|
||||
@@ -325,6 +329,10 @@ class DesignGuard
|
||||
$violations[] = "{$where}:{$line} Blade directive `{$directive}` inside a component tag, where it does not compile";
|
||||
}
|
||||
|
||||
foreach ($this->forbiddenRoleProps($contents) as [$line, $what]) {
|
||||
$violations[] = "{$where}:{$line} {$what}";
|
||||
}
|
||||
|
||||
if ($this->cssEntry !== null) {
|
||||
foreach ($this->missingStylesheetViolations($contents, $resolved) as [$line, $what]) {
|
||||
$violations[] = "{$where}:{$line} {$what}";
|
||||
@@ -341,7 +349,7 @@ class DesignGuard
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->offTheTokens($text) as $what) {
|
||||
foreach ([...$this->offTheTokens($text), ...$this->forbiddenRoles($text)] as $what) {
|
||||
$violations[] = "{$where}:{$line} {$what}";
|
||||
}
|
||||
|
||||
@@ -376,14 +384,77 @@ class DesignGuard
|
||||
|
||||
protected function colourPattern(): string
|
||||
{
|
||||
$names = [self::PALETTE];
|
||||
return '/(?<![\w-])'.self::UTILITY.'-'.self::PALETTE.'(?![\w-])/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Every place one line names a role `forbidColours()` left out: its `--md-sys-color-*` custom
|
||||
* property, or the `md-ink-*` class text.css draws it with.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function forbiddenRoles(string $text): array
|
||||
{
|
||||
$found = [];
|
||||
|
||||
foreach ($this->forbiddenColours as $role) {
|
||||
$role = preg_quote($role, '/');
|
||||
$names[] = "(?:on-)?{$role}(?:-container)?";
|
||||
$names = ['--md-sys-color-(?:on-)?'.preg_quote($role, '/').'(?:-container)?'];
|
||||
|
||||
foreach ([$role, "on-{$role}", "{$role}-container", "on-{$role}-container"] as $variant) {
|
||||
if (isset(self::INK_ROLE[$variant])) {
|
||||
$names[] = preg_quote(self::INK_ROLE[$variant], '/');
|
||||
}
|
||||
}
|
||||
|
||||
preg_match_all('/(?<![\w-])(?:'.implode('|', $names).')(?![\w-])/', $text, $matches);
|
||||
|
||||
foreach ($matches[0] as $written) {
|
||||
$found[] = "`{$written}`: role `{$role}` is not part of this application's palette";
|
||||
}
|
||||
}
|
||||
|
||||
return '/(?<![\w-])'.self::UTILITY.'-(?:'.implode('|', $names).')(?![\w-])/';
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every component `color` or `tone` prop in `$contents` naming a role `forbidColours()` left
|
||||
* out, literal (`color="tertiary"`) or any string a bound one can take
|
||||
* (`:tone="$failed ? 'error' : 'info'"`).
|
||||
*
|
||||
* @return list<array{0: int, 1: string}>
|
||||
*/
|
||||
protected function forbiddenRoleProps(string $contents): array
|
||||
{
|
||||
if ($this->forbiddenColours === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
preg_match_all('/<x-[\w.:-]+((?:[^>"]|"[^"]*")*)>/s', $contents, $tags, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
||||
|
||||
$found = [];
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
preg_match_all('/\s(?<bound>:?)(?:color|tone)="(?<value>[^"]*)"/', $tag[1][0], $props, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
||||
|
||||
foreach ($props as $prop) {
|
||||
$values = $prop['bound'][0] === ':'
|
||||
? (preg_match_all("/'([^']*)'/", $prop['value'][0], $strings) ? $strings[1] : [])
|
||||
: [$prop['value'][0]];
|
||||
|
||||
foreach ($values as $value) {
|
||||
foreach ($this->forbiddenColours as $role) {
|
||||
if (preg_match('/^(?:on-)?'.preg_quote($role, '/').'(?:-container)?$/', $value) === 1) {
|
||||
$found[] = [
|
||||
substr_count(substr($contents, 0, $tag[1][1] + $prop[0][1]), "\n") + 1,
|
||||
'`'.trim($prop[0][0])."`: role `{$role}` is not part of this application's palette",
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -592,9 +663,16 @@ class DesignGuard
|
||||
$violations = [];
|
||||
|
||||
foreach ($this->applicationStylesheets() as $file) {
|
||||
$css = $this->withoutTokenFunctions($this->maskedCss((string) file_get_contents($file)));
|
||||
$masked = $this->maskedCss((string) file_get_contents($file));
|
||||
$css = $this->withoutTokenFunctions($masked);
|
||||
$where = $this->relative($file);
|
||||
|
||||
foreach (explode("\n", $masked) as $index => $text) {
|
||||
foreach ($this->forbiddenRoles($text) as $what) {
|
||||
$violations[] = "{$where}:".($index + 1)." {$what}";
|
||||
}
|
||||
}
|
||||
|
||||
foreach ([
|
||||
...$this->literalColours($css),
|
||||
...$this->literalDeclarations($css),
|
||||
|
||||
Reference in New Issue
Block a user