Keep missingStylesheets() to imports, and never crash on a foreign one
Plan step 41 review: an application's CSS entry may import what a Vite build resolves but Stylesheets::bundle() refuses (tailwindcss mid- migration, a font URL, a layer() import); resolvedFiles() threw on each, failing the application's whole design test. It now follows relative imports only and skips the rest. Each missing stylesheet, shadowed tag and ->links() is reported once, at its first use, instead of at every occurrence; a CSS entry that does not exist says so; the @import line goes through vendor/ when Composer symlinks the package. missingStylesheets() also switched on the literal-value check for the entry's whole import graph, vendor stylesheets included, with no way to check imports alone. Check (iii) now reads the .css files scan() is given, like every other check; the entry's imports still feed the class exemptions, now escaped selectors (.hover\:underline) included. 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
9595722f35
commit
c0c71ef16c
+48
-11
@@ -93,26 +93,63 @@ final class Stylesheets
|
||||
}
|
||||
|
||||
/**
|
||||
* Every file `$files` reaches, transitively, through the same `@import` resolution `bundle()`
|
||||
* walks — real paths, `$files` themselves included — without concatenating anything.
|
||||
* `DesignGuard::missingStylesheets()` (plan step 41) asks this: whether an application's CSS
|
||||
* entry's import graph already reaches a given package stylesheet, which needs the file
|
||||
* identities `bundle()` resolves, not the CSS it produces. Shares `bundle()`'s cache (and so
|
||||
* its freshness rule) under the same key, `$base` fixed to `null`, since only `bundle()` ever
|
||||
* populates it.
|
||||
* Every file `$files` reaches, transitively, through their relative `@import`s — real paths,
|
||||
* `$files` themselves included — without concatenating anything.
|
||||
* `DesignGuard::missingStylesheets()` (plan step 41) asks this of an application's CSS entry,
|
||||
* which may import what `bundle()` refuses and a Vite build still resolves: a package name
|
||||
* (`tailwindcss`), a URL, an import with a `layer()` or media condition. So unlike `bundle()`
|
||||
* this never throws: an import naming a URL, an absolute path or a file that does not exist
|
||||
* relative to its stylesheet is skipped, and a conditional import still counts as reaching
|
||||
* its file.
|
||||
*
|
||||
* @param list<string> $files
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function resolvedFiles(array $files): array
|
||||
{
|
||||
if ($files === []) {
|
||||
return [];
|
||||
$seen = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$real = realpath($file);
|
||||
|
||||
if ($real !== false && is_file($real)) {
|
||||
self::reach($real, $seen);
|
||||
}
|
||||
}
|
||||
|
||||
self::bundle($files);
|
||||
return array_keys($seen);
|
||||
}
|
||||
|
||||
return array_keys(self::$cache[serialize([$files, null])]['files']);
|
||||
/**
|
||||
* @param array<string, true> $seen every file already reached, by real path
|
||||
*/
|
||||
private static function reach(string $file, array &$seen): void
|
||||
{
|
||||
if (isset($seen[$file])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$seen[$file] = true;
|
||||
$css = (string) file_get_contents($file);
|
||||
$masked = self::mask($css);
|
||||
|
||||
preg_match_all('/@import\b/i', $masked, $matches, PREG_OFFSET_CAPTURE);
|
||||
|
||||
foreach ($matches[0] as [, $start]) {
|
||||
$end = strpos($masked, ';', $start);
|
||||
$statement = substr($css, $start, ($end === false ? strlen($css) : $end) - $start);
|
||||
|
||||
if (preg_match('/^@import\s*(?:url\(\s*([\'"]?)([^\'"()\s]+)\1\s*\)|([\'"])(.+?)\3)/is', $statement, $match, PREG_UNMATCHED_AS_NULL) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$target = $match[2] ?? $match[4] ?? '';
|
||||
$real = preg_match('~^(?:[a-z][a-z0-9+.-]*:|/)~i', $target) === 1 ? false : realpath(dirname($file).'/'.$target);
|
||||
|
||||
if ($real !== false && is_file($real)) {
|
||||
self::reach($real, $seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user