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:
Andreas Reinhold / reini
2026-09-15 08:46:33 +02:00
co-authored by Claude Opus 5
parent 9595722f35
commit c0c71ef16c
8 changed files with 163 additions and 46 deletions
+19
View File
@@ -405,6 +405,25 @@ it('needs nothing more once the entry imports all.css', function () {
expect($violations)->toBe([]);
});
it('follows an entry that imports what only a Vite build resolves, reports each missing stylesheet once, and leaves the entry\'s literals to scan()', function () {
$entry = realpath(GUARD_FIXTURES.'/stylesheets/foreign-imports.css');
$mt2 = "stylesheets/views-foreign/page.blade.php:1 Tailwind spacing utility `mt-2` compiles to nothing — space between siblings is a layout component's `gap=\"space100\"` (8px); any other margin is `var(--md-sys-measurement-space100)` in your own CSS";
$button = "stylesheets/views-foreign/page.blade.php:2 `<x-button>` needs `components/button.css`, missing from stylesheets/foreign-imports.css — add `@import '../../../../resources/css/components/button.css';`";
expect(fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views-foreign'))->missingStylesheets($entry)->violations()))
->toBe([$mt2, $button])
->and(fixtureRelative(DesignGuard::scan([realpath(GUARD_FIXTURES.'/stylesheets/views-foreign'), $entry])->missingStylesheets($entry)->violations()))
->toBe([$mt2, $button, 'stylesheets/foreign-imports.css:7 literal colour `#ff0000` — use `var(--md-sys-color-*)`']);
});
it('says so when the CSS entry does not exist', function () {
$violations = DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/plain.blade.php'))
->missingStylesheets('/nowhere/app.css')
->violations();
expect($violations)->toBe(['/nowhere/app.css:1 the CSS entry `missingStylesheets()` names does not exist']);
});
it('requires foundation.css whatever the views hold', function () {
$violations = fixtureRelative(DesignGuard::scan(realpath(GUARD_FIXTURES.'/stylesheets/views/plain.blade.php'))
->missingStylesheets(realpath(GUARD_FIXTURES.'/stylesheets/missing-foundation.css'))
+21
View File
@@ -93,6 +93,27 @@ it('resolves the files an entry imports, transitively, without bundling any cont
);
});
it('resolves past what an application entry imports and bundle() refuses, without throwing', function () {
$dir = sys_get_temp_dir().'/livewire-material-resolved-'.Str::random(8);
File::makeDirectory($dir);
File::put($dir.'/theme.css', '.theme {}');
File::put($dir.'/app.css', implode("\n", [
"@import 'tailwindcss';",
"@import url('https://fonts.googleapis.com/css2?family=Roboto');",
"@import './theme.css' layer(app);",
"@import './missing.css';",
"@import '".stylesheetsBundlePath('components/icon.css')."';",
]));
try {
$files = Stylesheets::resolvedFiles([$dir.'/app.css']);
} finally {
File::deleteDirectory($dir);
}
expect($files)->toBe([realpath(sys_get_temp_dir()).'/'.basename($dir).'/app.css', realpath(sys_get_temp_dir()).'/'.basename($dir).'/theme.css']);
});
it('keeps a file two others import once, at its first position', function () {
$dir = sys_get_temp_dir().'/livewire-material-bundle-'.Str::random(8);
File::makeDirectory($dir);