mirror of
https://github.com/pestphp/pest.git
synced 2026-06-05 02:52:12 +02:00
wip
This commit is contained in:
@ -426,6 +426,39 @@ final class Graph
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unknown Blade files can still be routed precisely when another
|
||||||
|
// recorded Blade view statically references them (`@include`,
|
||||||
|
// `@extends`, `<x-alert />`, etc.). Walk the source-level Blade graph
|
||||||
|
// upward to rendered ancestors and invalidate tests that rendered those
|
||||||
|
// ancestors instead of broadcasting every Blade edit to the whole suite.
|
||||||
|
$staticallyHandledBlade = [];
|
||||||
|
foreach ($nonMigrationPaths as $rel) {
|
||||||
|
if (isset($this->fileIds[$rel])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! $this->isBladePath($rel)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! is_file($this->projectRoot.'/'.$rel)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bladeAffected = $this->affectedByStaticBladeUsage($rel);
|
||||||
|
|
||||||
|
if ($bladeAffected !== []) {
|
||||||
|
foreach ($bladeAffected as $testFile) {
|
||||||
|
$affectedSet[$testFile] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$staticallyHandledBlade[$rel] = true;
|
||||||
|
} elseif ($this->isBladeComponentPath($rel)) {
|
||||||
|
// Anonymous Blade components are leaf templates. If nothing in
|
||||||
|
// the project statically renders the component, treat it like an
|
||||||
|
// orphan rather than running the full suite.
|
||||||
|
$staticallyHandledBlade[$rel] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Watch-pattern lookup — fallback for files we don't have
|
// 2. Watch-pattern lookup — fallback for files we don't have
|
||||||
// precise edges for. When a file is already in `$fileIds` step
|
// precise edges for. When a file is already in `$fileIds` step
|
||||||
// 1 resolved it surgically; broadcasting it again through the
|
// 1 resolved it surgically; broadcasting it again through the
|
||||||
@ -450,6 +483,9 @@ final class Graph
|
|||||||
if (isset($sharedFilesResolved[$rel])) {
|
if (isset($sharedFilesResolved[$rel])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (isset($staticallyHandledBlade[$rel])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (! isset($this->fileIds[$rel])) {
|
if (! isset($this->fileIds[$rel])) {
|
||||||
if (! is_file($this->projectRoot.'/'.$rel)) {
|
if (! is_file($this->projectRoot.'/'.$rel)) {
|
||||||
// Deleted file unknown to the graph — no edge ever
|
// Deleted file unknown to the graph — no edge ever
|
||||||
@ -854,6 +890,187 @@ final class Graph
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isBladePath(string $rel): bool
|
||||||
|
{
|
||||||
|
return str_starts_with($rel, 'resources/views/') && str_ends_with($rel, '.blade.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isBladeComponentPath(string $rel): bool
|
||||||
|
{
|
||||||
|
return str_starts_with($rel, 'resources/views/components/') && str_ends_with($rel, '.blade.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string> Project-relative test files.
|
||||||
|
*/
|
||||||
|
private function affectedByStaticBladeUsage(string $changedBlade): array
|
||||||
|
{
|
||||||
|
$ancestors = $this->bladeAncestorsFor($changedBlade);
|
||||||
|
|
||||||
|
if ($ancestors === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ancestorIds = [];
|
||||||
|
foreach ($ancestors as $ancestor) {
|
||||||
|
if (isset($this->fileIds[$ancestor])) {
|
||||||
|
$ancestorIds[$this->fileIds[$ancestor]] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ancestorIds === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$affected = [];
|
||||||
|
foreach ($this->edges as $testFile => $ids) {
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
if (isset($ancestorIds[$id])) {
|
||||||
|
$affected[$testFile] = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys($affected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string> Project-relative Blade files that statically depend on $changedBlade, directly or transitively.
|
||||||
|
*/
|
||||||
|
private function bladeAncestorsFor(string $changedBlade): array
|
||||||
|
{
|
||||||
|
$allBladeFiles = $this->allBladeFiles();
|
||||||
|
|
||||||
|
if ($allBladeFiles === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$targets = [$changedBlade => true];
|
||||||
|
$ancestors = [];
|
||||||
|
$changed = true;
|
||||||
|
|
||||||
|
while ($changed) {
|
||||||
|
$changed = false;
|
||||||
|
|
||||||
|
foreach ($allBladeFiles as $candidate) {
|
||||||
|
if (isset($targets[$candidate])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isset($ancestors[$candidate])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = @file_get_contents($this->projectRoot.'/'.$candidate);
|
||||||
|
if ($source === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_keys($targets) as $target) {
|
||||||
|
if ($this->bladeSourceReferences($source, $target)) {
|
||||||
|
$ancestors[$candidate] = true;
|
||||||
|
$targets[$candidate] = true;
|
||||||
|
$changed = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys($ancestors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function allBladeFiles(): array
|
||||||
|
{
|
||||||
|
$views = $this->projectRoot.'/resources/views';
|
||||||
|
|
||||||
|
if (! is_dir($views)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
$iterator = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator($views, \FilesystemIterator::SKIP_DOTS),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if (! $file instanceof \SplFileInfo || ! $file->isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = $file->getPathname();
|
||||||
|
if (! str_ends_with($path, '.blade.php')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files[] = str_replace(DIRECTORY_SEPARATOR, '/', substr($path, strlen($this->projectRoot) + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
sort($files);
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function bladeSourceReferences(string $source, string $targetBlade): bool
|
||||||
|
{
|
||||||
|
$view = $this->viewNameForBlade($targetBlade);
|
||||||
|
|
||||||
|
if ($view !== null) {
|
||||||
|
$quoted = preg_quote($view, '#');
|
||||||
|
|
||||||
|
if (preg_match('#@(include|includeIf|includeWhen|includeUnless|extends|component|each)\s*\([^)]*[\'\"]'.$quoted.'[\'\"]#', $source) === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('#\b(view|View::make)\s*\(\s*[\'\"]'.$quoted.'[\'\"]#', $source) === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->componentNamesForBlade($targetBlade) as $component) {
|
||||||
|
$quoted = preg_quote($component, '#');
|
||||||
|
|
||||||
|
if (preg_match('#<x-'.$quoted.'(?=[\s>/.:])#i', $source) === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function viewNameForBlade(string $rel): ?string
|
||||||
|
{
|
||||||
|
if (! $this->isBladePath($rel)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tail = substr($rel, strlen('resources/views/'));
|
||||||
|
$tail = substr($tail, 0, -strlen('.blade.php'));
|
||||||
|
|
||||||
|
return str_replace('/', '.', $tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function componentNamesForBlade(string $rel): array
|
||||||
|
{
|
||||||
|
if (! $this->isBladeComponentPath($rel)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$tail = substr($rel, strlen('resources/views/components/'));
|
||||||
|
$tail = substr($tail, 0, -strlen('.blade.php'));
|
||||||
|
$name = str_replace('/', '.', $tail);
|
||||||
|
|
||||||
|
return $name === '' ? [] : [$name, str_replace('_', '-', $name)];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads `$rel` relative to the project root and extracts the
|
* Reads `$rel` relative to the project root and extracts the
|
||||||
* tables it declares via `Schema::create/table/drop/rename`.
|
* tables it declares via `Schema::create/table/drop/rename`.
|
||||||
|
|||||||
Reference in New Issue
Block a user