refactor: Divi-Reiter und divi-check in die Divi-Integration verschoben; CoreIndependenceTest prüft auch Cli, ChangeLog, Pods und die Wurzeldateien, erkennt Alias-Importe und kennt nur noch Plugin.php als Ausnahme

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 22:55:02 +02:00
co-authored by Claude Opus 5.5
parent d0d3488df8
commit 7f9a087b8b
10 changed files with 84 additions and 28 deletions
@@ -10,27 +10,37 @@ use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
/**
* The generic core knows no Divi class: nothing below the CORE_PATHS imports or names a class of
* LIT\MCP\Integrations\Divi. Divi plugs in through ports (AbilityGuard, ToolRefusal, BuilderDetector,
* The generic core knows no Divi class: nothing below the CORE_PATHS and none of the CORE_ROOTS files imports or names
* a class of LIT\MCP\Integrations\Divi. Divi plugs in through ports (AbilityGuard, ToolRefusal, BuilderDetector,
* MediaUsageSource, EnvironmentComponent, ContentSchema's excluded post types) that Plugin.php – the composition
* root, outside these paths – wires up. Divi's own names (et_* …) are DiviBoundaryTest's business, not this test's.
* root, the one listed exception – wires up. Divi's own UI (settings tab, WP-CLI command) lives in
* Integrations/Divi. Divi's own names (et_* …) are DiviBoundaryTest's business, not this test's.
*/
final class CoreIndependenceTest extends TestCase
{
/** Directories below src/ that must not use Divi classes. */
/** Directories below src/ (scanned recursively) that must not use Divi classes. */
private const CORE_PATHS = [
'Abilities',
'Integrations/Core',
'Integrations/WordPress',
'Integrations/Media',
'Admin',
'ChangeLog',
'Cli',
'Guides',
'Integrations/Core',
'Integrations/Media',
'Integrations/Pods',
'Integrations/WordPress',
'Server',
];
/** Files inside CORE_PATHS that may use Divi classes, each with its reason. */
/** CORE_PATHS that may be absent: no generic WP-CLI command exists yet (Divi's lives in Integrations/Divi/Cli). */
private const OPTIONAL_PATHS = ['Cli'];
/** Directories below src/ whose PHP files directly inside (not below) must not use Divi classes either. */
private const CORE_ROOTS = ['', 'Integrations'];
/** Files of the scan that may use Divi classes, each with its reason. */
private const EXCEPTIONS = [
'Admin/DiviTab.php' => 'The settings tab "Divi" is Divi UI: release state, admin check and release/withdraw.',
'Plugin.php' => 'Composition root: builds DiviIntegration and wires its ports (DiviFeatureGate) into the core.',
];
public function testTheCoreUsesNoDiviClass(): void
@@ -63,7 +73,7 @@ final class CoreIndependenceTest extends TestCase
}
}
public function testTheScanFindsImportsQualifiedNamesGroupUsesAndClassStrings(): void
public function testTheScanFindsImportsQualifiedNamesGroupUsesAliasesAndClassStrings(): void
{
$source = <<<'PHP'
<?php
@@ -72,11 +82,13 @@ final class CoreIndependenceTest extends TestCase
$a = \LIT\MCP\Integrations\Divi\Schema\DiviModules::isAvailable();
$b = 'LIT\\MCP\\Integrations\\Divi\\Design\\DesignSystemReader';
$c = "LIT\\MCP\\Integrations\\Divi\\Library\\LibraryAccess";
use LIT\MCP\Integrations\Divi as D;
PHP;
self::assertSame(
[
'Divi\Pages\DiviPages',
'LIT\MCP\Integrations\Divi',
'LIT\MCP\Integrations\Divi\Design\DesignSystemReader',
'LIT\MCP\Integrations\Divi\Library\LibraryAccess',
'LIT\MCP\Integrations\Divi\Release\ReleaseGate',
@@ -114,7 +126,8 @@ final class CoreIndependenceTest extends TestCase
T_CONSTANT_ENCAPSED_STRING => ltrim(str_replace('\\\\', '\\', substr($token[1], 1, -1)), '\\'),
default => '',
};
if (preg_match('/^(?:LIT\\\\MCP\\\\Integrations\\\\)?Divi\\\\[A-Za-z0-9_\\\\]+$/', $text) === 1) {
$diviClass = '/^(?:LIT\\\\MCP\\\\Integrations\\\\)?Divi\\\\[A-Za-z0-9_\\\\]+$/';
if (preg_match($diviClass, $text) === 1 || $text === 'LIT\\MCP\\Integrations\\Divi') {
$names[] = $text;
}
}
@@ -124,12 +137,44 @@ final class CoreIndependenceTest extends TestCase
return $names;
}
/** @return array<string, string> Path below src/ => absolute path of every PHP file in CORE_PATHS. */
public function testEveryScannedPathExists(): void
{
$root = self::root();
foreach ([...self::CORE_PATHS, ...self::CORE_ROOTS] as $directory) {
if (! in_array($directory, self::OPTIONAL_PATHS, true)) {
self::assertDirectoryExists($root . $directory, $directory . ' fehlt – Scan-Liste anpassen');
}
}
}
public function testTheDiviUiLivesInTheDiviIntegration(): void
{
$files = self::coreFiles();
self::assertArrayHasKey('Plugin.php', $files, 'root files are scanned');
self::assertArrayHasKey('Integrations/IntegrationRegistry.php', $files, 'Integrations root files are scanned');
self::assertArrayNotHasKey('Admin/DiviTab.php', $files, 'the Divi tab belongs to Integrations/Divi/Admin');
self::assertFileExists(self::root() . 'Integrations/Divi/Admin/DiviTab.php');
self::assertFileExists(self::root() . 'Integrations/Divi/Cli/DiviCheckCommand.php');
}
private static function root(): string
{
return dirname(__DIR__, 3) . '/src/';
}
/**
* @return array<string, string> Path below src/ => absolute path of every PHP file in CORE_PATHS (recursively) and
* directly in CORE_ROOTS.
*/
private static function coreFiles(): array
{
$root = dirname(__DIR__, 3) . '/src/';
$root = self::root();
$files = [];
foreach (self::CORE_PATHS as $directory) {
if (! is_dir($root . $directory)) {
continue; // Only an OPTIONAL_PATHS entry may be missing (testEveryScannedPathExists).
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root . $directory, FilesystemIterator::SKIP_DOTS)
);
@@ -139,6 +184,11 @@ final class CoreIndependenceTest extends TestCase
}
}
}
foreach (self::CORE_ROOTS as $directory) {
foreach (glob($root . ($directory === '' ? '' : $directory . '/') . '*.php') ?: [] as $path) {
$files[substr($path, strlen($root))] = $path;
}
}
ksort($files);
return $files;